diff options
| author | Sérgio Queiroz <sqmedeiros@gmail.com> | 2017-12-15 16:31:41 -0300 |
|---|---|---|
| committer | Sérgio Queiroz <sqmedeiros@gmail.com> | 2017-12-15 16:31:41 -0300 |
| commit | 4bdf8d40a9ca5f00e454a38d301a3ab35c9b50d5 (patch) | |
| tree | e59c5041761c237f27fe51589901da5a963ee103 | |
| parent | 0f53a65f4a32c8be2d84c4a8172b885065f7c1e5 (diff) | |
| download | lpeglabel-4bdf8d40a9ca5f00e454a38d301a3ab35c9b50d5.tar.gz lpeglabel-4bdf8d40a9ca5f00e454a38d301a3ab35c9b50d5.tar.bz2 lpeglabel-4bdf8d40a9ca5f00e454a38d301a3ab35c9b50d5.zip | |
Removing code related to labeled choice and recovery operators and updating tests
| -rw-r--r-- | lpcode.c | 39 | ||||
| -rw-r--r-- | lptree.c | 36 | ||||
| -rw-r--r-- | lptree.h | 4 | ||||
| -rw-r--r-- | lptypes.h | 15 | ||||
| -rw-r--r-- | lpvm.c | 69 | ||||
| -rw-r--r-- | lpvm.h | 1 | ||||
| -rw-r--r-- | testlabel.lua | 239 |
7 files changed, 102 insertions, 301 deletions
| @@ -220,9 +220,6 @@ int checkaux (TTree *tree, int pred) { | |||
| 220 | if (checkaux(sib2(tree), pred)) return 1; | 220 | if (checkaux(sib2(tree), pred)) return 1; |
| 221 | /* else return checkaux(sib1(tree), pred); */ | 221 | /* else return checkaux(sib1(tree), pred); */ |
| 222 | tree = sib1(tree); goto tailcall; | 222 | tree = sib1(tree); goto tailcall; |
| 223 | case TLabChoice: /* labeled failure */ | ||
| 224 | /* we do not know whether sib2 will be evaluated */ | ||
| 225 | tree = sib1(tree); goto tailcall; | ||
| 226 | case TCapture: case TGrammar: case TRule: | 223 | case TCapture: case TGrammar: case TRule: |
| 227 | /* return checkaux(sib1(tree), pred); */ | 224 | /* return checkaux(sib1(tree), pred); */ |
| 228 | tree = sib1(tree); goto tailcall; | 225 | tree = sib1(tree); goto tailcall; |
| @@ -264,7 +261,7 @@ int fixedlen (TTree *tree) { | |||
| 264 | /* else return fixedlen(sib2(tree)) + len; */ | 261 | /* else return fixedlen(sib2(tree)) + len; */ |
| 265 | len += n1; tree = sib2(tree); goto tailcall; | 262 | len += n1; tree = sib2(tree); goto tailcall; |
| 266 | } | 263 | } |
| 267 | case TChoice: case TLabChoice: { /* labeled failure */ | 264 | case TChoice: { |
| 268 | int n1 = fixedlen(sib1(tree)); | 265 | int n1 = fixedlen(sib1(tree)); |
| 269 | int n2 = fixedlen(sib2(tree)); | 266 | int n2 = fixedlen(sib2(tree)); |
| 270 | if (n1 != n2 || n1 < 0) | 267 | if (n1 != n2 || n1 < 0) |
| @@ -314,7 +311,7 @@ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { | |||
| 314 | loopset(i, firstset->cs[i] = follow->cs[i]); /* follow = fullset(?) */ | 311 | loopset(i, firstset->cs[i] = follow->cs[i]); /* follow = fullset(?) */ |
| 315 | return 1; | 312 | return 1; |
| 316 | } | 313 | } |
| 317 | case TChoice: case TLabChoice: { /*(?) labeled failure */ | 314 | case TChoice: { |
| 318 | Charset csaux; | 315 | Charset csaux; |
| 319 | int e1 = getfirst(sib1(tree), follow, firstset); | 316 | int e1 = getfirst(sib1(tree), follow, firstset); |
| 320 | int e2 = getfirst(sib2(tree), follow, &csaux); | 317 | int e2 = getfirst(sib2(tree), follow, &csaux); |
| @@ -399,7 +396,7 @@ static int headfail (TTree *tree) { | |||
| 399 | if (!nofail(sib2(tree))) return 0; | 396 | if (!nofail(sib2(tree))) return 0; |
| 400 | /* else return headfail(sib1(tree)); */ | 397 | /* else return headfail(sib1(tree)); */ |
| 401 | tree = sib1(tree); goto tailcall; | 398 | tree = sib1(tree); goto tailcall; |
| 402 | case TChoice: case TLabChoice: /* labeled failure */ | 399 | case TChoice: |
| 403 | if (!headfail(sib1(tree))) return 0; | 400 | if (!headfail(sib1(tree))) return 0; |
| 404 | /* else return headfail(sib2(tree)); */ | 401 | /* else return headfail(sib2(tree)); */ |
| 405 | tree = sib2(tree); goto tailcall; | 402 | tree = sib2(tree); goto tailcall; |
| @@ -419,7 +416,7 @@ static int needfollow (TTree *tree) { | |||
| 419 | case TChar: case TSet: case TAny: | 416 | case TChar: case TSet: case TAny: |
| 420 | case TFalse: case TTrue: case TAnd: case TNot: | 417 | case TFalse: case TTrue: case TAnd: case TNot: |
| 421 | case TRunTime: case TGrammar: case TCall: case TBehind: | 418 | case TRunTime: case TGrammar: case TCall: case TBehind: |
| 422 | case TThrow: case TLabChoice: /* (?)labeled failure */ | 419 | case TThrow: /* (?)labeled failure */ |
| 423 | return 0; | 420 | return 0; |
| 424 | case TChoice: case TRep: | 421 | case TChoice: case TRep: |
| 425 | return 1; | 422 | return 1; |
| @@ -456,8 +453,6 @@ int sizei (const Instruction *i) { | |||
| 456 | return 2; | 453 | return 2; |
| 457 | case IThrowRec: /* labeled failure */ | 454 | case IThrowRec: /* labeled failure */ |
| 458 | return 3; | 455 | return 3; |
| 459 | case ILabChoice: | ||
| 460 | return (CHARSETINSTSIZE - 1) + 2; /* labeled failure */ | ||
| 461 | 456 | ||
| 462 | default: return 1; | 457 | default: return 1; |
| 463 | } | 458 | } |
| @@ -522,8 +517,7 @@ static int addinstruction (CompileState *compst, Opcode op, int aux) { | |||
| 522 | static int addoffsetinst (CompileState *compst, Opcode op) { | 517 | static int addoffsetinst (CompileState *compst, Opcode op) { |
| 523 | int i = addinstruction(compst, op, 0); /* instruction */ | 518 | int i = addinstruction(compst, op, 0); /* instruction */ |
| 524 | addinstruction(compst, (Opcode)0, 0); /* open space for offset */ | 519 | addinstruction(compst, (Opcode)0, 0); /* open space for offset */ |
| 525 | assert(op == ITestSet || sizei(&getinstr(compst, i)) == 2 || | 520 | assert(op == ITestSet || sizei(&getinstr(compst, i)) == 2); |
| 526 | op == ILabChoice); /* labeled failure */ | ||
| 527 | return i; | 521 | return i; |
| 528 | } | 522 | } |
| 529 | 523 | ||
| @@ -746,24 +740,6 @@ static void codechoice (CompileState *compst, TTree *p1, TTree *p2, int opt, | |||
| 746 | } | 740 | } |
| 747 | 741 | ||
| 748 | 742 | ||
| 749 | /* labeled failure begin */ | ||
| 750 | static void codelabchoice (CompileState *compst, TTree *p1, TTree *p2, int opt, | ||
| 751 | const Charset *fl, const byte *cs) { | ||
| 752 | int emptyp2 = (p2->tag == TTrue); | ||
| 753 | int pcommit; | ||
| 754 | int test = NOINST; | ||
| 755 | int pchoice = addoffsetinst(compst, ILabChoice); | ||
| 756 | addcharset(compst, cs); | ||
| 757 | codegen(compst, p1, emptyp2, test, fullset); | ||
| 758 | pcommit = addoffsetinst(compst, ICommit); | ||
| 759 | jumptohere(compst, pchoice); | ||
| 760 | jumptohere(compst, test); | ||
| 761 | codegen(compst, p2, opt, NOINST, fl); | ||
| 762 | jumptohere(compst, pcommit); | ||
| 763 | } | ||
| 764 | /* labeled failure end */ | ||
| 765 | |||
| 766 | |||
| 767 | /* | 743 | /* |
| 768 | ** And predicate | 744 | ** And predicate |
| 769 | ** optimization: fixedlen(p) = n ==> <&p> == <p>; behind n | 745 | ** optimization: fixedlen(p) = n ==> <&p> == <p>; behind n |
| @@ -1001,10 +977,6 @@ static void codegen (CompileState *compst, TTree *tree, int opt, int tt, | |||
| 1001 | codethrow(compst, tree); | 977 | codethrow(compst, tree); |
| 1002 | break; | 978 | break; |
| 1003 | } | 979 | } |
| 1004 | case TLabChoice: { /* labeled failure */ | ||
| 1005 | codelabchoice(compst, sib1(tree), sib2(tree), opt, fl, treelabelset(tree)); | ||
| 1006 | break; | ||
| 1007 | } | ||
| 1008 | default: assert(0); | 980 | default: assert(0); |
| 1009 | } | 981 | } |
| 1010 | } | 982 | } |
| @@ -1027,7 +999,6 @@ static void peephole (CompileState *compst) { | |||
| 1027 | switch (code[i].i.code) { | 999 | switch (code[i].i.code) { |
| 1028 | case IChoice: case ICall: case ICommit: case IPartialCommit: | 1000 | case IChoice: case ICall: case ICommit: case IPartialCommit: |
| 1029 | case IBackCommit: case ITestChar: case ITestSet: | 1001 | case IBackCommit: case ITestChar: case ITestSet: |
| 1030 | case ILabChoice: /* labeled failure */ | ||
| 1031 | case ITestAny: { /* instructions with labels */ | 1002 | case ITestAny: { /* instructions with labels */ |
| 1032 | jumptothere(compst, i, finallabel(code, i)); /* optimize label */ | 1003 | jumptothere(compst, i, finallabel(code, i)); /* optimize label */ |
| 1033 | break; | 1004 | break; |
| @@ -28,7 +28,7 @@ const byte numsiblings[] = { | |||
| 28 | 0, 0, 2, 1, /* call, opencall, rule, grammar */ | 28 | 0, 0, 2, 1, /* call, opencall, rule, grammar */ |
| 29 | 1, /* behind */ | 29 | 1, /* behind */ |
| 30 | 1, 1, /* capture, runtime capture */ | 30 | 1, 1, /* capture, runtime capture */ |
| 31 | 0, 2, 2 /* labeled failure throw, recovery, labeled choice */ | 31 | 0 /* labeled failure throw */ |
| 32 | }; | 32 | }; |
| 33 | 33 | ||
| 34 | 34 | ||
| @@ -533,21 +533,6 @@ static TTree *newthrowleaf (lua_State *L) { | |||
| 533 | tree->u.s.ps = 0; /* there is no recovery rule associated */ | 533 | tree->u.s.ps = 0; /* there is no recovery rule associated */ |
| 534 | return tree; | 534 | return tree; |
| 535 | } | 535 | } |
| 536 | |||
| 537 | static TTree *newrootlab2sib (lua_State *L, int tag) { | ||
| 538 | int s1, s2; | ||
| 539 | TTree *tree1 = getpatt(L, 1, &s1); | ||
| 540 | TTree *tree2 = getpatt(L, 2, &s2); | ||
| 541 | TTree *tree = newtree(L, bytes2slots(LABELSETSIZE) + 1 + s1 + s2); /* create new tree */ | ||
| 542 | tree->tag = tag; | ||
| 543 | tree->u.s.ps = 1 + s1; | ||
| 544 | tree->u.s.plab = 1 + s1 + s2; | ||
| 545 | memcpy(sib1(tree), tree1, s1 * sizeof(TTree)); | ||
| 546 | memcpy(sib2(tree), tree2, s2 * sizeof(TTree)); | ||
| 547 | loopset(i, treelabelset(tree)[i] = 0); | ||
| 548 | joinktables(L, 1, sib2(tree), 2); | ||
| 549 | return tree; | ||
| 550 | } | ||
| 551 | /* labeled failure end */ | 536 | /* labeled failure end */ |
| 552 | 537 | ||
| 553 | 538 | ||
| @@ -736,22 +721,6 @@ static int lp_throw (lua_State *L) { | |||
| 736 | tree->key = addtonewktable(L, 0, 1); | 721 | tree->key = addtonewktable(L, 0, 1); |
| 737 | return 1; | 722 | return 1; |
| 738 | } | 723 | } |
| 739 | |||
| 740 | |||
| 741 | /* | ||
| 742 | ** labeled choice function | ||
| 743 | */ | ||
| 744 | static int lp_labchoice (lua_State *L) { | ||
| 745 | int n = lua_gettop(L); | ||
| 746 | TTree *tree = newrootlab2sib(L, TLabChoice); | ||
| 747 | int i; | ||
| 748 | for (i = 3; i <= n; i++) { | ||
| 749 | int d = luaL_checkinteger(L, i); | ||
| 750 | luaL_argcheck(L, d >= 1 && d < MAXLABELS, i, "the number of a label must be between 1 and 255"); | ||
| 751 | setlabel(treelabelset(tree), (byte)d); | ||
| 752 | } | ||
| 753 | return 1; | ||
| 754 | } | ||
| 755 | /* labeled failure end */ | 724 | /* labeled failure end */ |
| 756 | 725 | ||
| 757 | 726 | ||
| @@ -1088,7 +1057,7 @@ static int verifyrule (lua_State *L, TTree *tree, int *passed, int npassed, | |||
| 1088 | return nb; | 1057 | return nb; |
| 1089 | /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */ | 1058 | /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */ |
| 1090 | tree = sib2(tree); goto tailcall; | 1059 | tree = sib2(tree); goto tailcall; |
| 1091 | case TChoice: case TLabChoice: /* must check both children */ /* labeled failure */ | 1060 | case TChoice: /* must check both children */ |
| 1092 | nb = verifyrule(L, sib1(tree), passed, npassed, nb); | 1061 | nb = verifyrule(L, sib1(tree), passed, npassed, nb); |
| 1093 | /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ | 1062 | /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ |
| 1094 | tree = sib2(tree); goto tailcall; | 1063 | tree = sib2(tree); goto tailcall; |
| @@ -1344,7 +1313,6 @@ static struct luaL_Reg pattreg[] = { | |||
| 1344 | {"setmaxstack", lp_setmax}, | 1313 | {"setmaxstack", lp_setmax}, |
| 1345 | {"type", lp_type}, | 1314 | {"type", lp_type}, |
| 1346 | {"T", lp_throw}, /* labeled failure throw */ | 1315 | {"T", lp_throw}, /* labeled failure throw */ |
| 1347 | {"Lc", lp_labchoice}, /* labeled failure choice */ | ||
| 1348 | {NULL, NULL} | 1316 | {NULL, NULL} |
| 1349 | }; | 1317 | }; |
| 1350 | 1318 | ||
| @@ -35,9 +35,7 @@ typedef enum TTag { | |||
| 35 | 'sib1' is capture body */ | 35 | 'sib1' is capture body */ |
| 36 | TRunTime, /* run-time capture: 'key' is Lua function; | 36 | TRunTime, /* run-time capture: 'key' is Lua function; |
| 37 | 'sib1' is capture body */ | 37 | 'sib1' is capture body */ |
| 38 | TThrow, /* labeled failure: ktable[key] is label's */ | 38 | TThrow, /* labeled failure: ktable[key] is label's name */ |
| 39 | TLabChoice /* labed failure: 'sib1' /{labels} 'sib2' */ | ||
| 40 | /* the set of labels is stored in next CHARSETSIZE bytes */ | ||
| 41 | } TTag; | 39 | } TTag; |
| 42 | 40 | ||
| 43 | 41 | ||
| @@ -145,21 +145,6 @@ typedef struct Charset { | |||
| 145 | #define testchar(st,c) (((int)(st)[((c) >> 3)] & (1 << ((c) & 7)))) | 145 | #define testchar(st,c) (((int)(st)[((c) >> 3)] & (1 << ((c) & 7)))) |
| 146 | 146 | ||
| 147 | /* labeled failure begin */ | 147 | /* labeled failure begin */ |
| 148 | #define MAXLABELS (UCHAR_MAX + 1) | ||
| 149 | |||
| 150 | #define LABELSETSIZE CHARSETSIZE | ||
| 151 | |||
| 152 | typedef Charset Labelset; | ||
| 153 | |||
| 154 | #define setlabel setchar | ||
| 155 | |||
| 156 | #define testlabel testchar | ||
| 157 | |||
| 158 | /* access to labelset */ | ||
| 159 | #define treelabelset(t) ((byte *)((t) + (t)->u.s.plab)) | ||
| 160 | |||
| 161 | #define IDXLFAIL 0 | ||
| 162 | |||
| 163 | #define LFAIL 0 | 148 | #define LFAIL 0 |
| 164 | 149 | ||
| 165 | /* update the farthest failure */ | 150 | /* update the farthest failure */ |
| @@ -26,13 +26,6 @@ | |||
| 26 | 26 | ||
| 27 | static const Instruction giveup = {{IGiveup, 0, 0}}; | 27 | static const Instruction giveup = {{IGiveup, 0, 0}}; |
| 28 | 28 | ||
| 29 | /* labeled failure */ | ||
| 30 | static void setlabelfail(Labelset *ls) { | ||
| 31 | loopset(i, ls->cs[i] = 0); | ||
| 32 | ls->cs[IDXLFAIL] = 1; | ||
| 33 | } | ||
| 34 | /* labeled failure end */ | ||
| 35 | |||
| 36 | 29 | ||
| 37 | /* | 30 | /* |
| 38 | ** {====================================================== | 31 | ** {====================================================== |
| @@ -44,7 +37,6 @@ static void setlabelfail(Labelset *ls) { | |||
| 44 | typedef struct Stack { | 37 | typedef struct Stack { |
| 45 | const char *s; /* saved position (or NULL for calls) */ | 38 | const char *s; /* saved position (or NULL for calls) */ |
| 46 | const Instruction *p; /* next instruction */ | 39 | const Instruction *p; /* next instruction */ |
| 47 | const Labelset *ls; /* labeled failure */ | ||
| 48 | int caplevel; | 40 | int caplevel; |
| 49 | } Stack; | 41 | } Stack; |
| 50 | 42 | ||
| @@ -163,10 +155,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 163 | int captop = 0; /* point to first empty slot in captures */ | 155 | int captop = 0; /* point to first empty slot in captures */ |
| 164 | int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ | 156 | int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ |
| 165 | const Instruction *p = op; /* current instruction */ | 157 | const Instruction *p = op; /* current instruction */ |
| 166 | const Instruction *pk = NULL; /* resume instruction */ | 158 | stack->p = &giveup; stack->s = s; stack->caplevel = 0; stack++; /* labeled failure */ |
| 167 | Labelset lsfail; | ||
| 168 | setlabelfail(&lsfail); | ||
| 169 | stack->p = &giveup; stack->s = s; stack->ls = &lsfail; stack->caplevel = 0; stack++; /* labeled failure */ | ||
| 170 | *sfail = s; /* labeled failure */ | 159 | *sfail = s; /* labeled failure */ |
| 171 | lua_pushlightuserdata(L, stackbase); | 160 | lua_pushlightuserdata(L, stackbase); |
| 172 | for (;;) { | 161 | for (;;) { |
| @@ -198,7 +187,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 198 | if (s < e) { p++; s++; } | 187 | if (s < e) { p++; s++; } |
| 199 | else { | 188 | else { |
| 200 | *labelf = LFAIL; /* labeled failure */ | 189 | *labelf = LFAIL; /* labeled failure */ |
| 201 | pk = p + 1; | ||
| 202 | updatefarthest(*sfail, s); /*labeled failure */ | 190 | updatefarthest(*sfail, s); /*labeled failure */ |
| 203 | goto fail; | 191 | goto fail; |
| 204 | } | 192 | } |
| @@ -213,7 +201,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 213 | if ((byte)*s == p->i.aux && s < e) { p++; s++; } | 201 | if ((byte)*s == p->i.aux && s < e) { p++; s++; } |
| 214 | else { | 202 | else { |
| 215 | *labelf = LFAIL; /* labeled failure */ | 203 | *labelf = LFAIL; /* labeled failure */ |
| 216 | pk = p + 1; | ||
| 217 | updatefarthest(*sfail, s); /*labeled failure */ | 204 | updatefarthest(*sfail, s); /*labeled failure */ |
| 218 | goto fail; | 205 | goto fail; |
| 219 | } | 206 | } |
| @@ -230,7 +217,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 230 | { p += CHARSETINSTSIZE; s++; } | 217 | { p += CHARSETINSTSIZE; s++; } |
| 231 | else { | 218 | else { |
| 232 | *labelf = LFAIL; /* labeled failure */ | 219 | *labelf = LFAIL; /* labeled failure */ |
| 233 | pk = p + CHARSETINSTSIZE; | ||
| 234 | updatefarthest(*sfail, s); /*labeled failure */ | 220 | updatefarthest(*sfail, s); /*labeled failure */ |
| 235 | goto fail; | 221 | goto fail; |
| 236 | } | 222 | } |
| @@ -247,7 +233,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 247 | int n = p->i.aux; | 233 | int n = p->i.aux; |
| 248 | if (n > s - o) { | 234 | if (n > s - o) { |
| 249 | *labelf = LFAIL; /* labeled failure */ | 235 | *labelf = LFAIL; /* labeled failure */ |
| 250 | pk = p + 1; | ||
| 251 | updatefarthest(*sfail, s); /*labeled failure */ | 236 | updatefarthest(*sfail, s); /*labeled failure */ |
| 252 | goto fail; | 237 | goto fail; |
| 253 | } | 238 | } |
| @@ -271,35 +256,22 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 271 | stack = doublestack(L, &stacklimit, ptop); | 256 | stack = doublestack(L, &stacklimit, ptop); |
| 272 | stack->p = p + getoffset(p); | 257 | stack->p = p + getoffset(p); |
| 273 | stack->s = s; | 258 | stack->s = s; |
| 274 | stack->ls = &lsfail; /* labeled failure */ | ||
| 275 | stack->caplevel = captop; | 259 | stack->caplevel = captop; |
| 276 | stack++; | 260 | stack++; |
| 277 | p += 2; | 261 | p += 2; |
| 278 | continue; | 262 | continue; |
| 279 | } | 263 | } |
| 280 | case ILabChoice: { /* labeled failure */ | ||
| 281 | if (stack == stacklimit) | ||
| 282 | stack = doublestack(L, &stacklimit, ptop); | ||
| 283 | stack->p = p + getoffset(p); | ||
| 284 | stack->s = s; | ||
| 285 | stack->ls = (const Labelset *) ((p + 2)->buff); | ||
| 286 | stack->caplevel = captop; | ||
| 287 | stack++; | ||
| 288 | p += (CHARSETINSTSIZE - 1) + 2; | ||
| 289 | continue; | ||
| 290 | } | ||
| 291 | case ICall: { | 264 | case ICall: { |
| 292 | if (stack == stacklimit) | 265 | if (stack == stacklimit) |
| 293 | stack = doublestack(L, &stacklimit, ptop); | 266 | stack = doublestack(L, &stacklimit, ptop); |
| 294 | stack->s = NULL; | 267 | stack->s = NULL; |
| 295 | stack->p = p + 2; /* save return address */ | 268 | stack->p = p + 2; /* save return address */ |
| 296 | stack->ls = NULL; | ||
| 297 | stack++; | 269 | stack++; |
| 298 | p += getoffset(p); | 270 | p += getoffset(p); |
| 299 | continue; | 271 | continue; |
| 300 | } | 272 | } |
| 301 | case ICommit: { | 273 | case ICommit: { |
| 302 | assert(stack > getstackbase(L, ptop) && (stack - 1)->ls != NULL); /* labeled failure */ | 274 | assert(stack > getstackbase(L, ptop)); |
| 303 | assert((stack - 1)->s != NULL); | 275 | assert((stack - 1)->s != NULL); |
| 304 | stack--; | 276 | stack--; |
| 305 | p += getoffset(p); | 277 | p += getoffset(p); |
| @@ -325,8 +297,10 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 325 | printf("IThrow there %s top = %d\n", lua_tostring(L, -1), lua_gettop(L)); | 297 | printf("IThrow there %s top = %d\n", lua_tostring(L, -1), lua_gettop(L)); |
| 326 | lua_pop(L, 1);*/ | 298 | lua_pop(L, 1);*/ |
| 327 | *labelf = (p+1)->i.key; | 299 | *labelf = (p+1)->i.key; |
| 328 | pk = p + 1; | 300 | if (*labelf == LFAIL) |
| 301 | luaL_error(L, "labelf is %d", *labelf); | ||
| 329 | *sfail = s; | 302 | *sfail = s; |
| 303 | stack = getstackbase(L, ptop); | ||
| 330 | goto fail; | 304 | goto fail; |
| 331 | } | 305 | } |
| 332 | case IThrowRec: { /* labeled failure */ | 306 | case IThrowRec: { /* labeled failure */ |
| @@ -335,12 +309,13 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 335 | printf("IThrowRec there %s top = %d\n", lua_tostring(L, -1), lua_gettop(L)); | 309 | printf("IThrowRec there %s top = %d\n", lua_tostring(L, -1), lua_gettop(L)); |
| 336 | lua_pop(L, 1);*/ | 310 | lua_pop(L, 1);*/ |
| 337 | *labelf = (p+2)->i.key; | 311 | *labelf = (p+2)->i.key; |
| 312 | if (*labelf == LFAIL) | ||
| 313 | luaL_error(L, "labelf is %d", *labelf); | ||
| 338 | *sfail = s; | 314 | *sfail = s; |
| 339 | if (stack == stacklimit) | 315 | if (stack == stacklimit) |
| 340 | stack = doublestack(L, &stacklimit, ptop); | 316 | stack = doublestack(L, &stacklimit, ptop); |
| 341 | stack->s = NULL; | 317 | stack->s = NULL; |
| 342 | stack->p = p + 3; | 318 | stack->p = p + 3; |
| 343 | stack->ls = NULL; | ||
| 344 | stack->caplevel = captop; | 319 | stack->caplevel = captop; |
| 345 | stack++; | 320 | stack++; |
| 346 | p += getoffset(p); | 321 | p += getoffset(p); |
| @@ -352,31 +327,16 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 352 | /* go through */ | 327 | /* go through */ |
| 353 | case IFail: | 328 | case IFail: |
| 354 | *labelf = LFAIL; /* labeled failure */ | 329 | *labelf = LFAIL; /* labeled failure */ |
| 355 | pk = NULL; | ||
| 356 | updatefarthest(*sfail, s); /*labeled failure */ | 330 | updatefarthest(*sfail, s); /*labeled failure */ |
| 357 | fail: { /* pattern failed: try to backtrack */ | 331 | fail: { /* pattern failed: try to backtrack */ |
| 358 | const Labelset *auxlab = NULL; | ||
| 359 | Stack *pstack = stack; | ||
| 360 | do { /* remove pending calls */ | 332 | do { /* remove pending calls */ |
| 361 | assert(pstack > getstackbase(L, ptop)); | 333 | assert(stack > getstackbase(L, ptop)); |
| 362 | auxlab = (--pstack)->ls; | 334 | s = (--stack)->s; |
| 363 | } while (auxlab == NULL || !(pstack->p == &giveup || testlabel(pstack->ls->cs, *labelf))); | 335 | } while (s == NULL); |
| 364 | if (pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */ | 336 | if (ndyncap > 0) /* is there matchtime captures? */ |
| 365 | stack = pstack; | 337 | ndyncap -= removedyncap(L, capture, stack->caplevel, captop); |
| 366 | s = stack->s; | 338 | captop = stack->caplevel; |
| 367 | if (ndyncap > 0) /* is there matchtime captures? */ | 339 | p = stack->p; |
| 368 | ndyncap -= removedyncap(L, capture, stack->caplevel, captop); | ||
| 369 | captop = stack->caplevel; | ||
| 370 | } else { /* labeled failure: recovery frame */ | ||
| 371 | if (stack == stacklimit) | ||
| 372 | stack = doublestack(L, &stacklimit, ptop); | ||
| 373 | stack->s = NULL; | ||
| 374 | stack->p = pk; /* save return address */ | ||
| 375 | stack->ls = NULL; | ||
| 376 | stack->caplevel = captop; /* TODO: really necessary?? */ | ||
| 377 | stack++; | ||
| 378 | } | ||
| 379 | p = pstack->p; | ||
| 380 | #if defined(DEBUG) | 340 | #if defined(DEBUG) |
| 381 | printf("**FAIL**\n"); | 341 | printf("**FAIL**\n"); |
| 382 | #endif | 342 | #endif |
| @@ -394,7 +354,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
| 394 | res = resdyncaptures(L, fr, s - o, e - o); /* get result */ | 354 | res = resdyncaptures(L, fr, s - o, e - o); /* get result */ |
| 395 | if (res == -1) { /* fail? */ | 355 | if (res == -1) { /* fail? */ |
| 396 | *labelf = LFAIL; /* labeled failure */ | 356 | *labelf = LFAIL; /* labeled failure */ |
| 397 | pk = NULL; | ||
| 398 | updatefarthest(*sfail, s); /*labeled failure */ | 357 | updatefarthest(*sfail, s); /*labeled failure */ |
| 399 | goto fail; | 358 | goto fail; |
| 400 | } | 359 | } |
| @@ -36,7 +36,6 @@ typedef enum Opcode { | |||
| 36 | ICloseRunTime, | 36 | ICloseRunTime, |
| 37 | IThrow, /* fails with a given label */ | 37 | IThrow, /* fails with a given label */ |
| 38 | IThrowRec, /* fails with a given label and call rule at 'offset' */ | 38 | IThrowRec, /* fails with a given label and call rule at 'offset' */ |
| 39 | ILabChoice /* stack a choice; next fail with label 'f' will jump to 'offset' */ | ||
| 40 | } Opcode; | 39 | } Opcode; |
| 41 | 40 | ||
| 42 | 41 | ||
diff --git a/testlabel.lua b/testlabel.lua index 90a8f9e..9e2a586 100644 --- a/testlabel.lua +++ b/testlabel.lua | |||
| @@ -202,78 +202,77 @@ assert(r == nil and l == '1' and poserr == 1) | |||
| 202 | 202 | ||
| 203 | p = ##m.T(1) + m.P"a" | 203 | p = ##m.T(1) + m.P"a" |
| 204 | r, l, poserr = p:match("abc") | 204 | r, l, poserr = p:match("abc") |
| 205 | assert(r == nil and l == 1 and poserr == 1) | 205 | assert(r == nil and l == '1' and poserr == 1) |
| 206 | 206 | ||
| 207 | p = -m.T(1) * m.P"a" | 207 | p = -m.T(1) * m.P"a" |
| 208 | r, l, poserr = p:match("abc") | 208 | r, l, poserr = p:match("abc") |
| 209 | assert(r == nil and l == 1 and poserr == 1) | 209 | assert(r == nil and l == '1' and poserr == 1) |
| 210 | 210 | ||
| 211 | p = -m.T(1) * m.P"a" | 211 | p = -m.T(1) * m.P"a" |
| 212 | r, l, poserr = p:match("bbc") | 212 | r, l, poserr = p:match("bbc") |
| 213 | assert(r == nil and l == 1 and poserr == 1) | 213 | assert(r == nil and l == '1' and poserr == 1) |
| 214 | 214 | ||
| 215 | p = -(-m.T(1)) * m.P"a" | 215 | p = -(-m.T(1)) * m.P"a" |
| 216 | r, l, poserr = p:match("abc") | 216 | r, l, poserr = p:match("abc") |
| 217 | assert(r == nil and l == 1 and poserr == 1) | 217 | assert(r == nil and l == '1' and poserr == 1) |
| 218 | 218 | ||
| 219 | p = m.Rec(-m.T(22), m.P"a", 22) | 219 | p = m.P{ |
| 220 | "S", | ||
| 221 | S = -m.T(22), | ||
| 222 | ["22"] = m.P"a" | ||
| 223 | } | ||
| 220 | r, l, poserr = p:match("abc") | 224 | r, l, poserr = p:match("abc") |
| 221 | assert(r == nil and l == 0 and poserr == 2) | 225 | assert(r == nil and l == 'fail' and poserr == 2) |
| 222 | 226 | ||
| 223 | assert(p:match("bbc") == 1) | 227 | assert(p:match("bbc") == 1) |
| 224 | 228 | ||
| 225 | p = m.Rec(#m.T(22), m.P"a", 22) | 229 | p = m.P{ |
| 226 | assert(p:match("abc") == 1) | 230 | "S", |
| 227 | 231 | S = #m.T(22), | |
| 228 | p = #m.Rec(m.T(22), m.P"a", 22) | 232 | ["22"] = m.P"a" |
| 229 | assert(p:match("abc") == 1) | 233 | } |
| 230 | |||
| 231 | p = m.Rec(m.T(22), #m.P"a", 22) | ||
| 232 | assert(p:match("abc") == 1) | ||
| 233 | |||
| 234 | p = m.Rec(#m.T(22), m.P"a", 22) | ||
| 235 | r, l, poserr = p:match("bbc") | ||
| 236 | assert(r == nil and l == 0 and poserr == 1) | ||
| 237 | |||
| 238 | p = m.Rec(#m.P("a") * m.T(22), m.T(15), 22) | ||
| 239 | r, l, poserr = p:match("abc") | ||
| 240 | assert(r == nil and l == 15 and poserr == 1) | ||
| 241 | |||
| 242 | p = m.Rec(#(m.P("a") * m.T(22)), m.T(15), 22) | ||
| 243 | r, l, poserr = p:match("abc") | ||
| 244 | assert(r == nil and l == 15 and poserr == 2) | ||
| 245 | |||
| 246 | p = m.Lc(#m.T(22), m.P"a", 22) | ||
| 247 | assert(p:match("abc") == 2) | ||
| 248 | |||
| 249 | p = #m.Lc(m.T(22), m.P"a", 22) | ||
| 250 | assert(p:match("abc") == 1) | 234 | assert(p:match("abc") == 1) |
| 251 | 235 | ||
| 252 | p = m.Lc(m.T(22), #m.P"a", 22) | 236 | p = m.P{ |
| 237 | "S", | ||
| 238 | S = m.T(22), | ||
| 239 | ["22"] = #m.P"a" | ||
| 240 | } | ||
| 253 | assert(p:match("abc") == 1) | 241 | assert(p:match("abc") == 1) |
| 254 | 242 | ||
| 255 | p = m.Lc(#m.T(22), m.P"a", 22) | 243 | p = m.P{ |
| 244 | "S", | ||
| 245 | S = #m.T(22), | ||
| 246 | ["22"] = m.P"a" | ||
| 247 | } | ||
| 256 | r, l, poserr = p:match("bbc") | 248 | r, l, poserr = p:match("bbc") |
| 257 | assert(r == nil and l == 0 and poserr == 1) | 249 | assert(r == nil and l == 'fail' and poserr == 1) |
| 258 | 250 | ||
| 259 | p = m.Lc(#m.P("a") * m.T(22), m.T(15), 22) | 251 | p = m.P{ |
| 252 | "S", | ||
| 253 | S = #m.P"a" * m.T(22), | ||
| 254 | ["22"] = m.T(15) | ||
| 255 | } | ||
| 260 | r, l, poserr = p:match("abc") | 256 | r, l, poserr = p:match("abc") |
| 261 | assert(r == nil and l == 15 and poserr == 1) | 257 | assert(r == nil and l == '15' and poserr == 1) |
| 262 | 258 | ||
| 263 | p = m.Lc(#(m.P("a") * m.T(22)), m.T(15), 22) | 259 | p = m.P{ |
| 260 | "S", | ||
| 261 | S = #(m.P"a" * m.T(22)), | ||
| 262 | ["22"] = m.T(15) | ||
| 263 | } | ||
| 264 | r, l, poserr = p:match("abc") | 264 | r, l, poserr = p:match("abc") |
| 265 | assert(r == nil and l == 15 and poserr == 1) | 265 | assert(r == nil and l == '15' and poserr == 2) |
| 266 | |||
| 267 | 266 | ||
| 268 | 267 | ||
| 269 | -- tests related to repetition | 268 | -- tests related to repetition |
| 270 | p = m.T(1)^0 | 269 | p = m.T(1)^0 |
| 271 | r, l, poserr = p:match("ab") | 270 | r, l, poserr = p:match("ab") |
| 272 | assert(r == nil and l == 1 and poserr == 1) | 271 | assert(r == nil and l == '1' and poserr == 1) |
| 273 | 272 | ||
| 274 | p = (m.P"a" + m.T(1))^0 | 273 | p = (m.P"a" + m.T(1))^0 |
| 275 | r, l, poserr = p:match("aa") | 274 | r, l, poserr = p:match("aa") |
| 276 | assert(r == nil and l == 1 and poserr == 3) | 275 | assert(r == nil and l == '1' and poserr == 3) |
| 277 | 276 | ||
| 278 | 277 | ||
| 279 | -- Bug reported by Matthew Allen | 278 | -- Bug reported by Matthew Allen |
| @@ -281,18 +280,19 @@ assert(r == nil and l == 1 and poserr == 3) | |||
| 281 | -- applied in case of labels | 280 | -- applied in case of labels |
| 282 | 281 | ||
| 283 | -- recovery operator | 282 | -- recovery operator |
| 284 | p = m.Rec(m.P"A", m.P(true), 1) + m.P("B") | 283 | p = m.P{ |
| 285 | assert(p:match("B") == 2) | 284 | "S", |
| 286 | 285 | S = m.P"A" + m.P"B", | |
| 287 | p = m.Rec(m.P"A", m.P(false), 1) + m.P("B") | 286 | ["2"] = m.P(true) |
| 288 | assert(p:match("B") == 2) | 287 | } |
| 289 | |||
| 290 | 288 | ||
| 291 | -- labeled choices | ||
| 292 | p = m.Lc(m.P"A", m.P(true), 1) + m.P("B") | ||
| 293 | assert(p:match("B") == 2) | 289 | assert(p:match("B") == 2) |
| 294 | 290 | ||
| 295 | p = m.Lc(m.P"A", m.P(false), 1) + m.P("B") | 291 | p = m.P{ |
| 292 | "S", | ||
| 293 | S = m.P"A" + m.P"B", | ||
| 294 | ["2"] = m.P(false) | ||
| 295 | } | ||
| 296 | assert(p:match("B") == 2) | 296 | assert(p:match("B") == 2) |
| 297 | 297 | ||
| 298 | 298 | ||
| @@ -303,152 +303,73 @@ B -> %1 | |||
| 303 | ]] | 303 | ]] |
| 304 | g = m.P{ | 304 | g = m.P{ |
| 305 | "S", | 305 | "S", |
| 306 | S = m.Rec(m.V"A", m.P"a", 1), | 306 | S = m.V"A", |
| 307 | A = m.V"B", | 307 | A = m.V"B", |
| 308 | B = m.T(1), | 308 | B = m.T(2), |
| 309 | ["2"] = m.P'a' | ||
| 309 | } | 310 | } |
| 310 | assert(g:match("ab") == 2) | 311 | assert(g:match("ab") == 2) |
| 311 | r, l, poserr = g:match("bc") | 312 | r, l, poserr = g:match("bc") |
| 312 | assert(r == nil and l == 0 and poserr == 1) | 313 | assert(r == nil and l == 'fail' and poserr == 1) |
| 313 | 314 | ||
| 314 | 315 | ||
| 315 | --[[ | 316 | --[[ |
| 316 | S -> A | 317 | S -> A |
| 317 | A -> (B (';' / %{1}))* | 318 | A -> (B (';' / %{2}))* |
| 318 | B -> 'a' | 319 | B -> 'a' |
| 319 | ]] | 320 | ]] |
| 320 | g = m.P{ | 321 | g = m.P{ |
| 321 | "S", | 322 | "S", |
| 322 | S = m.V"A", | 323 | S = m.V"A", |
| 323 | A = m.P(m.V"B" * (";" + m.T(1)))^0, | 324 | A = m.P(m.V"B" * (";" + m.T(2)))^0, |
| 324 | B = m.P'a', | 325 | B = m.P'a', |
| 325 | } | 326 | } |
| 326 | assert(g:match("a;a;") == 5) | 327 | assert(g:match("a;a;") == 5) |
| 327 | 328 | ||
| 328 | r, l, poserr = g:match("a;a") | 329 | r, l, poserr = g:match("a;a") |
| 329 | assert(r == nil and l == 1 and poserr == 4) | 330 | assert(r == nil and l == '2' and poserr == 4) |
| 330 | |||
| 331 | 331 | ||
| 332 | -- %1 //{1,3} %2 //{2} 'a' | ||
| 333 | p = m.Rec(m.Rec(m.T(1), m.T(2), 1, 3), m.P"a", 2) | ||
| 334 | assert(p:match("abc") == 2) | ||
| 335 | |||
| 336 | r, l, poserr = p:match("") | ||
| 337 | assert(r == nil and l == 0 and poserr == 1) | ||
| 338 | 332 | ||
| 339 | p = m.Rec(m.T(1), m.Rec(m.T(2), m.P"a", 2), 1, 3) | 333 | p = m.P{ |
| 340 | assert(p:match("abc") == 2) | 334 | "S", |
| 341 | 335 | S = m.T'A', | |
| 342 | r, l, poserr = p:match("") | 336 | A = m.T'B', |
| 343 | assert(r == nil and l == 0 and poserr == 1) | 337 | B = m.P'a' |
| 344 | |||
| 345 | -- labeled choice | ||
| 346 | --[[ | ||
| 347 | S -> A /{1} 'a' | ||
| 348 | A -> B | ||
| 349 | B -> %1 | ||
| 350 | ]] | ||
| 351 | g = m.P{ | ||
| 352 | "S", | ||
| 353 | S = m.Lc(m.V"A", m.P"a", 1), | ||
| 354 | A = m.V"B", | ||
| 355 | B = m.T(1), | ||
| 356 | } | ||
| 357 | assert(g:match("ab") == 2) | ||
| 358 | r, l, poserr = g:match("bc") | ||
| 359 | assert(r == nil and l == 0 and poserr == 1) | ||
| 360 | |||
| 361 | |||
| 362 | --[[ | ||
| 363 | S -> A | ||
| 364 | A -> (B (';' / %{1}))* | ||
| 365 | B -> 'a' | ||
| 366 | ]] | ||
| 367 | g = m.P{ | ||
| 368 | "S", | ||
| 369 | S = m.V"A", | ||
| 370 | A = m.P(m.V"B" * (";" + m.T(1)))^0, | ||
| 371 | B = m.P'a', | ||
| 372 | } | 338 | } |
| 373 | assert(g:match("a;a;") == 5) | ||
| 374 | |||
| 375 | r, l, poserr = g:match("a;a") | ||
| 376 | assert(r == nil and l == 1 and poserr == 4) | ||
| 377 | |||
| 378 | |||
| 379 | -- %1 /{1,3} %2 /{2} 'a' | ||
| 380 | p = m.Lc(m.Lc(m.T(1), m.T(2), 1, 3), m.P"a", 2) | ||
| 381 | assert(p:match("abc") == 2) | ||
| 382 | |||
| 383 | r, l, poserr = p:match("") | ||
| 384 | assert(r == nil and l == 0 and poserr == 1) | ||
| 385 | |||
| 386 | p = m.Lc(m.T(1), m.Lc(m.T(2), m.P"a", 2), 1, 3) | ||
| 387 | assert(p:match("abc") == 2) | 339 | assert(p:match("abc") == 2) |
| 388 | 340 | ||
| 389 | r, l, poserr = p:match("") | 341 | r, l, poserr = p:match("") |
| 390 | assert(r == nil and l == 0 and poserr == 1) | 342 | assert(r == nil and l == 'fail' and poserr == 1) |
| 391 | 343 | ||
| 392 | 344 | ||
| 393 | -- Infinte Loop TODO: check the semantics | 345 | -- Infinte Loop TODO: check the semantics |
| 394 | -- %1 //{1} %1 | 346 | p = m.P{ |
| 395 | p = m.Rec(m.T(1), m.T(1), 1) | 347 | "S", |
| 348 | S = m.T"A", | ||
| 349 | A = m.T"S", | ||
| 350 | } | ||
| 396 | --r, l, poserr = p:match("ab") | 351 | --r, l, poserr = p:match("ab") |
| 397 | --assert(r == nil and l == 1 and poserr == "ab") | 352 | --assert(r == nil and l == 1 and poserr == "ab") |
| 398 | 353 | ||
| 399 | -- %1 //{1} 'a' (!. / %1) | 354 | p = m.P{ |
| 400 | p = m.Rec(m.T(1), m.P"a" * (-m.P(1) + m.T(1)), 1) | 355 | "S", |
| 356 | S = m.T"A", | ||
| 357 | A = m.P'a' * (-m.P(1) + m.T"A"), | ||
| 358 | } | ||
| 401 | r, l, poserr = p:match("ab") | 359 | r, l, poserr = p:match("ab") |
| 402 | assert(r == nil and l == 0 and poserr == 2) | 360 | assert(r == nil and l == 'fail' and poserr == 2) |
| 403 | 361 | ||
| 404 | r, l, poserr = p:match("cd") | 362 | r, l, poserr = p:match("cd") |
| 405 | assert(r == nil and l == 0 and poserr == 1) | 363 | assert(r == nil and l == 'fail' and poserr == 1) |
| 406 | 364 | ||
| 407 | -- %1 //{1} . (!. / %1) | 365 | p = m.P{ |
| 408 | p = m.Rec(m.T(1), m.P(1) * (-m.P(1) + m.T(1)), 1) | 366 | "S", |
| 367 | S = m.T"A", | ||
| 368 | A = m.P(1) * (-m.P(1) + m.T"A"), | ||
| 369 | } | ||
| 409 | assert(p:match("abc") == 4) | 370 | assert(p:match("abc") == 4) |
| 410 | 371 | ||
| 411 | 372 | ||
| 412 | -- testing the limit of labels | ||
| 413 | -- can only throw labels between 1 and 255 | ||
| 414 | local r = pcall(m.Rec, m.P"b", m.P"a", 0) | ||
| 415 | assert(r == false) | ||
| 416 | |||
| 417 | local r = pcall(m.Rec, m.P"b", m.P"a", 256) | ||
| 418 | assert(r == false) | ||
| 419 | |||
| 420 | local r = pcall(m.Rec, m.P"b", m.P"a", -1) | ||
| 421 | assert(r == false) | ||
| 422 | |||
| 423 | local r = pcall(m.Lc, m.P"b", m.P"a", 0) | ||
| 424 | assert(r == false) | ||
| 425 | |||
| 426 | local r = pcall(m.Lc, m.P"b", m.P"a", 256) | ||
| 427 | assert(r == false) | ||
| 428 | |||
| 429 | local r = pcall(m.Lc, m.P"b", m.P"a", -1) | ||
| 430 | assert(r == false) | ||
| 431 | |||
| 432 | local r = pcall(m.T, 0) | ||
| 433 | assert(r == false) | ||
| 434 | |||
| 435 | local r = pcall(m.T, 256) | ||
| 436 | assert(r == false) | ||
| 437 | |||
| 438 | local r = pcall(m.T, -1) | ||
| 439 | assert(r == false) | ||
| 440 | |||
| 441 | |||
| 442 | local r = m.Rec(m.P"b", m.P"a", 255) | ||
| 443 | assert(p:match("a") == 2) | ||
| 444 | |||
| 445 | p = m.T(255) | ||
| 446 | s = "abc" | ||
| 447 | r, l, poserr = p:match(s) | ||
| 448 | assert(r == nil and l == 255 and poserr == 1) | ||
| 449 | |||
| 450 | |||
| 451 | |||
| 452 | print("+") | 373 | print("+") |
| 453 | 374 | ||
| 454 | --[[ grammar based on Figure 8 of paper submitted to SCP | 375 | --[[ grammar based on Figure 8 of paper submitted to SCP |
