Skip to content

Commit 254acd7

Browse files
committed
v5.2.4
- fix source locations for copied lambdas - amend projection boxes a bare primitive verb - indexed monadic compound assignment a[i]+: - parse errors for malformed assignment
1 parent c4ad67b commit 254acd7

17 files changed

Lines changed: 264 additions & 33 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ make test
3737

3838
```
3939
$ ./gk
40-
gk-v5.2.3 Copyright (c) 2023-2026 Charles Hall
40+
gk-v5.2.4 Copyright (c) 2023-2026 Charles Hall
4141
4242
1+2
4343
3

src/fn.c

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ K fnestack[EVALDEPTH]; /* per-lambda-call stack; depth-indexed, must match A0/
1111
int fnestacki=-1;
1212
static char* spf;
1313

14+
/* A deserialized lambda has definition text but no parse result. Until its
15+
first fnd(), slot 1 carries this private (file;line) marker: the db call is
16+
its local provenance anchor, while line offsets reconstructed from the text
17+
remain virtual. It is never serialized (bd already writes slot 1 as null)
18+
and fnd() consumes it before installing the real parse result. */
19+
static int fnanchor(K x, char **file, int *line) {
20+
if(!x || s(x) || T(x)!=0 || n(x)!=2) return 0;
21+
K *p=px(x);
22+
if(s(p[0]) || T(p[0])!=-3 || s(p[1]) || T(p[1])!=1) return 0;
23+
*file=px(p[0]); *line=ik(p[1]);
24+
return 1;
25+
}
26+
27+
static K fnanchor_new(void) {
28+
K r=tn(0,2),*p=px(r);
29+
p[0]=tnv(3,strlen(pfile),xmemdup(pfile,1+strlen(pfile)));
30+
p[1]=t(1,(u32)fileline);
31+
return r;
32+
}
33+
1434
void fninit(void) {
1535
K f;
1636
f=fnnew("{x dvl,y}"); dset(C,sp("dv"),f); fnfree(f);
@@ -71,15 +91,48 @@ static K fncp_(K x) {
7191
pf[1]=null; /* parse result */
7292
pf[2]=null; /* scope */
7393
pf[3]=FN_VF(0,0); /* valence + force-monad FM[] index */
74-
if((p=fnd(f))) { fnfree(f); return p; }
94+
95+
/* fnd() reparses the definition, and pgparse() stamps that fresh parse with
96+
the current pfile/fileline globals. A copy may be made long after and in
97+
a different file from the definition (a lambda projection does exactly
98+
that), so using the current globals moves error locations to the copy
99+
site. Recover the definition location from the source parse before the
100+
reparse; lex() will add the proper relative offsets for nested lambdas. */
101+
char *pfile0=pfile;
102+
int fileline0=fileline;
103+
int filevirtual0=filevirtual;
104+
char *anchorfile;
105+
int anchorline;
106+
if(fnanchor(px[1],&anchorfile,&anchorline)) {
107+
pfile=anchorfile;
108+
fileline=anchorline;
109+
filevirtual=1;
110+
}
111+
else if(px[1] && px[1]!=null && !T(px[1]) && n(px[1])) {
112+
K loc=((K*)px(px[1]))[0];
113+
if(loc && !T(loc) && n(loc)>=7) {
114+
K *ploc=px(loc);
115+
if(-3==T(ploc[4]) && 1==T(ploc[6])) {
116+
pfile=px(ploc[4]);
117+
i32 base=ik(ploc[6]);
118+
filevirtual=base<0;
119+
fileline=filevirtual?~base:base;
120+
}
121+
}
122+
}
123+
p=fnd(f);
124+
pfile=pfile0;
125+
fileline=fileline0;
126+
filevirtual=filevirtual0;
127+
if(p) { fnfree(f); return p; }
75128
/* A lambda scope's slot[0] (parent) is the environment its FREE variables
76129
resolve against. fnd just bound the fresh copy's parent to the ambient
77130
cs -- but a COPY of a lambda must keep the SOURCE's captured environment,
78131
otherwise free variables rebind to whatever scope did the copy (dynamic
79132
scope; lost closures -- e.g. a local-capturing lambda passed to another
80133
function). The source parent is correct whether it is a live lexical
81134
enclosing scope or a frozen closure snapshot (slot[3]==1). */
82-
if(px[2]!=null && pf[2]!=null) {
135+
if(px[2] && px[2]!=null && pf[2] && pf[2]!=null) {
83136
K *sps=px(px[2]); K *nps=px(pf[2]);
84137
_k(nps[0]); nps[0]=k_(sps[0]);
85138
((K*)px(nps[0]))[4]=t(1,1); /* the copy parents there too (scope.c slot 4) */
@@ -497,6 +550,7 @@ K fnrestore(K f) {
497550
K *pf, cap, sc, *psc, *ps, p;
498551
if(0xc3!=s(f)) return f;
499552
pf=px(f);
553+
if(pf[1]==null) pf[1]=fnanchor_new();
500554
if(0x80!=s(pf[2])) return f; /* not a closure blob */
501555
cap=pf[2];
502556
pf[2]=null; /* let fnd() build the real scope */
@@ -532,7 +586,7 @@ K fnrestore(K f) {
532586
(b)[(l)]=(c); \
533587
} while (0)
534588

535-
K fnd(K f) {
589+
static K fnd_(K f) {
536590
K p,r=0,*pf;
537591
char *ff=0,*ff0=0,*b,**v,*g,*h;
538592
int j,s,n,q,vx,vy,vz,ffq=0,first=1,params=1;
@@ -760,6 +814,24 @@ K fnd(K f) {
760814
return r;
761815
}
762816

817+
K fnd(K f) {
818+
K *pf=px(f),anchor=pf[1],r;
819+
char *anchorfile,*pfile0;
820+
int anchorline,fileline0,filevirtual0;
821+
if(!fnanchor(anchor,&anchorfile,&anchorline)) return fnd_(f);
822+
823+
/* The marker owns anchorfile, so keep anchor alive until fnd_ has copied the
824+
filename into every parse frame. Detach it first: fnd_ replaces slot 1
825+
with the actual parse result. */
826+
pf[1]=null;
827+
pfile0=pfile; fileline0=fileline; filevirtual0=filevirtual;
828+
pfile=anchorfile; fileline=anchorline; filevirtual=1;
829+
r=fnd_(f);
830+
pfile=pfile0; fileline=fileline0; filevirtual=filevirtual0;
831+
_k(anchor);
832+
return r;
833+
}
834+
763835
// make a copy of r and parent scope
764836
// closure only if parent scope == s0
765837
// can't create a closure like this:

src/k.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,7 @@ K kamendi3(K d, K i, K f) {
10121012
K args=tn(0,3); K *pa=px(args);
10131013
pa[0]=kcp(d); if(E(pa[0])) { e=pa[0]; _k(args); goto cleanup; }
10141014
pa[1]=kcp(i); if(E(pa[1])) { e=pa[1]; _k(args); goto cleanup; }
1015-
pa[2]=f?kcp(f):inull; if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
1015+
pa[2]=!f?inull:f<256?t(1,st(0xc0,f+32)):kcp(f); if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
10161016
args=st(0x81,args);
10171017
K g=t(1,st(0xc0,45)); /* @ */
10181018
_k(d); _k(i); _k(f);
@@ -1784,7 +1784,7 @@ K kamendi4(K d, K i, K f, K y) {
17841784
K args=tn(0,4); K *pa=px(args);
17851785
pa[0]=kcp(d); if(E(pa[0])) { e=pa[0]; _k(args); goto cleanup; }
17861786
pa[1]=kcp(i); if(E(pa[1])) { e=pa[1]; _k(args); goto cleanup; }
1787-
pa[2]=f?kcp(f):inull; if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
1787+
pa[2]=!f?inull:f<256?t(1,st(0xc0,f+32)):kcp(f); if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
17881788
pa[3]=kcp(y); if(E(pa[3])) { e=pa[3]; _k(args); goto cleanup; }
17891789
args=st(0x81,args);
17901790
K g=t(1,st(0xc0,45)); /* @ */
@@ -2401,7 +2401,7 @@ K kamend3(K d, K i, K f) {
24012401
K args=tn(0,3); K *pa=px(args);
24022402
pa[0]=kcp(d); if(E(pa[0])) { e=pa[0]; _k(args); goto cleanup; }
24032403
pa[1]=kcp(i); if(E(pa[1])) { e=pa[1]; _k(args); goto cleanup; }
2404-
pa[2]=f?kcp(f):inull; if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
2404+
pa[2]=!f?inull:f<256?t(1,st(0xc0,f+32)):kcp(f); if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
24052405
args=st(0x81,args);
24062406
K g=t(1,st(0xc0,43)); /* . */
24072407
_k(d); _k(i); _k(f);
@@ -3043,7 +3043,7 @@ K kamend4(K d, K i, K f, K y) {
30433043
K args=tn(0,4); K *pa=px(args);
30443044
pa[0]=kcp(d); if(E(pa[0])) { e=pa[0]; _k(args); goto cleanup; }
30453045
pa[1]=kcp(i); if(E(pa[1])) { e=pa[1]; _k(args); goto cleanup; }
3046-
pa[2]=f?kcp(f):inull; if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
3046+
pa[2]=!f?inull:f<256?t(1,st(0xc0,f+32)):kcp(f); if(E(pa[2])) { e=pa[2]; _k(args); goto cleanup; }
30473047
pa[3]=kcp(y); if(E(pa[3])) { e=pa[3]; _k(args); goto cleanup; }
30483048
args=st(0x81,args);
30493049
K g=t(1,st(0xc0,43)); /* . */

src/lex.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ static int gf(pgs *pgs) {
590590
char *p1_=p1;
591591
int line_=line;
592592
int fileline0=fileline;
593-
fileline+=startline;
593+
if(!filevirtual) fileline+=startline;
594594
f=fnnew(q);
595595
p=p_; p0=p0_; p1=p1_; line=line_;
596596
fileline=fileline0;

src/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ int main(int argc, char **argv) {
8282
setvbuf(stderr, NULL, _IONBF, 0); /* glibc has this by default; Windows pipes
8383
don't -- without it, buffered stderr (prompts/errors) races the unbuffered
8484
stdout (results) and the merged transcript reorders intermittently */
85-
if(!quiet) fprintf(stderr, "gk-v5.2.3 Copyright (c) 2023-2026 Charles Hall\n\n");
85+
if(!quiet) fprintf(stderr, "gk-v5.2.4 Copyright (c) 2023-2026 Charles Hall\n\n");
8686
#ifdef _WIN32
8787
SetConsoleCtrlHandler(ctlc,TRUE);
8888
#else

src/p.c

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ long gk_alloc_budget=GK_ALLOC_BUDGET;
5050
#endif
5151
int opencode=1;
5252
char *pfile="";
53-
int gline,glinei,gline0,gline0i,fileline;
53+
int gline,glinei,gline0,gline0i,fileline,filevirtual;
5454

5555
/* A projection of a LAMBDA is a noun, exactly like the lambda itself: a verb
5656
juxtaposed on its left applies (`,{x+y}[1]` enlists, as `,{x+y}` does), it
@@ -264,8 +264,15 @@ static void printerror(K v, K x0, int i) {
264264
char *ff0=px(f0);
265265
if(pline[i]) { /* multiline lambda */
266266
if(strlen(pfile)) {
267-
fprintf(stderr,"%s ... + %d in %s:%d\n",ff0,pline[i],pfile,1+pline[i]+ik(px0[6]));
268-
LOADLINE=1+pline[i]+ik(px0[6]);
267+
i32 base=ik(px0[6]);
268+
if(base<0) {
269+
fprintf(stderr,"%s ... + %d from %s:%d\n",ff0,pline[i],pfile,-base);
270+
LOADLINE=-base;
271+
}
272+
else {
273+
fprintf(stderr,"%s ... + %d in %s:%d\n",ff0,pline[i],pfile,1+pline[i]+base);
274+
LOADLINE=1+pline[i]+base;
275+
}
269276
}
270277
else fprintf(stderr,"%s ... + %d\n",ff0,pline[i]);
271278
kprint(v,"","\n","");
@@ -278,8 +285,9 @@ static void printerror(K v, K x0, int i) {
278285
}
279286
else { /* single line lambda */
280287
if(strlen(pfile)) {
281-
fprintf(stderr,"in %s:%d\n",pfile,1+ik(px0[6]));
282-
LOADLINE=1+ik(px0[6]);
288+
i32 base=ik(px0[6]);
289+
if(base<0) { fprintf(stderr,"from %s:%d\n",pfile,-base); LOADLINE=-base; }
290+
else { fprintf(stderr,"in %s:%d\n",pfile,1+base); LOADLINE=1+base; }
283291
}
284292
kprint(v,"","\n","");
285293
fprintf(stderr,"%s\n",ff0);
@@ -555,6 +563,9 @@ static K r44(K x) {
555563
return r;
556564
}
557565

566+
/* The assign verbs ':' and '::' never take a bracket RHS */
567+
if(':'==f0 || 0x82==s(f0)) { _k(x); return KERR_PARSE; }
568+
558569
/* fast path: f[a;b;...] where f is a simple variable resolving to a
559570
0xc3 lambda, 0xd9 projection, or 0xda wrapper, and the param list
560571
is 0x41 or 0x81. Bypasses avb/strlen/strchr/snprintf and the
@@ -1277,7 +1288,9 @@ K pgreduce_(K x0, int *quiet) {
12771288
--pA;
12781289
b=*--pA;
12791290
a=*--pA;
1280-
if(!a||!b) { _k(a); _k(b); *pA++=KERR_TYPE; break; } /* backstop: never store/amend a NULL operand (mirrors the 0x81 guard above). Known producers are fixed at the push site, but the operand-stack-never-NULL invariant has several entry points. */
1291+
if(!a||!b) { _k(a); _k(b); *pA++=KERR_TYPE; break; } /* backstop: never store/amend a NULL operand */
1292+
/* a bare bracket group is not a value */
1293+
if(0x41==s(b)) { _k(a); _k(b); *pA++=KERR_PARSE; break; }
12811294
if(s(b)) { b=reduce(b); if(E(b)||EXIT) { _k(a); *pA++=b; break; } }
12821295
if(0x40==s(a)) { /* a::1 */
12831296
if(!VST(b)) { _k(b); *pA++=KERR_PARSE; break; }
@@ -1345,14 +1358,60 @@ K pgreduce_(K x0, int *quiet) {
13451358
if(pA<=A+1) { k_(v); break; }
13461359
--pA;
13471360
a=*--pA;
1348-
if(0x40!=s(a)) { _k(a); *pA++=KERR_VALUE; break; }
1349-
if(KERR_VALUE==(a_=vlookup(a))) a_=null;
1350-
if(E(a_)) { *pA++=a_; break; }
1351-
t=k(strchr(P,ik(v))-P,0,a_);
1352-
if(E(t)) { *pA++=t; break; };
1353-
p=scope_set(cs,a,t);
1354-
if(E(p)) { *pA++=p; } /* t already freed by scope_set */
1355-
else { *pA++=p; *quiet=1; }
1361+
if(0x40==s(a)) {
1362+
K rs; if(KERR_VALUE==(a_=vlookuprs(a,&rs))) { a_=null; rs=scope_home(); }
1363+
if(E(a_)) { *pA++=a_; break; }
1364+
rs=asnrs(rs);
1365+
t=k(strchr(P,ik(v))-P,0,a_);
1366+
if(E(t)||EXIT) { *pA++=t; break; };
1367+
p=scope_set(rs,a,t);
1368+
if(E(p)) { *pA++=p; } /* t already freed by scope_set */
1369+
else { *pA++=p; *quiet=1; }
1370+
}
1371+
else if(0x44==s(a)) { /* a[0]-: - amend with the monad */
1372+
pa=px(a);
1373+
K target=pa[0]; K a_=k_(target); K i_=k_(pa[1]); _k(a);
1374+
if(0x41==s(i_)) {
1375+
if(n(i_)) {
1376+
i_=r41(i_); if(E(i_)||EXIT) { _k(a_); *pA++=i_; break; }
1377+
if(0x81==s(i_)) i_=b(48)&i_;
1378+
}
1379+
else { _k(i_); i_=null; }
1380+
}
1381+
else if(0x81==s(i_)) {
1382+
if(n(i_)) i_=b(48)&i_;
1383+
else { _k(i_); i_=null; }
1384+
}
1385+
else { _k(a_); _k(i_); *pA++=KERR_TYPE; break; }
1386+
if(0x40!=s(a_)) { _k(a_); _k(i_); *pA++=KERR_TYPE; break; }
1387+
// resolve, then apply closure check
1388+
K rs; if(KERR_VALUE==(a_=vlookuprs(a_,&rs))) { a_=null; rs=scope_home(); }
1389+
if(E(a_)) { _k(i_); *pA++=a_; break; }
1390+
{ K rsf=rs; rs=asnrs(rsf);
1391+
/* redirected write (non-closure parent / namespace): the found
1392+
binding survives, so kamend3 must not amend it in place */
1393+
if(rs!=rsf && a_!=null && (T(a_)<=0||T(a_)==2) && ((ko*)(b(48)&a_))->r) {
1394+
K a2=kcp(a_); _k(a_); a_=a2;
1395+
if(E(a_)) { _k(i_); *pA++=a_; break; }
1396+
} }
1397+
K r=kamend3(a_,k_(i_),strchr(P,ik(v))-P);
1398+
if(E(r)) { _k(i_); *pA++=r; }
1399+
else {
1400+
u64 zi=i+1; while(zi<nx && 0x83==s(px[zi])) ++zi;
1401+
if(disc&&zi>=nx) { _k(i_); t=null; } /* statement position: value freed unread */
1402+
else {
1403+
/* Compound assignment returns the selected values after the
1404+
complete amend (not the whole amended container). */
1405+
t=k(11,k_(r),i_);
1406+
/* selection failure must not discard the completed write */
1407+
if(E(t)||EXIT) { if(t>=256) _k(t); t=null; }
1408+
}
1409+
p=scope_set(rs,target,r);
1410+
if(E(p)) { _k(t); *pA++=p; } /* r already freed by scope_set */
1411+
else { _k(p); *pA++=t; *quiet=1; }
1412+
}
1413+
}
1414+
else { _k(a); *pA++=KERR_VALUE; break; }
13561415
break;
13571416
case 0xce: /* a+:1 */
13581417
if(pA<=A+2) {
@@ -1932,7 +1991,8 @@ apply_n_fallback: {
19321991
}
19331992
break;
19341993
case 2: /* 64 64 66 ... */
1935-
if(pA<=A+1) { *pA++=KERR_VALENCE; break; }
1994+
/* an assign token with no target/value to consume is a malformed statement */
1995+
if(pA<=A+1) { *pA++=KERR_PARSE; break; }
19361996
b=*--pA;
19371997
a=*--pA;
19381998

@@ -3710,7 +3770,7 @@ static K listbc(pgs *s, pn *a, int t) {
37103770
((K*)px(pz[k]))[3]=line;
37113771
((K*)px(pz[k]))[4]=tnv(3,strlen(s->file),xmemdup(s->file,1+strlen(s->file)));
37123772
((K*)px(pz[k]))[5]=t(1,(u32)a->line); // gline
3713-
((K*)px(pz[k]))[6]=t(1,(u32)fileline); // ggline
3773+
((K*)px(pz[k]))[6]=t(1,(u32)s->fileline); // ggline
37143774
bc(s,a->a[i],values,index,line,&vm);
37153775
if(n(values)==1) {
37163776
pv=px(values);
@@ -4819,6 +4879,12 @@ K pgparse(char *q, int load, K locals) {
48194879
pz=px(z);
48204880
s->p=q;
48214881
s->file=pfile;
4882+
/* A negative frame base marks source reconstructed from a serialized
4883+
definition. Its relative lambda lines are real, but its file location is
4884+
only an anchor, so printerror prints "+N from FILE:LINE" without adding N
4885+
to the physical line. Keep the process-global fileline itself ordinary:
4886+
the lexer performs arithmetic on it while finding nested lambdas. */
4887+
s->fileline=filevirtual?~fileline:fileline;
48224888
s->valuesmax=256;
48234889
s->ti=0;s->tc=0;s->si=-1;s->ri=-1;s->vi=-1;
48244890
if(opencode) stmt=ksplit(q,"\r\n");
@@ -4842,9 +4908,9 @@ K pgparse(char *q, int load, K locals) {
48424908
((K*)px(pz[zn]))[1]=index;
48434909
((K*)px(pz[zn]))[2]=k_(stmt);
48444910
((K*)px(pz[zn]))[3]=line;
4845-
((K*)px(pz[zn]))[4]=tnv(3,strlen(pfile),xmemdup(pfile,1+strlen(pfile)));
4911+
((K*)px(pz[zn]))[4]=tnv(3,strlen(s->file),xmemdup(s->file,1+strlen(s->file)));
48464912
((K*)px(pz[zn]))[5]=t(1,(u32)gline);
4847-
((K*)px(pz[zn]))[6]=t(1,(u32)fileline);
4913+
((K*)px(pz[zn]))[6]=t(1,(u32)s->fileline);
48484914
n(z)++;
48494915
s->values=values;
48504916
s->index=index;

src/p.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ typedef struct {
6767
K locals;
6868
int overflow; /* set by mark_lvalues when a statement's parse tree exceeds
6969
maxr depth; pgparse turns it into a "stack" error */
70+
int fileline; /* file base line captured with file at pgparse entry */
7071
} pgs;
7172

7273
extern int quiet,RETURN;
@@ -96,7 +97,7 @@ extern long gk_budget;
9697
extern long gk_alloc_budget;
9798
#define GK_ALLOC_BUDGET (64L*1024*1024)
9899
#endif
99-
extern int gline,glinei,gline0,gline0i,fileline;
100+
extern int gline,glinei,gline0,gline0i,fileline,filevirtual;
100101
extern char *glinep,*gline0p;
101102
extern K params[];
102103
extern int paramsi;

src/wasm.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ EMSCRIPTEN_KEEPALIVE void gk_init(void) {
3434
ipc_init_ns();
3535
wasm = 1; /* no interactive error subconsole in a browser tab (no stdin) */
3636
/* The startup banner (same as main.c). To stdout so it shows in the REPL output. */
37-
printf("gk-v5.2.3 Copyright (c) 2023-2026 Charles Hall\n\n");
37+
printf("gk-v5.2.4 Copyright (c) 2023-2026 Charles Hall\n\n");
3838
}
3939

4040
/* An eval error opens gk's interactive `>` debug subconsole IN PLACE (p.c:312-317:

t/r805

Whitespace-only changes.

t/r806

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-1 2 3
2+
(1 -2
3+
3 4)
4+
-1 2 3
5+
-3
6+
-1 -2 -3
7+
-1 -2 -3
8+
-1 -2 -3
9+
9
10+
6 2 3
11+
-1 -2 -3

0 commit comments

Comments
 (0)