From abb26cdfd3d44e7b10ec001f2afb980616c71573 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 Jul 2016 15:13:58 -0300 Subject: Changing the semantics of labels to recovery/resume (ILabChoice does not work) --- lpcode.c | 7 ++++--- lptypes.h | 2 ++ lpvm.c | 56 +++++++++++++++++++++++++++++++++++++++++++------------- testlabel.lua | 54 +++++++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 92 insertions(+), 27 deletions(-) diff --git a/lpcode.c b/lpcode.c index 2987cfc..f7246da 100644 --- a/lpcode.c +++ b/lpcode.c @@ -725,16 +725,17 @@ static void codelabchoice (CompileState *compst, TTree *p1, TTree *p2, int opt, static void coderecovery (CompileState *compst, TTree *p1, TTree *p2, int opt, const Charset *fl, const byte *cs) { int emptyp2 = (p2->tag == TTrue); - int pcommit; + int pjmp; int test = NOINST; int precovery = addoffsetinst(compst, IRecov); addcharset(compst, cs); codegen(compst, p1, emptyp2, test, fullset); - pcommit = addoffsetinst(compst, ICommit); + pjmp = addoffsetinst(compst, IJmp); jumptohere(compst, precovery); jumptohere(compst, test); codegen(compst, p2, opt, NOINST, fl); - jumptohere(compst, pcommit); + addinstruction(compst, IRet, 0); + jumptohere(compst, pjmp); } /* labeled failure end */ diff --git a/lptypes.h b/lptypes.h index dc4ada6..85339a0 100644 --- a/lptypes.h +++ b/lptypes.h @@ -161,6 +161,8 @@ typedef Charset Labelset; #define IDXLFAIL 0 #define LFAIL 0 + +#define MAXRECOVERY 200 /* labeled failure end */ #endif diff --git a/lpvm.c b/lpvm.c index 277acf7..db996e5 100644 --- a/lpvm.c +++ b/lpvm.c @@ -48,6 +48,12 @@ typedef struct Stack { } Stack; +typedef struct RecStack { + const Labelset *ls; /* labeled failure */ + const Instruction *prec; /* recovery instruction */ +} RecStack; + + #define getstackbase(L, ptop) ((Stack *)lua_touserdata(L, stackidx(ptop))) @@ -156,15 +162,20 @@ static int removedyncap (lua_State *L, Capture *capture, const char *match (lua_State *L, const char *o, const char *s, const char *e, Instruction *op, Capture *capture, int ptop, byte *labelf, const char **sfail) { /* labeled failure */ Stack stackbase[INITBACK]; + RecStack recbase[MAXRECOVERY]; Stack *stacklimit = stackbase + INITBACK; Stack *stack = stackbase; /* point to first empty slot in stack */ - int capsize = INITCAPSIZE; + RecStack *reclimit = recbase + MAXRECOVERY; + RecStack *recstack = recbase; + int capsize = INITCAPSIZE; int captop = 0; /* point to first empty slot in captures */ int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ const Instruction *p = op; /* current instruction */ + const Instruction *pk = NULL; /* resume instruction */ Labelset lsfail; setlabelfail(&lsfail); stack->p = &giveup; stack->s = s; stack->ls = &lsfail; stack->caplevel = 0; stack++; /* labeled failure */ + recstack->prec = &giveup; recstack->ls = &lsfail; recstack++; /* labeled failure */ lua_pushlightuserdata(L, stackbase); for (;;) { #if defined(DEBUG) @@ -194,6 +205,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if (s < e) { p++; s++; } else { *labelf = LFAIL; /* labeled failure */ + pk = p + 1; *sfail = s; goto fail; } @@ -208,6 +220,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if ((byte)*s == p->i.aux && s < e) { p++; s++; } else { *labelf = LFAIL; /* labeled failure */ + pk = p + 1; *sfail = s; goto fail; } @@ -224,6 +237,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, { p += CHARSETINSTSIZE; s++; } else { *labelf = LFAIL; /* labeled failure */ + pk = p + CHARSETINSTSIZE; *sfail = s; goto fail; } @@ -240,6 +254,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, int n = p->i.aux; if (n > s - o) { *labelf = LFAIL; /* labeled failure */ + pk = p + 1; *sfail = s; goto fail; } @@ -281,17 +296,14 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, continue; } case IRecov: { /* labeled failure */ - if (stack == stacklimit) - stack = doublestack(L, &stacklimit, ptop); - stack->p = p + getoffset(p); - stack->s = NULL; - stack->ls = (const Labelset *) ((p + 2)->buff); - stack->caplevel = captop; - stack++; + if (recstack == reclimit) + luaL_error(L, "recovery stack overflow (current limit is %d)", MAXRECOVERY); + recstack->prec = p + getoffset(p); + recstack->ls = (const Labelset *) ((p + 2)->buff); + recstack++; p += (CHARSETINSTSIZE - 1) + 2; continue; } - case ICall: { if (stack == stacklimit) stack = doublestack(L, &stacklimit, ptop); @@ -303,8 +315,8 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, continue; } case ICommit: { - assert(stack > getstackbase(L, ptop) && (stack - 1)->ls != NULL); /* labeled failure */ - /* assert((stack - 1)->s != NULL); labeled failure: IRecov does not push s onto the stack */ + /*assert(stack > getstackbase(L, ptop) && (stack - 1)->ls != NULL); */ /* labeled failure */ + assert((stack - 1)->s != NULL); /* labeled failure: IRecov does not push s onto the stack */ stack--; p += getoffset(p); continue; @@ -325,8 +337,24 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, } case IThrow: { /* labeled failure */ *labelf = p->i.aux; + while ((--recstack)->prec != &giveup) { + if (testlabel(recstack->ls->cs, *labelf)) { + /* Push resume/call frame */ + stack->s = NULL; + stack->p = p + 1; /* resume to instruction after IThrow */ + stack->ls = NULL; + stack++; + p = recstack->prec; + break; + } + } + if (recstack->prec != &giveup) + continue; + p = recstack->prec; /* p = IGiveup */ + stack = getstackbase(L, ptop); *sfail = s; - goto fail; + /*goto fail;*/ + continue; } case IFailTwice: assert(stack > getstackbase(L, ptop)); @@ -334,6 +362,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, /* go through */ case IFail: *labelf = LFAIL; /* labeled failure */ + pk = NULL; *sfail = s; fail: { /* pattern failed: try to backtrack */ const Labelset *auxlab = NULL; @@ -343,7 +372,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, } while (auxlab == NULL || (stack->p != &giveup && !testlabel(stack->ls->cs, *labelf))); if (stack->p == &giveup || stack->s != NULL) { /* labeled failure */ s = stack->s; - } + } if (ndyncap > 0) /* is there matchtime captures? */ ndyncap -= removedyncap(L, capture, stack->caplevel, captop); captop = stack->caplevel; @@ -362,6 +391,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if (res == -1) { /* fail? */ *labelf = LFAIL; /* labeled failure */ *sfail = (const char *) s; /* TODO: ??? */ + pk = NULL; goto fail; } s = o + res; /* else update current position */ diff --git a/testlabel.lua b/testlabel.lua index db16354..cbe623c 100644 --- a/testlabel.lua +++ b/testlabel.lua @@ -32,6 +32,7 @@ local g = m.P{ r, l, serror = g:match(s) assert(r == nil and l == 1 and serror == "abc") +--[==[ TODO: labeled choice does not work anymore -- throws a label that is not caught by labeled choice p = m.Lc(m.T(2), m.P"a", 1, 3) r, l, serror = p:match(s) @@ -188,6 +189,7 @@ assert(p:match("abc") == 2) r, l, serror = p:match("") assert(r == nil and l == 0 and serror == "") +]==] -- testing the limit of labels p = m.T(0) @@ -209,6 +211,7 @@ assert(r == false) print("+") +--[==[ TODO: labeled choice does not work anymore --[[ grammar based on Figure 8 of paper submitted to SCP S -> S0 /{1} ID /{2} ID '=' Exp /{3} 'unsigned'* 'int' ID /{4} 'unsigned'* ID ID / %error S0 -> ID S1 / 'unsigned' S2 / 'int' %3 @@ -548,12 +551,12 @@ assert(g:match(s) == terror['undefined']) print("+") - +]==] -- test recovery operator -p = m.Rec("a", "b") +p = m.Rec("a", "b") assert(p:match("a") == 2) -assert(p:match("b") == 2) +--assert(p:match("b") == 2) checkeqlab({nil, 0, "c"}, p:match("c")) p = m.Rec("a", "b", 3) @@ -583,13 +586,44 @@ g = m.P{ assert(g:match("abc") == 4) assert(g:match("aabc") == 5) -assert(g:match("aadc") == 5) -assert(g:match("bc") == 3) +--assert(g:match("aadc") == 5) --old semantics +checkeqlab({nil, 0, "dc"}, g:match("aadc")) --new semantics +assert(g:match("bc") == 3) -- new semantics checkeqlab({nil, 0, "bc"}, g:match("bbc")) -assert(g:match("xxc") == 4) -assert(g:match("c") == 2) -checkeqlab({nil, 0, ""}, g:match("fail")) -checkeqlab({nil, 0, ""}, g:match("aaxx")) +--assert(g:match("xxc") == 4) old semantics +checkeqlab({nil, 0, "xxc"}, g:match("xxc")) --new semantics +--assert(g:match("c") == 2) --old semantics +checkeqlab({nil, 0, "c"}, g:match("c")) --new semantics +--checkeqlab({nil, 0, ""}, g:match("fail")) --old semantics +checkeqlab({nil, 0, "fail"}, g:match("fail")) --new semantics +--checkeqlab({nil, 0, ""}, g:match("aaxx")) --old semantics +checkeqlab({nil, 0, "xx"}, g:match("aaxx")) --new semantics + + +--[[ +S -> (A //{0} (!c .)*) C +A -> a*b / ^{0} +C -> c+ +]] +g = m.P{ + "S", + S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0) * m.V"C", + A = m.P"a"^0 * m.P"b" + m.T(0), + C = m.P"c"^1, +} + +assert(g:match("abc") == 4) +assert(g:match("aabc") == 5) +assert(g:match("aadc") == 5) --updated +assert(g:match("bc") == 3) -- new semantics +checkeqlab({nil, 0, "bc"}, g:match("bbc")) +assert(g:match("xxc") == 4) +assert(g:match("c") == 2) --old semantics updated +checkeqlab({nil, 0, ""}, g:match("fail")) --old semantics updated +checkeqlab({nil, 0, ""}, g:match("aaxx")) --old semantics updated + + + --[[ S -> (A //{99} (!c .)*) C @@ -726,7 +760,5 @@ print(eval "(1+1-1*(2/2+)-():") --> syntax error: extra characters found after the expression (at index - - print("OK") -- cgit v1.2.3-55-g6feb From 7757aac333c5a37181f942d5f20223023cbdc207 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Fri, 22 Jul 2016 16:41:02 -0300 Subject: Removing "pk" from lpvm.c --- lpvm.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lpvm.c b/lpvm.c index db996e5..917a626 100644 --- a/lpvm.c +++ b/lpvm.c @@ -171,7 +171,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, int captop = 0; /* point to first empty slot in captures */ int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ const Instruction *p = op; /* current instruction */ - const Instruction *pk = NULL; /* resume instruction */ Labelset lsfail; setlabelfail(&lsfail); stack->p = &giveup; stack->s = s; stack->ls = &lsfail; stack->caplevel = 0; stack++; /* labeled failure */ @@ -205,7 +204,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if (s < e) { p++; s++; } else { *labelf = LFAIL; /* labeled failure */ - pk = p + 1; *sfail = s; goto fail; } @@ -220,7 +218,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if ((byte)*s == p->i.aux && s < e) { p++; s++; } else { *labelf = LFAIL; /* labeled failure */ - pk = p + 1; *sfail = s; goto fail; } @@ -237,7 +234,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, { p += CHARSETINSTSIZE; s++; } else { *labelf = LFAIL; /* labeled failure */ - pk = p + CHARSETINSTSIZE; *sfail = s; goto fail; } @@ -254,7 +250,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, int n = p->i.aux; if (n > s - o) { *labelf = LFAIL; /* labeled failure */ - pk = p + 1; *sfail = s; goto fail; } @@ -362,7 +357,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, /* go through */ case IFail: *labelf = LFAIL; /* labeled failure */ - pk = NULL; *sfail = s; fail: { /* pattern failed: try to backtrack */ const Labelset *auxlab = NULL; @@ -391,7 +385,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if (res == -1) { /* fail? */ *labelf = LFAIL; /* labeled failure */ *sfail = (const char *) s; /* TODO: ??? */ - pk = NULL; goto fail; } s = o + res; /* else update current position */ -- cgit v1.2.3-55-g6feb From a9c5a38994074d2c9c5da4cf081c4ca9fa8b3271 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Fri, 9 Sep 2016 16:26:44 -0300 Subject: Updating implementation of Recovery to follow Fabio's idea. Not compatible with labeled ordered choice --- lpcode.c | 6 ++--- lptree.c | 6 ++--- lptypes.h | 2 -- lpvm.c | 77 +++++++++++++++++++++++++++-------------------------------- makefile | 14 +++++------ test.lua | 2 +- testlabel.lua | 5 ++-- 7 files changed, 52 insertions(+), 60 deletions(-) diff --git a/lpcode.c b/lpcode.c index f7246da..d5f8d68 100644 --- a/lpcode.c +++ b/lpcode.c @@ -725,17 +725,17 @@ static void codelabchoice (CompileState *compst, TTree *p1, TTree *p2, int opt, static void coderecovery (CompileState *compst, TTree *p1, TTree *p2, int opt, const Charset *fl, const byte *cs) { int emptyp2 = (p2->tag == TTrue); - int pjmp; + int pcommit; int test = NOINST; int precovery = addoffsetinst(compst, IRecov); addcharset(compst, cs); codegen(compst, p1, emptyp2, test, fullset); - pjmp = addoffsetinst(compst, IJmp); + pcommit = addoffsetinst(compst, ICommit); jumptohere(compst, precovery); jumptohere(compst, test); codegen(compst, p2, opt, NOINST, fl); addinstruction(compst, IRet, 0); - jumptohere(compst, pjmp); + jumptohere(compst, pcommit); } /* labeled failure end */ diff --git a/lptree.c b/lptree.c index 2044a5c..700398b 100644 --- a/lptree.c +++ b/lptree.c @@ -748,7 +748,7 @@ static int lp_recovery (lua_State *L) { int n = lua_gettop(L); TTree *tree = newrootlab2sib(L, TRecov); if (n == 2) { /* catches fail as default */ - setlabel(treelabelset(tree), LFAIL); + /*setlabel(treelabelset(tree), LFAIL); recovery does not catch regular fail */ } else { int i; for (i = 3; i <= n; i++) { @@ -1361,8 +1361,8 @@ static struct luaL_Reg metareg[] = { }; -int luaopen_lpeglabel (lua_State *L); /* labeled failure */ -int luaopen_lpeglabel (lua_State *L) { /* labeled failure */ +int luaopen_lpeglabelrec (lua_State *L); /* labeled failure */ +int luaopen_lpeglabelrec (lua_State *L) { /* labeled failure */ luaL_newmetatable(L, PATTERN_T); lua_pushnumber(L, MAXBACK); /* initialize maximum backtracking */ lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); diff --git a/lptypes.h b/lptypes.h index 85339a0..dc4ada6 100644 --- a/lptypes.h +++ b/lptypes.h @@ -161,8 +161,6 @@ typedef Charset Labelset; #define IDXLFAIL 0 #define LFAIL 0 - -#define MAXRECOVERY 200 /* labeled failure end */ #endif diff --git a/lpvm.c b/lpvm.c index 917a626..44975a1 100644 --- a/lpvm.c +++ b/lpvm.c @@ -48,12 +48,6 @@ typedef struct Stack { } Stack; -typedef struct RecStack { - const Labelset *ls; /* labeled failure */ - const Instruction *prec; /* recovery instruction */ -} RecStack; - - #define getstackbase(L, ptop) ((Stack *)lua_touserdata(L, stackidx(ptop))) @@ -162,25 +156,22 @@ static int removedyncap (lua_State *L, Capture *capture, const char *match (lua_State *L, const char *o, const char *s, const char *e, Instruction *op, Capture *capture, int ptop, byte *labelf, const char **sfail) { /* labeled failure */ Stack stackbase[INITBACK]; - RecStack recbase[MAXRECOVERY]; Stack *stacklimit = stackbase + INITBACK; Stack *stack = stackbase; /* point to first empty slot in stack */ - RecStack *reclimit = recbase + MAXRECOVERY; - RecStack *recstack = recbase; - int capsize = INITCAPSIZE; + int capsize = INITCAPSIZE; int captop = 0; /* point to first empty slot in captures */ int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ const Instruction *p = op; /* current instruction */ + const Instruction *pk = NULL; /* resume instruction */ Labelset lsfail; setlabelfail(&lsfail); stack->p = &giveup; stack->s = s; stack->ls = &lsfail; stack->caplevel = 0; stack++; /* labeled failure */ - recstack->prec = &giveup; recstack->ls = &lsfail; recstack++; /* labeled failure */ lua_pushlightuserdata(L, stackbase); for (;;) { #if defined(DEBUG) + printinst(op, p); printf("s: |%s| stck:%d, dyncaps:%d, caps:%d ", s, stack - getstackbase(L, ptop), ndyncap, captop); - printinst(op, p); printcaplist(capture, capture + captop); #endif assert(stackidx(ptop) + ndyncap == lua_gettop(L) && ndyncap <= captop); @@ -204,6 +195,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if (s < e) { p++; s++; } else { *labelf = LFAIL; /* labeled failure */ + pk = p + 1; *sfail = s; goto fail; } @@ -218,6 +210,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if ((byte)*s == p->i.aux && s < e) { p++; s++; } else { *labelf = LFAIL; /* labeled failure */ + pk = p + 1; *sfail = s; goto fail; } @@ -234,6 +227,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, { p += CHARSETINSTSIZE; s++; } else { *labelf = LFAIL; /* labeled failure */ + pk = p + CHARSETINSTSIZE; *sfail = s; goto fail; } @@ -250,6 +244,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, int n = p->i.aux; if (n > s - o) { *labelf = LFAIL; /* labeled failure */ + pk = p + 1; *sfail = s; goto fail; } @@ -291,11 +286,13 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, continue; } case IRecov: { /* labeled failure */ - if (recstack == reclimit) - luaL_error(L, "recovery stack overflow (current limit is %d)", MAXRECOVERY); - recstack->prec = p + getoffset(p); - recstack->ls = (const Labelset *) ((p + 2)->buff); - recstack++; + if (stack == stacklimit) + stack = doublestack(L, &stacklimit, ptop); + stack->p = p + getoffset(p); + stack->s = NULL; + stack->ls = (const Labelset *) ((p + 2)->buff); + stack->caplevel = captop; + stack++; p += (CHARSETINSTSIZE - 1) + 2; continue; } @@ -310,8 +307,8 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, continue; } case ICommit: { - /*assert(stack > getstackbase(L, ptop) && (stack - 1)->ls != NULL); */ /* labeled failure */ - assert((stack - 1)->s != NULL); /* labeled failure: IRecov does not push s onto the stack */ + assert(stack > getstackbase(L, ptop) && (stack - 1)->ls != NULL); /* labeled failure */ + /*assert((stack - 1)->s != NULL); labeled failure: IRecov does not push s onto the stack */ stack--; p += getoffset(p); continue; @@ -332,24 +329,9 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, } case IThrow: { /* labeled failure */ *labelf = p->i.aux; - while ((--recstack)->prec != &giveup) { - if (testlabel(recstack->ls->cs, *labelf)) { - /* Push resume/call frame */ - stack->s = NULL; - stack->p = p + 1; /* resume to instruction after IThrow */ - stack->ls = NULL; - stack++; - p = recstack->prec; - break; - } - } - if (recstack->prec != &giveup) - continue; - p = recstack->prec; /* p = IGiveup */ - stack = getstackbase(L, ptop); + pk = p + 1; *sfail = s; - /*goto fail;*/ - continue; + goto fail; } case IFailTwice: assert(stack > getstackbase(L, ptop)); @@ -357,20 +339,30 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, /* go through */ case IFail: *labelf = LFAIL; /* labeled failure */ + pk = NULL; *sfail = s; fail: { /* pattern failed: try to backtrack */ const Labelset *auxlab = NULL; + Stack *pstack = stack; do { /* remove pending calls */ - assert(stack > getstackbase(L, ptop)); - auxlab = (--stack)->ls; - } while (auxlab == NULL || (stack->p != &giveup && !testlabel(stack->ls->cs, *labelf))); - if (stack->p == &giveup || stack->s != NULL) { /* labeled failure */ + assert(pstack > getstackbase(L, ptop)); + auxlab = (--pstack)->ls; + } while (auxlab == NULL || (pstack->p != &giveup && labelf != LFAIL && !testlabel(pstack->ls->cs, *labelf))); + if (pstack->p == &giveup || pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */ + stack = pstack; s = stack->s; - } + } else { /* labeled failure: recovery frame */ + if (stack == stacklimit) + stack = doublestack(L, &stacklimit, ptop); + stack->s = NULL; + stack->p = pk; /* save return address */ + stack->ls = NULL; + stack++; + } if (ndyncap > 0) /* is there matchtime captures? */ ndyncap -= removedyncap(L, capture, stack->caplevel, captop); captop = stack->caplevel; - p = stack->p; + p = pstack->p; continue; } case ICloseRunTime: { @@ -385,6 +377,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if (res == -1) { /* fail? */ *labelf = LFAIL; /* labeled failure */ *sfail = (const char *) s; /* TODO: ??? */ + pk = NULL; goto fail; } s = o + res; /* else update current position */ diff --git a/makefile b/makefile index 5728b38..414651e 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,4 @@ -LIBNAME = lpeglabel +LIBNAME = lpeglabelrec LUADIR = ../lua/ COPT = -O2 @@ -29,24 +29,24 @@ FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o # For Linux linux: - make lpeglabel.so "DLLFLAGS = -shared -fPIC" + make lpeglabelrec.so "DLLFLAGS = -shared -fPIC" # For Mac OS macosx: - make lpeglabel.so "DLLFLAGS = -bundle -undefined dynamic_lookup" + make lpeglabelrec.so "DLLFLAGS = -bundle -undefined dynamic_lookup" -lpeglabel.so: $(FILES) - env $(CC) $(DLLFLAGS) $(FILES) -o lpeglabel.so +lpeglabelrec.so: $(FILES) + env $(CC) $(DLLFLAGS) $(FILES) -o lpeglabelrec.so $(FILES): makefile -test: test.lua testlabel.lua testerrors.lua relabel.lua lpeglabel.so +test: test.lua testlabel.lua testerrors.lua relabel.lua lpeglabelrec.so lua test.lua lua testlabel.lua lua testerrors.lua clean: - rm -f $(FILES) lpeglabel.so + rm -f $(FILES) lpeglabelrec.so lpcap.o: lpcap.c lpcap.h lptypes.h diff --git a/test.lua b/test.lua index d5922ac..fc2b607 100755 --- a/test.lua +++ b/test.lua @@ -4,7 +4,7 @@ -- require"strict" -- just to be pedantic -local m = require"lpeglabel" +local m = require"lpeglabelrec" -- for general use diff --git a/testlabel.lua b/testlabel.lua index cbe623c..8cfb671 100644 --- a/testlabel.lua +++ b/testlabel.lua @@ -1,4 +1,4 @@ -local m = require 'lpeglabel' +local m = require 'lpeglabelrec' local p, r, l, s, serror @@ -557,6 +557,7 @@ print("+") p = m.Rec("a", "b") assert(p:match("a") == 2) --assert(p:match("b") == 2) +checkeqlab({nil, 0, "b"}, p:match("b")) checkeqlab({nil, 0, "c"}, p:match("c")) p = m.Rec("a", "b", 3) @@ -607,7 +608,7 @@ C -> c+ ]] g = m.P{ "S", - S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0) * m.V"C", + S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0, 0) * m.V"C", --explicitly put 0 in Rec A = m.P"a"^0 * m.P"b" + m.T(0), C = m.P"c"^1, } -- cgit v1.2.3-55-g6feb From beb03a70df23225b5db7d86f2ea799e33d7dd252 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Wed, 21 Sep 2016 10:07:34 -0300 Subject: Fixing a bug in lpvm.c (now the tests passed) --- lpvm.c | 4 ++-- relabel.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lpvm.c b/lpvm.c index 44975a1..e8ff287 100644 --- a/lpvm.c +++ b/lpvm.c @@ -360,8 +360,8 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, stack++; } if (ndyncap > 0) /* is there matchtime captures? */ - ndyncap -= removedyncap(L, capture, stack->caplevel, captop); - captop = stack->caplevel; + ndyncap -= removedyncap(L, capture, pstack->caplevel, captop); + captop = pstack->caplevel; p = pstack->p; continue; } diff --git a/relabel.lua b/relabel.lua index ff2e486..0e7195e 100644 --- a/relabel.lua +++ b/relabel.lua @@ -6,7 +6,7 @@ local pcall = pcall local setmetatable = setmetatable local unpack, tinsert, concat = table.unpack or unpack, table.insert, table.concat local rep = string.rep -local m = require"lpeglabel" +local m = require"lpeglabelrec" -- 'm' will be used to parse expressions, and 'mm' will be used to -- create expressions; that is, 're' runs on 'm', creating patterns -- cgit v1.2.3-55-g6feb From 5eb916ec7b845094ef121be13027690418395814 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Wed, 21 Sep 2016 16:33:14 -0300 Subject: Now captures seems to work as expected in case of a recovery --- lpvm.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lpvm.c b/lpvm.c index e8ff287..b200dce 100644 --- a/lpvm.c +++ b/lpvm.c @@ -351,17 +351,18 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, if (pstack->p == &giveup || pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */ stack = pstack; s = stack->s; + if (ndyncap > 0) /* is there matchtime captures? */ + ndyncap -= removedyncap(L, capture, stack->caplevel, captop); + captop = stack->caplevel; } else { /* labeled failure: recovery frame */ if (stack == stacklimit) stack = doublestack(L, &stacklimit, ptop); stack->s = NULL; stack->p = pk; /* save return address */ stack->ls = NULL; + stack->caplevel = captop; /* TODO: necessary?? */ stack++; } - if (ndyncap > 0) /* is there matchtime captures? */ - ndyncap -= removedyncap(L, capture, pstack->caplevel, captop); - captop = pstack->caplevel; p = pstack->p; continue; } -- cgit v1.2.3-55-g6feb From d84dd6b3659f94b09e67eb90a10e71eb05c5630e Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Tue, 11 Oct 2016 18:18:29 -0300 Subject: Adding new examples to testlabel.lua --- testlabel.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/testlabel.lua b/testlabel.lua index 8cfb671..25036b2 100644 --- a/testlabel.lua +++ b/testlabel.lua @@ -761,5 +761,55 @@ print(eval "(1+1-1*(2/2+)-():") --> syntax error: extra characters found after the expression (at index +print("+") + +local g = m.P{ + "S", + S = V"End" + V'A' * V'S', + A = P'a' + T(1), + End = P"." * (-P(1) + T(2)), +} + +assert(g:match("a.") == 3) +assert(g:match("aa.") == 4) +assert(g:match(".") == 2) +checkeqlab({nil, 1, "ba."}, g:match("ba.")) +checkeqlab({nil, 1, "ba."}, g:match("aba.")) +checkeqlab({nil, 1, "cba."}, g:match("cba.")) +checkeqlab({nil, 2, "a"}, g:match("a.a")) + + +local g2 = m.P{ + "S", + S = m.Rec(g, V"B", 1), + B = P'b'^1 + T(3) +} + +assert(g2:match("a.") == 3) +assert(g2:match("aa.") == 4) +assert(g2:match(".") == 2) +assert(g2:match("ba.") == 4) +assert(g2:match("aba.") == 5) +checkeqlab({nil, 3, "cba."}, g2:match("cba.")) +checkeqlab({nil, 2, "a"}, g2:match("a.a")) + +local g3 = m.P{ + "S", + S = m.Rec(g2, V"C", 2, 3), + C = P'c'^1 + T(4) +} + +assert(g3:match("a.") == 3) +assert(g3:match("aa.") == 4) +assert(g3:match(".") == 2) +assert(g3:match("ba.") == 4) +assert(g3:match("aba.") == 5) +assert(g3:match("cba.") == 5) +checkeqlab({nil, 4, "a"}, g3:match("a.a")) +checkeqlab({nil, 4, "dc"}, g3:match("dc")) +checkeqlab({nil, 4, "d"}, g3:match(".d")) + + + print("OK") -- cgit v1.2.3-55-g6feb From fd28f9d9e54f33bf7ae3a5e12dc71478f9c91aea Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Thu, 10 Nov 2016 16:26:11 -0300 Subject: Removing labeled choice, updating testlabel, and disabling an optmization related to Throw --- lpcode.c | 39 ++--- lpprint.c | 4 +- lptree.c | 23 +-- lptree.h | 2 +- lpvm.c | 11 -- lpvm.h | 1 - relabel.lua | 457 --------------------------------------------------------- relabelrec.lua | 396 +++++++++++++++++++++++++++++++++++++++++++++++++ test.lua | 2 +- testlabel.lua | 453 ++++++++++++++++++++++++++++++++++++++------------------ 10 files changed, 724 insertions(+), 664 deletions(-) delete mode 100644 relabel.lua create mode 100644 relabelrec.lua diff --git a/lpcode.c b/lpcode.c index d5f8d68..b2dbba2 100644 --- a/lpcode.c +++ b/lpcode.c @@ -196,7 +196,7 @@ int checkaux (TTree *tree, int pred) { if (checkaux(sib2(tree), pred)) return 1; /* else return checkaux(sib1(tree), pred); */ tree = sib1(tree); goto tailcall; - case TLabChoice: case TRecov: /* labeled failure */ + case TRecov: /* labeled failure */ /* we do not know whether sib2 will be evaluated */ tree = sib1(tree); goto tailcall; case TCapture: case TGrammar: case TRule: @@ -218,10 +218,10 @@ int fixedlenx (TTree *tree, int count, int len) { switch (tree->tag) { case TChar: case TSet: case TAny: return len + 1; - case TFalse: case TTrue: case TNot: case TAnd: case TBehind: - case TThrow: /* labeled failure */ + case TFalse: case TTrue: case TNot: case TAnd: case TBehind: return len; case TRep: case TRunTime: case TOpenCall: + case TThrow: /* labeled failure */ return -1; case TCapture: case TRule: case TGrammar: /* return fixedlenx(sib1(tree), count); */ @@ -237,7 +237,7 @@ int fixedlenx (TTree *tree, int count, int len) { /* else return fixedlenx(sib2(tree), count, len); */ tree = sib2(tree); goto tailcall; } - case TChoice: case TLabChoice: { /* labeled failure */ + case TChoice: { int n1, n2; n1 = fixedlenx(sib1(tree), count, len); if (n1 < 0) return -1; @@ -287,7 +287,7 @@ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { loopset(i, firstset->cs[i] = follow->cs[i]); /* follow = fullset(?) */ return 1; } - case TChoice: case TLabChoice: { /*(?) labeled failure */ + case TChoice: { Charset csaux; int e1 = getfirst(sib1(tree), follow, firstset); int e2 = getfirst(sib2(tree), follow, &csaux); @@ -378,7 +378,7 @@ static int headfail (TTree *tree) { if (!nofail(sib2(tree))) return 0; /* else return headfail(sib1(tree)); */ tree = sib1(tree); goto tailcall; - case TChoice: case TLabChoice: case TRecov: /* labeled failure */ + case TChoice: case TRecov: /* labeled failure */ if (!headfail(sib1(tree))) return 0; /* else return headfail(sib2(tree)); */ tree = sib2(tree); goto tailcall; @@ -398,7 +398,7 @@ static int needfollow (TTree *tree) { case TChar: case TSet: case TAny: case TFalse: case TTrue: case TAnd: case TNot: case TRunTime: case TGrammar: case TCall: case TBehind: - case TThrow: case TLabChoice: case TRecov: /* (?)labeled failure */ + case TThrow: case TRecov: /* (?)labeled failure */ return 0; case TChoice: case TRep: return 1; @@ -433,7 +433,7 @@ int sizei (const Instruction *i) { return 2; case IThrow: /* labeled failure */ return 1; - case ILabChoice: case IRecov: + case IRecov: return (CHARSETINSTSIZE - 1) + 2; /* labeled failure */ default: return 1; } @@ -499,7 +499,7 @@ static int addoffsetinst (CompileState *compst, Opcode op) { int i = addinstruction(compst, op, 0); /* instruction */ addinstruction(compst, (Opcode)0, 0); /* open space for offset */ assert(op == ITestSet || sizei(&getinstr(compst, i)) == 2 || - op == IRecov || op == ILabChoice); /* labeled failure */ + op == IRecov); /* labeled failure */ return i; } @@ -707,21 +707,6 @@ static void codechoice (CompileState *compst, TTree *p1, TTree *p2, int opt, /* labeled failure begin */ -static void codelabchoice (CompileState *compst, TTree *p1, TTree *p2, int opt, - const Charset *fl, const byte *cs) { - int emptyp2 = (p2->tag == TTrue); - int pcommit; - int test = NOINST; - int pchoice = addoffsetinst(compst, ILabChoice); - addcharset(compst, cs); - codegen(compst, p1, emptyp2, test, fullset); - pcommit = addoffsetinst(compst, ICommit); - jumptohere(compst, pchoice); - jumptohere(compst, test); - codegen(compst, p2, opt, NOINST, fl); - jumptohere(compst, pcommit); -} - static void coderecovery (CompileState *compst, TTree *p1, TTree *p2, int opt, const Charset *fl, const byte *cs) { int emptyp2 = (p2->tag == TTrue); @@ -970,10 +955,6 @@ static void codegen (CompileState *compst, TTree *tree, int opt, int tt, addinstruction(compst, IThrow, (byte) tree->u.label); break; } - case TLabChoice: { /* labeled failure */ - codelabchoice(compst, sib1(tree), sib2(tree), opt, fl, treelabelset(tree)); - break; - } case TRecov: { /* labeled failure */ coderecovery(compst, sib1(tree), sib2(tree), opt, fl, treelabelset(tree)); break; @@ -1000,7 +981,7 @@ static void peephole (CompileState *compst) { switch (code[i].i.code) { case IChoice: case ICall: case ICommit: case IPartialCommit: case IBackCommit: case ITestChar: case ITestSet: - case ILabChoice: case IRecov: /* labeled failure */ + case IRecov: /* labeled failure */ case ITestAny: { /* instructions with labels */ jumptothere(compst, i, finallabel(code, i)); /* optimize label */ break; diff --git a/lpprint.c b/lpprint.c index 122d2e5..ff69a6a 100644 --- a/lpprint.c +++ b/lpprint.c @@ -112,7 +112,7 @@ void printinst (const Instruction *op, const Instruction *p) { printf("%d", p->i.aux); break; } - case ILabChoice: case IRecov: { /* labeled failure */ + case IRecov: { /* labeled failure */ printjmp(op, p); printcharset((p+2)->buff); break; @@ -223,7 +223,7 @@ void printtree (TTree *tree, int ident) { default: { int sibs = numsiblings[tree->tag]; printf("\n"); - if (tree->tag == TLabChoice || tree->tag == TRecov) { /* labeled failure */ + if (tree->tag == TRecov) { /* labeled failure */ printcharset(treelabelset(tree)); printf("\n"); } diff --git a/lptree.c b/lptree.c index 700398b..9861cfe 100644 --- a/lptree.c +++ b/lptree.c @@ -723,37 +723,25 @@ static int lp_behind (lua_State *L) { */ static int lp_throw (lua_State *L) { int label = luaL_checkinteger(L, -1); - luaL_argcheck(L, label >= 0 && label < MAXLABELS, -1, "the number of a label must be between 0 and 255"); + luaL_argcheck(L, label >= 1 && label < MAXLABELS, -1, "the number of a label must be between 1 and 255"); newthrowleaf(L, label); return 1; } /* -** labeled choice function +** labeled recovery function */ -static int lp_labchoice (lua_State *L) { - int n = lua_gettop(L); - TTree *tree = newrootlab2sib(L, TLabChoice); - int i; - for (i = 3; i <= n; i++) { - int d = luaL_checkinteger(L, i); - luaL_argcheck(L, d >= 0 && d < MAXLABELS, i, "the number of a label must be between 0 and 255"); - setlabel(treelabelset(tree), (byte)d); - } - return 1; -} - - static int lp_recovery (lua_State *L) { int n = lua_gettop(L); TTree *tree = newrootlab2sib(L, TRecov); + luaL_argcheck(L, n >= 3, 3, "non-nil value expected"); if (n == 2) { /* catches fail as default */ /*setlabel(treelabelset(tree), LFAIL); recovery does not catch regular fail */ } else { int i; for (i = 3; i <= n; i++) { int d = luaL_checkinteger(L, i); - luaL_argcheck(L, d >= 0 && d < MAXLABELS, i, "the number of a label must be between 0 and 255"); + luaL_argcheck(L, d >= 1 && d < MAXLABELS, i, "the number of a label must be between 1 and 255"); setlabel(treelabelset(tree), (byte)d); } } @@ -1089,7 +1077,7 @@ static int verifyrule (lua_State *L, TTree *tree, int *passed, int npassed, return nb; /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */ tree = sib2(tree); goto tailcall; - case TChoice: case TLabChoice: case TRecov: /* must check both children */ /* labeled failure */ + case TChoice: case TRecov: /* must check both children */ /* labeled failure */ nb = verifyrule(L, sib1(tree), passed, npassed, nb); /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ tree = sib2(tree); goto tailcall; @@ -1342,7 +1330,6 @@ static struct luaL_Reg pattreg[] = { {"setmaxstack", lp_setmax}, {"type", lp_type}, {"T", lp_throw}, /* labeled failure throw */ - {"Lc", lp_labchoice}, /* labeled failure choice */ {"Rec", lp_recovery}, /* labeled failure choice */ {NULL, NULL} }; diff --git a/lptree.h b/lptree.h index cca346e..b75f323 100644 --- a/lptree.h +++ b/lptree.h @@ -25,7 +25,7 @@ typedef enum TTag { TBehind, /* match behind */ TCapture, /* regular capture */ TRunTime, /* run-time capture */ - TThrow, TLabChoice, TRecov /* labeled failure */ + TThrow, TRecov /* labeled failure */ } TTag; /* number of siblings for each tree */ diff --git a/lpvm.c b/lpvm.c index b200dce..122c8f4 100644 --- a/lpvm.c +++ b/lpvm.c @@ -273,17 +273,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, stack++; p += 2; continue; - } - case ILabChoice: { /* labeled failure */ - if (stack == stacklimit) - stack = doublestack(L, &stacklimit, ptop); - stack->p = p + getoffset(p); - stack->s = s; - stack->ls = (const Labelset *) ((p + 2)->buff); - stack->caplevel = captop; - stack++; - p += (CHARSETINSTSIZE - 1) + 2; - continue; } case IRecov: { /* labeled failure */ if (stack == stacklimit) diff --git a/lpvm.h b/lpvm.h index 3c27027..bcfc22f 100644 --- a/lpvm.h +++ b/lpvm.h @@ -35,7 +35,6 @@ typedef enum Opcode { ICloseCapture, ICloseRunTime, IThrow, /* "fails" with a specific label labeled failure */ - ILabChoice, /* labeled choice */ IRecov /* stack a recovery; next fail with label 'f' will jump to 'offset' */ } Opcode; diff --git a/relabel.lua b/relabel.lua deleted file mode 100644 index 0e7195e..0000000 --- a/relabel.lua +++ /dev/null @@ -1,457 +0,0 @@ --- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $ - --- imported functions and modules -local tonumber, type, print, error, ipairs = tonumber, type, print, error, ipairs -local pcall = pcall -local setmetatable = setmetatable -local unpack, tinsert, concat = table.unpack or unpack, table.insert, table.concat -local rep = string.rep -local m = require"lpeglabelrec" - --- 'm' will be used to parse expressions, and 'mm' will be used to --- create expressions; that is, 're' runs on 'm', creating patterns --- on 'mm' -local mm = m - --- pattern's metatable -local mt = getmetatable(mm.P(0)) - - - --- No more global accesses after this point -local version = _VERSION -if version == "Lua 5.2" then _ENV = nil end - - -local any = m.P(1) -local dummy = mm.P(false) - - -local errinfo = { - {"NoPatt", "no pattern found"}, - {"ExtraChars", "unexpected characters after the pattern"}, - - {"ExpPatt1", "expected a pattern after '/' or the label(s)"}, - - {"ExpPatt2", "expected a pattern after '&'"}, - {"ExpPatt3", "expected a pattern after '!'"}, - - {"ExpPatt4", "expected a pattern after '('"}, - {"ExpPatt5", "expected a pattern after ':'"}, - {"ExpPatt6", "expected a pattern after '{~'"}, - {"ExpPatt7", "expected a pattern after '{|'"}, - - {"ExpPatt8", "expected a pattern after '<-'"}, - - {"ExpPattOrClose", "expected a pattern or closing '}' after '{'"}, - - {"ExpNum", "expected a number after '^', '+' or '-' (no space)"}, - {"ExpCap", "expected a string, number, '{}' or name after '->'"}, - - {"ExpName1", "expected the name of a rule after '=>'"}, - {"ExpName2", "expected the name of a rule after '=' (no space)"}, - {"ExpName3", "expected the name of a rule after '<' (no space)"}, - - {"ExpLab1", "expected at least one label after '{'"}, - {"ExpLab2", "expected a label after the comma"}, - - {"ExpNameOrLab", "expected a name or label after '%' (no space)"}, - - {"ExpItem", "expected at least one item after '[' or '^'"}, - - {"MisClose1", "missing closing ')'"}, - {"MisClose2", "missing closing ':}'"}, - {"MisClose3", "missing closing '~}'"}, - {"MisClose4", "missing closing '|}'"}, - {"MisClose5", "missing closing '}'"}, -- for the captures - - {"MisClose6", "missing closing '>'"}, - {"MisClose7", "missing closing '}'"}, -- for the labels - - {"MisClose8", "missing closing ']'"}, - - {"MisTerm1", "missing terminating single quote"}, - {"MisTerm2", "missing terminating double quote"}, -} - -local errmsgs = {} -local labels = {} - -for i, err in ipairs(errinfo) do - errmsgs[i] = err[2] - labels[err[1]] = i -end - -local syntaxerrs = {} - -local function expect (pattern, labelname) - local label = labels[labelname] - local record = function (input, pos) - tinsert(syntaxerrs, { label = label, pos = pos }) - return true - end - return pattern + m.Cmt("", record) * m.T(label) -end - - --- Pre-defined names -local Predef = { nl = m.P"\n" } -local tlabels = {} - - -local mem -local fmem -local gmem - - -local function updatelocale () - mm.locale(Predef) - Predef.a = Predef.alpha - Predef.c = Predef.cntrl - Predef.d = Predef.digit - Predef.g = Predef.graph - Predef.l = Predef.lower - Predef.p = Predef.punct - Predef.s = Predef.space - Predef.u = Predef.upper - Predef.w = Predef.alnum - Predef.x = Predef.xdigit - Predef.A = any - Predef.a - Predef.C = any - Predef.c - Predef.D = any - Predef.d - Predef.G = any - Predef.g - Predef.L = any - Predef.l - Predef.P = any - Predef.p - Predef.S = any - Predef.s - Predef.U = any - Predef.u - Predef.W = any - Predef.w - Predef.X = any - Predef.x - mem = {} -- restart memoization - fmem = {} - gmem = {} - local mt = {__mode = "v"} - setmetatable(mem, mt) - setmetatable(fmem, mt) - setmetatable(gmem, mt) -end - - -updatelocale() - - - -local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) - - -local function getdef (id, defs) - local c = defs and defs[id] - if not c then - error("undefined name: " .. id) - end - return c -end - - -local function mult (p, n) - local np = mm.P(true) - while n >= 1 do - if n%2 >= 1 then np = np * p end - p = p * p - n = n/2 - end - return np -end - -local function equalcap (s, i, c) - if type(c) ~= "string" then return nil end - local e = #c + i - if s:sub(i, e - 1) == c then return e else return nil end -end - - -local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 - -local name = m.C(m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0) - -local arrow = S * "<-" - --- a defined name only have meaning in a given environment -local Def = name * m.Carg(1) - -local num = m.C(m.R"09"^1) * S / tonumber - -local String = "'" * m.C((any - "'" - m.P"\n")^0) * expect("'", "MisTerm1") - + '"' * m.C((any - '"' - m.P"\n")^0) * expect('"', "MisTerm2") - - -local defined = "%" * Def / function (c,Defs) - local cat = Defs and Defs[c] or Predef[c] - if not cat then - error("name '" .. c .. "' undefined") - end - return cat -end - -local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R - -local item = defined + Range + m.C(any - m.P"\n") - -local Class = - "[" - * (m.C(m.P"^"^-1)) -- optional complement symbol - * m.Cf(expect(item, "ExpItem") * (item - "]")^0, mt.__add) - / function (c, p) return c == "^" and any - p or p end - * expect("]", "MisClose8") - -local function adddef (t, k, exp) - if t[k] then - error("'"..k.."' already defined as a rule") - else - t[k] = exp - end - return t -end - -local function firstdef (n, r) return adddef({n}, n, r) end - - -local function NT (n, b) - if not b then - error("rule '"..n.."' used outside a grammar") - else return mm.V(n) - end -end - -local function labchoice (...) - local t = { ... } - local n = #t - local p = t[1] - local i = 2 - while i + 1 <= n do - -- t[i] == nil when there are no labels - p = t[i] and mm.Lc(p, t[i+1], unpack(t[i])) or mt.__add(p, t[i+1]) - i = i + 2 - end - - return p -end - --- error recovery -local skip = m.P { "Skip", - Skip = (-m.P"/" * -m.P(name * arrow) * m.V"Ignored")^0 * m.Cc(dummy); - Ignored = m.V"Group" + any; - Group = "(" * (-m.P")" * m.V"Ignored")^0 * ")" - + "{" * (-m.P"}" * m.V"Ignored")^0 * "}" - + "[" * (-m.P"]" * m.V"Ignored")^0 * "]" - + "'" * (-m.P"'" * m.V"Ignored")^0 * "'" - + '"' * (-m.P'"' * m.V"Ignored")^0 * '"'; -} - -local ignore = m.Cmt(any, function (input, pos) - return syntaxerrs[#syntaxerrs].pos, dummy -end) - -local pointAtStart = m.Cmt(any, function (input, pos) - -- like ignore but makes the last syntax error point at the start - local ret = syntaxerrs[#syntaxerrs].pos - syntaxerrs[#syntaxerrs].pos = pos-1 - return ret, dummy -end) - - -local function labify (labelnames) - for i, l in ipairs(labelnames) do - labelnames[i] = labels[l] - end - return labelnames -end - -local labelset1 = labify { - "ExpPatt2", "ExpPatt3", - "ExpPatt4", "ExpPatt5", "ExpPatt6", "ExpPatt7", - "ExpPatt8", "ExpPattOrClose", - "ExpNum", "ExpCap", - "ExpName1", "ExpName2", "ExpName3", - "ExpNameOrLab", "ExpItem", - "MisClose6", "MisClose7" -} - -local labelset2 = labify { - "MisClose1", "MisClose2", "MisClose3", "MisClose4", "MisClose5" -} - -local labelset3 = labify { - "ExpPatt1", "ExpLab1", "ExpLab2", "MisClose7" -} - -local exp = m.P{ "Exp", - Exp = S * ( m.V"Grammar" - + (m.V"RecovSeq" * (S * "/" * m.Lc((m.Ct(m.V"Labels") + m.Cc(nil)) - * expect(S * m.V"RecovSeq", - "ExpPatt1"), - m.Cc(nil) * skip, - unpack(labelset3)) - )^0 - ) / labchoice); - Labels = m.P"{" * expect(S * m.V"Label", "ExpLab1") - * (S * "," * expect(S * m.V"Label", "ExpLab2"))^0 - * expect(S * "}", "MisClose7"); - RecovSeq = m.Lc(m.V"Seq", skip, unpack(labelset1)); - Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix" * (S * m.V"Prefix")^0, mt.__mul); - Prefix = "&" * expect(S * m.V"Prefix", "ExpPatt2") / mt.__len - + "!" * expect(S * m.V"Prefix", "ExpPatt3") / mt.__unm - + m.V"Suffix"; - Suffix = m.Cf(m.V"RecovPrimary" * - ( S * ( m.P"+" * m.Cc(1, mt.__pow) - + m.P"*" * m.Cc(0, mt.__pow) - + m.P"?" * m.Cc(-1, mt.__pow) - + "^" * expect( m.Cg(num * m.Cc(mult)) - + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow) - ), - "ExpNum") - + "->" * expect(S * ( m.Cg((String + num) * m.Cc(mt.__div)) - + m.P"{}" * m.Cc(nil, m.Ct) - + m.Cg(Def / getdef * m.Cc(mt.__div)) - ), - "ExpCap") - + "=>" * expect(S * m.Cg(Def / getdef * m.Cc(m.Cmt)), - "ExpName1") - ) - )^0, function (a,b,f) return f(a,b) end ); - RecovPrimary = m.Lc(m.V"Primary", ignore, unpack(labelset2)); - Primary = "(" * expect(m.V"Exp", "ExpPatt4") * expect(S * ")", "MisClose1") - + m.Lc(String / mm.P, pointAtStart, - labels["MisTerm1"], labels["MisTerm2"]) - + m.Lc(Class, pointAtStart, labels["MisClose8"]) - + defined - + "%" * expect(m.V"Labels", "ExpNameOrLab") / mm.T - + "{:" * (name * ":" + m.Cc(nil)) * expect(m.V"Exp", "ExpPatt5") - * expect(S * ":}", "MisClose2") - / function (n, p) return mm.Cg(p, n) end - + "=" * expect(name, "ExpName2") - / function (n) return mm.Cmt(mm.Cb(n), equalcap) end - + m.P"{}" / mm.Cp - + "{~" * expect(m.V"Exp", "ExpPatt6") - * expect(S * "~}", "MisClose3") / mm.Cs - + "{|" * expect(m.V"Exp", "ExpPatt7") - * expect(S * "|}", "MisClose4") / mm.Ct - + "{" * expect(m.V"Exp", "ExpPattOrClose") - * expect(S * "}", "MisClose5") / mm.C - + m.P"." * m.Cc(any) - + (name * -arrow + "<" * expect(name, "ExpName3") - * expect(">", "MisClose6")) * m.Cb("G") / NT; - Label = num + name / function (f) return tlabels[f] end; - Definition = name * arrow * expect(m.V"Exp", "ExpPatt8"); - Grammar = m.Cg(m.Cc(true), "G") - * m.Cf(m.V"Definition" / firstdef * (S * m.Cg(m.V"Definition"))^0, - adddef) / mm.P; -} - -local pattern = S * m.Cg(m.Cc(false), "G") * expect(exp, "NoPatt") / mm.P - * S * expect(-any, "ExtraChars") - -local function lineno (s, i) - if i == 1 then return 1, 1 end - local adjustment = 0 - -- report the current line if at end of line, not the next - if s:sub(i,i) == '\n' then - i = i-1 - adjustment = 1 - end - local rest, num = s:sub(1,i):gsub("[^\n]*\n", "") - local r = #rest - return 1 + num, (r ~= 0 and r or 1) + adjustment -end - -local function splitlines(str) - local t = {} - local function helper(line) tinsert(t, line) return "" end - helper((str:gsub("(.-)\r?\n", helper))) - return t -end - -local function compile (p, defs) - if mm.type(p) == "pattern" then return p end -- already compiled - p = p .. " " -- for better reporting of column numbers in errors when at EOF - local ok, cp, label, suffix = pcall(function() return pattern:match(p, 1, defs) end) - if not ok and #syntaxerrs == 0 then - if type(cp) == "string" then - cp = cp:gsub("^[^:]+:[^:]+: ", "") - end - error(cp, 3) - end - if #syntaxerrs > 0 then - local lines = splitlines(p) - local errors = {} - for i, err in ipairs(syntaxerrs) do - local line, col = lineno(p, err.pos) - tinsert(errors, "L" .. line .. ":C" .. col .. ": " .. errmsgs[err.label]) - tinsert(errors, lines[line]) - tinsert(errors, rep(" ", col-1) .. "^") - end - syntaxerrs = {} - error("syntax error(s) in pattern\n" .. concat(errors, "\n"), 3) - end - return cp -end - -local function match (s, p, i) - local cp = mem[p] - if not cp then - cp = compile(p) - mem[p] = cp - end - return cp:match(s, i or 1) -end - -local function find (s, p, i) - local cp = fmem[p] - if not cp then - cp = compile(p) / 0 - cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } - fmem[p] = cp - end - local i, e = cp:match(s, i or 1) - if i then return i, e - 1 - else return i - end -end - -local function gsub (s, p, rep) - local g = gmem[p] or {} -- ensure gmem[p] is not collected while here - gmem[p] = g - local cp = g[rep] - if not cp then - cp = compile(p) - cp = mm.Cs((cp / rep + 1)^0) - g[rep] = cp - end - return cp:match(s) -end - -local function setlabels (t) - tlabels = t -end - -local function calcline (s, i) - if i == 1 then return 1, 1 end - local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") - local col = #rest - return 1 + line, col ~= 0 and col or 1 -end - - --- exported names -local re = { - compile = compile, - match = match, - find = find, - gsub = gsub, - updatelocale = updatelocale, - setlabels = setlabels, - calcline = calcline -} - -if version == "Lua 5.1" then _G.re = re end - -return re diff --git a/relabelrec.lua b/relabelrec.lua new file mode 100644 index 0000000..16ca7f0 --- /dev/null +++ b/relabelrec.lua @@ -0,0 +1,396 @@ +-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $ + +-- imported functions and modules +local tonumber, type, print, error, ipairs = tonumber, type, print, error, ipairs +local pcall = pcall +local setmetatable = setmetatable +local unpack, tinsert, concat = table.unpack or unpack, table.insert, table.concat +local rep = string.rep +local m = require"lpeglabelrec" + +-- 'm' will be used to parse expressions, and 'mm' will be used to +-- create expressions; that is, 're' runs on 'm', creating patterns +-- on 'mm' +local mm = m + +-- pattern's metatable +local mt = getmetatable(mm.P(0)) + + + +-- No more global accesses after this point +local version = _VERSION +if version == "Lua 5.2" then _ENV = nil end + + +local any = m.P(1) +local dummy = mm.P(false) + + +local errinfo = { + {"NoPatt", "no pattern found"}, + {"ExtraChars", "unexpected characters after the pattern"}, + + {"ExpPatt1", "expected a pattern after '/' or the label(s)"}, + + {"ExpPatt2", "expected a pattern after '&'"}, + {"ExpPatt3", "expected a pattern after '!'"}, + + {"ExpPatt4", "expected a pattern after '('"}, + {"ExpPatt5", "expected a pattern after ':'"}, + {"ExpPatt6", "expected a pattern after '{~'"}, + {"ExpPatt7", "expected a pattern after '{|'"}, + + {"ExpPatt8", "expected a pattern after '<-'"}, + + {"ExpPattOrClose", "expected a pattern or closing '}' after '{'"}, + + {"ExpNum", "expected a number after '^', '+' or '-' (no space)"}, + {"ExpCap", "expected a string, number, '{}' or name after '->'"}, + + {"ExpName1", "expected the name of a rule after '=>'"}, + {"ExpName2", "expected the name of a rule after '=' (no space)"}, + {"ExpName3", "expected the name of a rule after '<' (no space)"}, + + {"ExpLab1", "expected at least one label after '{'"}, + {"ExpLab2", "expected a label after the comma"}, + + {"ExpNameOrLab", "expected a name or label after '%' (no space)"}, + + {"ExpItem", "expected at least one item after '[' or '^'"}, + + {"MisClose1", "missing closing ')'"}, + {"MisClose2", "missing closing ':}'"}, + {"MisClose3", "missing closing '~}'"}, + {"MisClose4", "missing closing '|}'"}, + {"MisClose5", "missing closing '}'"}, -- for the captures + + {"MisClose6", "missing closing '>'"}, + {"MisClose7", "missing closing '}'"}, -- for the labels + + {"MisClose8", "missing closing ']'"}, + + {"MisTerm1", "missing terminating single quote"}, + {"MisTerm2", "missing terminating double quote"}, +} + +local errmsgs = {} +local labels = {} + +for i, err in ipairs(errinfo) do + errmsgs[i] = err[2] + labels[err[1]] = i +end + +local function expect (pattern, labelname) + local label = labels[labelname] + return pattern + m.T(label) +end + + +-- Pre-defined names +local Predef = { nl = m.P"\n" } +local tlabels = {} + + +local mem +local fmem +local gmem + + +local function updatelocale () + mm.locale(Predef) + Predef.a = Predef.alpha + Predef.c = Predef.cntrl + Predef.d = Predef.digit + Predef.g = Predef.graph + Predef.l = Predef.lower + Predef.p = Predef.punct + Predef.s = Predef.space + Predef.u = Predef.upper + Predef.w = Predef.alnum + Predef.x = Predef.xdigit + Predef.A = any - Predef.a + Predef.C = any - Predef.c + Predef.D = any - Predef.d + Predef.G = any - Predef.g + Predef.L = any - Predef.l + Predef.P = any - Predef.p + Predef.S = any - Predef.s + Predef.U = any - Predef.u + Predef.W = any - Predef.w + Predef.X = any - Predef.x + mem = {} -- restart memoization + fmem = {} + gmem = {} + local mt = {__mode = "v"} + setmetatable(mem, mt) + setmetatable(fmem, mt) + setmetatable(gmem, mt) +end + + +updatelocale() + + + +local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) + + +local function getdef (id, defs) + local c = defs and defs[id] + if not c then + error("undefined name: " .. id) + end + return c +end + + +local function mult (p, n) + local np = mm.P(true) + while n >= 1 do + if n%2 >= 1 then np = np * p end + p = p * p + n = n/2 + end + return np +end + +local function equalcap (s, i, c) + if type(c) ~= "string" then return nil end + local e = #c + i + if s:sub(i, e - 1) == c then return e else return nil end +end + + +local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 + +local name = m.C(m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0) + +local arrow = S * "<-" + +-- a defined name only have meaning in a given environment +local Def = name * m.Carg(1) + +local num = m.C(m.R"09"^1) * S / tonumber + +local String = "'" * m.C((any - "'" - m.P"\n")^0) * expect("'", "MisTerm1") + + '"' * m.C((any - '"' - m.P"\n")^0) * expect('"', "MisTerm2") + + +local defined = "%" * Def / function (c,Defs) + local cat = Defs and Defs[c] or Predef[c] + if not cat then + error("name '" .. c .. "' undefined") + end + return cat +end + +local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R + +local item = defined + Range + m.C(any - m.P"\n") + +local Class = + "[" + * (m.C(m.P"^"^-1)) -- optional complement symbol + * m.Cf(expect(item, "ExpItem") * (item - "]")^0, mt.__add) + / function (c, p) return c == "^" and any - p or p end + * expect("]", "MisClose8") + +local function adddef (t, k, exp) + if t[k] then + error("'"..k.."' already defined as a rule") + else + t[k] = exp + end + return t +end + +local function firstdef (n, r) return adddef({n}, n, r) end + + +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end + +local function choicerec (...) + local t = { ... } + local n = #t + local p = t[1] + local i = 2 + while i + 1 <= n do + -- t[i] == nil when there are no labels + p = t[i] and mm.Rec(p, t[i+1], unpack(t[i])) or mt.__add(p, t[i+1]) + i = i + 2 + end + + return p +end + +local exp = m.P{ "Exp", + Exp = S * ( m.V"Grammar" + + (m.V"Seq" * (S * (("//" * m.Ct(m.V"Labels")) + ("/" * m.Cc(nil))) + * expect(S * m.V"Seq", "ExpPatt1") + )^0 + ) / choicerec); + Labels = m.P"{" * expect(S * m.V"Label", "ExpLab1") + * (S * "," * expect(S * m.V"Label", "ExpLab2"))^0 + * expect(S * "}", "MisClose7"); + Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix" * (S * m.V"Prefix")^0, mt.__mul); + Prefix = "&" * expect(S * m.V"Prefix", "ExpPatt2") / mt.__len + + "!" * expect(S * m.V"Prefix", "ExpPatt3") / mt.__unm + + m.V"Suffix"; + Suffix = m.Cf(m.V"Primary" * + ( S * ( m.P"+" * m.Cc(1, mt.__pow) + + m.P"*" * m.Cc(0, mt.__pow) + + m.P"?" * m.Cc(-1, mt.__pow) + + "^" * expect( m.Cg(num * m.Cc(mult)) + + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow) + ), + "ExpNum") + + "->" * expect(S * ( m.Cg((String + num) * m.Cc(mt.__div)) + + m.P"{}" * m.Cc(nil, m.Ct) + + m.Cg(Def / getdef * m.Cc(mt.__div)) + ), + "ExpCap") + + "=>" * expect(S * m.Cg(Def / getdef * m.Cc(m.Cmt)), + "ExpName1") + ) + )^0, function (a,b,f) return f(a,b) end ); + Primary = "(" * expect(m.V"Exp", "ExpPatt4") * expect(S * ")", "MisClose1") + + String / mm.P + + Class + + defined + + "%" * expect(m.P"{", "ExpNameOrLab") + * expect(S * m.V"Label", "ExpLab1") + * expect(S * "}", "MisClose7") / mm.T + + "{:" * (name * ":" + m.Cc(nil)) * expect(m.V"Exp", "ExpPatt5") + * expect(S * ":}", "MisClose2") + / function (n, p) return mm.Cg(p, n) end + + "=" * expect(name, "ExpName2") + / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + + m.P"{}" / mm.Cp + + "{~" * expect(m.V"Exp", "ExpPatt6") + * expect(S * "~}", "MisClose3") / mm.Cs + + "{|" * expect(m.V"Exp", "ExpPatt7") + * expect(S * "|}", "MisClose4") / mm.Ct + + "{" * expect(m.V"Exp", "ExpPattOrClose") + * expect(S * "}", "MisClose5") / mm.C + + m.P"." * m.Cc(any) + + (name * -arrow + "<" * expect(name, "ExpName3") + * expect(">", "MisClose6")) * m.Cb("G") / NT; + Label = num + name / function (f) return tlabels[f] end; + Definition = name * arrow * expect(m.V"Exp", "ExpPatt8"); + Grammar = m.Cg(m.Cc(true), "G") + * m.Cf(m.V"Definition" / firstdef * (S * m.Cg(m.V"Definition"))^0, + adddef) / mm.P; +} + +local pattern = S * m.Cg(m.Cc(false), "G") * expect(exp, "NoPatt") / mm.P + * S * expect(-any, "ExtraChars") + +local function lineno (s, i) + if i == 1 then return 1, 1 end + local adjustment = 0 + -- report the current line if at end of line, not the next + if s:sub(i,i) == '\n' then + i = i-1 + adjustment = 1 + end + local rest, num = s:sub(1,i):gsub("[^\n]*\n", "") + local r = #rest + return 1 + num, (r ~= 0 and r or 1) + adjustment +end + +local function splitlines(str) + local t = {} + local function helper(line) tinsert(t, line) return "" end + helper((str:gsub("(.-)\r?\n", helper))) + return t +end + +local function compile (p, defs) + if mm.type(p) == "pattern" then return p end -- already compiled + p = p .. " " -- for better reporting of column numbers in errors when at EOF + local ok, cp, label, suffix = pcall(function() return pattern:match(p, 1, defs) end) + if not ok and cp then + if type(cp) == "string" then + cp = cp:gsub("^[^:]+:[^:]+: ", "") + end + error(cp, 3) + end + if not cp then + local lines = splitlines(p) + local line, col = lineno(p, #p - #suffix + 1) + local err = {} + tinsert(err, "L" .. line .. ":C" .. col .. ": " .. errmsgs[label]) + tinsert(err, lines[line]) + tinsert(err, rep(" ", col-1) .. "^") + error("syntax error(s) in pattern\n" .. concat(err, "\n"), 3) + end + return cp +end + +local function match (s, p, i) + local cp = mem[p] + if not cp then + cp = compile(p) + mem[p] = cp + end + return cp:match(s, i or 1) +end + +local function find (s, p, i) + local cp = fmem[p] + if not cp then + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } + fmem[p] = cp + end + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end +end + +local function gsub (s, p, rep) + local g = gmem[p] or {} -- ensure gmem[p] is not collected while here + gmem[p] = g + local cp = g[rep] + if not cp then + cp = compile(p) + cp = mm.Cs((cp / rep + 1)^0) + g[rep] = cp + end + return cp:match(s) +end + +local function setlabels (t) + tlabels = t +end + +local function calcline (s, i) + if i == 1 then return 1, 1 end + local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") + local col = #rest + return 1 + line, col ~= 0 and col or 1 +end + + +-- exported names +local re = { + compile = compile, + match = match, + find = find, + gsub = gsub, + updatelocale = updatelocale, + setlabels = setlabels, + calcline = calcline +} + +if version == "Lua 5.1" then _G.re = re end + +return re diff --git a/test.lua b/test.lua index fc2b607..18ab20f 100755 --- a/test.lua +++ b/test.lua @@ -1110,7 +1110,7 @@ checkeq(t, {'a', 'aa', 20, 'a', 'aaa', 'aaa'}) -- Tests for 're' module ------------------------------------------------------------------- -local re = require "relabel" +local re = require "relabelrec" local match, compile = re.match, re.compile diff --git a/testlabel.lua b/testlabel.lua index 25036b2..cb92657 100644 --- a/testlabel.lua +++ b/testlabel.lua @@ -11,6 +11,15 @@ local function checkeqlab (x, ...) end end +local function checkeq (x, y, p) +if p then print(x,y) end + if type(x) ~= "table" then assert(x == y) + else + for k,v in pairs(x) do checkeq(v, y[k], p) end + for k,v in pairs(y) do checkeq(v, x[k], p) end + end +end + -- throws a label p = m.T(1) s = "abc" @@ -32,48 +41,97 @@ local g = m.P{ r, l, serror = g:match(s) assert(r == nil and l == 1 and serror == "abc") ---[==[ TODO: labeled choice does not work anymore --- throws a label that is not caught by labeled choice -p = m.Lc(m.T(2), m.P"a", 1, 3) + +-- throws a label that is not caught by the recovery operator +p = m.Rec(m.T(2), m.P"a", 1, 3) r, l, serror = p:match(s) assert(r == nil and l == 2 and serror == "abc") --- modifies previous pattern --- adds another labeled choice to catch label "2" -p = m.Lc(p, m.P"a", 2) +-- wraps the previous pattern with a recovery that catches label "2" +p = m.Rec(p, m.P"a", 2) assert(p:match(s) == 2) --- throws a label that is caught by labeled choice -p = m.Lc(m.T(25), m.P"a", 25) +-- throws a label that is caught by recovery +p = m.Rec(m.T(25), m.P"a", 25) assert(p:match(s) == 2) -- "fail" is label "0" --- throws the "fail" label that is not caught by the labeled choice +-- throws the "fail" label after the recovery s = "bola" r, l, serror = p:match("bola") assert(r == nil and l == 0 and serror == "bola") --- labeled choice does not catch "fail" by default -p = m.Lc(m.P"b", m.P"a", 1) +-- Recovery does not catch "fail" by default +p = m.Rec(m.P"b", m.P"a", 1) r, l, serror = p:match("abc") assert(r == nil and l == 0 and serror == "abc") assert(p:match("bola") == 2) --- labeled choice can catch "fail" -p = m.Lc(m.P"b", m.P"a", 0) + +-- recovery operator catches "1" or "3" +p = m.Rec((m.P"a" + m.T(1)) * m.T(3), (m.P"a" + m.P"b"), 1, 3) +assert(p:match("aac") == 3) +assert(p:match("abc") == 3) +r, l, serror = p:match("acc") +assert(r == nil and l == 0 and serror == "cc") + +--throws 1, recovery pattern matches 'b', throw 3, and rec pat mathces 'a' +assert(p:match("bac") == 3) + +r, l, serror = p:match("cab") +assert(r == nil and l == 0 and serror == "cab") + + +-- associativity +-- (p1 / %1) //{1} (p2 / %2) //{2} p3 +-- left-associativity +-- ("a" //{1} "b") //{2} "c" +p = m.Rec(m.Rec(m.P"a" + m.T(1), m.P"b" + m.T(2), 1), m.P"c", 2) assert(p:match("abc") == 2) -assert(p:match("bola") == 2) +assert(p:match("bac") == 2) +assert(p:match("cab") == 2) +r, l, serror = p:match("dab") +assert(r == nil and l == 0 and serror == "dab") --- "fail" is label "0" --- labeled choice catches "fail" or "3" -p = m.Lc(m.P"a" * m.T(3), (m.P"a" + m.P"b"), 0, 3) + +-- righ-associativity +-- "a" //{1} ("b" //{2} "c") +p = m.Rec(m.P"a" + m.T(1), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1) assert(p:match("abc") == 2) assert(p:match("bac") == 2) +assert(p:match("cab") == 2) +r, l, serror = p:match("dab") +assert(r == nil and l == 0 and serror == "dab") + + +-- associativity -> in this case the error thrown by p1 is only +-- recovered when we have a left-associative operator +-- (p1 / %2) //{1} (p2 / %2) //{2} p3 +-- left-associativity +-- ("a" //{1} "b") //{2} "c" +p = m.Rec(m.Rec(m.P"a" + m.T(2), m.P"b" + m.T(2), 1), m.P"c", 2) +assert(p:match("abc") == 2) +r, l, serror = p:match("bac") +assert(r == nil and l == 0 and serror == "bac") +assert(p:match("cab") == 2) +r, l, serror = p:match("dab") +assert(r == nil and l == 0 and serror == "dab") + +-- righ-associativity +-- "a" //{1} ("b" //{2} "c") +p = m.Rec(m.P"a" + m.T(2), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1) +assert(p:match("abc") == 2) +r, l, serror = p:match("bac") +assert(r == nil and l == 2 and serror == "bac") r, l, serror = p:match("cab") -assert(r == nil and l == 0 and serror == "cab") +assert(r == nil and l == 2 and serror == "cab") +r, l, serror = p:match("dab") +assert(r == nil and l == 2 and serror == "dab") + + -- tests related to predicates p = #m.T(1) + m.P"a" @@ -84,74 +142,66 @@ p = ##m.T(1) + m.P"a" r, l, serror = p:match("abc") assert(r == nil and l == 1 and serror == "abc") -p = #m.T(0) * m.P"a" -assert(p:match("abc") == fail) - -p = #m.T(0) + m.P"a" -assert(p:match("abc") == 2) - p = -m.T(1) * m.P"a" r, l, serror = p:match("abc") assert(r == nil and l == 1 and serror == "abc") +p = -m.T(1) * m.P"a" +r, l, serror = p:match("bbc") +assert(r == nil and l == 1 and serror == "bbc") + p = -(-m.T(1)) * m.P"a" r, l, serror = p:match("abc") assert(r == nil and l == 1 and serror == "abc") -p = -m.T(0) * m.P"a" -assert(p:match("abc") == 2) +p = m.Rec(-m.T(22), m.P"a", 22) +r, l, serror = p:match("abc") +assert(r == nil and l == 0 and serror == "bc") -p = -m.T(0) + m.P"a" -assert(p:match("abc") == 1) +assert(p:match("bbc") == 1) -p = -(-m.T(0)) + m.P"a" -assert(p:match("abc") == 2) +p = m.Rec(#m.T(22), m.P"a", 22) +assert(p:match("abc") == 1) -p = m.Lc(-m.T(22), m.P"a", 22) -assert(p:match("abc") == 2) +p = #m.Rec(m.T(22), m.P"a", 22) +assert(p:match("abc") == 1) -p = m.Lc(-m.T(0), m.P"a", 0) +p = m.Rec(m.T(22), #m.P"a", 22) assert(p:match("abc") == 1) -p = m.Lc(#m.T(22), m.P"a", 22) -assert(p:match("abc") == 2) +p = m.Rec(#m.T(22), m.P"a", 22) +r, l, serror = p:match("bbc") +assert(r == nil and l == 0 and serror == "bbc") -p = m.Lc(#m.T(0), m.P"a", 0) -assert(p:match("abc") == 2) -- tests related to repetition p = m.T(1)^0 r, l, serror = p:match("ab") assert(r == nil and l == 1 and serror == "ab") -p = m.T(0)^0 -assert(p:match("ab") == 1) - p = (m.P"a" + m.T(1))^0 r, l, serror = p:match("aa") assert(r == nil and l == 1 and serror == "") -p = (m.P"a" + m.T(0))^0 -assert(p:match("aa") == 3) -- Bug reported by Matthew Allen -- some optmizations performed by LPeg should not be -- applied in case of labeled choices -p = m.Lc(m.P"A", m.P(true), 1) + m.P("B") +p = m.Rec(m.P"A", m.P(true), 1) + m.P("B") assert(p:match("B") == 2) -p = m.Lc(m.P"A", m.P(false), 1) + m.P("B") +p = m.Rec(m.P"A", m.P(false), 1) + m.P("B") assert(p:match("B") == 2) --[[ -S -> A /{1} 'a' +S -> A //{1} 'a' A -> B B -> %1 ]] g = m.P{ "S", - S = m.Lc(m.V"A", m.P"a", 1), + S = m.Rec(m.V"A", m.P"a", 1), A = m.V"B", B = m.T(1), } @@ -177,66 +227,96 @@ r, l, serror = g:match("a;a") assert(r == nil and l == 1 and serror == "") --- %1 /{1,3} %2 /{2} 'a' -p = m.Lc(m.Lc(m.T(1), m.T(2), 1, 3), m.P"a", 2) +-- %1 //{1,3} %2 //{2} 'a' +p = m.Rec(m.Rec(m.T(1), m.T(2), 1, 3), m.P"a", 2) assert(p:match("abc") == 2) r, l, serror = p:match("") assert(r == nil and l == 0 and serror == "") -p = m.Lc(m.T(1), m.Lc(m.T(2), m.P"a", 2), 1, 3) +p = m.Rec(m.T(1), m.Rec(m.T(2), m.P"a", 2), 1, 3) assert(p:match("abc") == 2) r, l, serror = p:match("") assert(r == nil and l == 0 and serror == "") -]==] + + +-- Infinte Loop TODO: check the semantics +-- %1 //{1} %1 +p = m.Rec(m.T(1), m.T(1), 1) +--r, l, serror = p:match("ab") +--assert(r == nil and l == 1 and serror == "ab") + +-- %1 //{1} 'a' (!. / %1) +p = m.Rec(m.T(1), m.P"a" * (-m.P(1) + m.T(1)), 1) +r, l, serror = p:match("ab") +assert(r == nil and l == 0 and serror == "b") + +r, l, serror = p:match("cd") +assert(r == nil and l == 0 and serror == "cd") + +-- %1 //{1} . (!. / %1) +p = m.Rec(m.T(1), m.P(1) * (-m.P(1) + m.T(1)), 1) +assert(p:match("abc") == 4) + -- testing the limit of labels -p = m.T(0) -s = "abc" -r, l, serror = p:match(s) -assert(r == nil and l == 0 and serror == "abc") +-- can only throw labels between 1 and 255 +local r = pcall(m.Rec, m.P"b", m.P"a", 0) +assert(r == false) -p = m.T(255) -s = "abc" -r, l, serror = p:match(s) -assert(r == nil and l == 255 and serror == "abc") +local r = pcall(m.Rec, m.P"b", m.P"a", 256) +assert(r == false) -local r = pcall(m.T, -1) +local r = pcall(m.Rec, m.P"b", m.P"a", -1) +assert(r == false) + +local r = pcall(m.T, 0) assert(r == false) local r = pcall(m.T, 256) assert(r == false) +local r = pcall(m.T, -1) +assert(r == false) + + +local r = m.Rec(m.P"b", m.P"a", 255) +assert(p:match("a") == 2) + +p = m.T(255) +s = "abc" +r, l, serror = p:match(s) +assert(r == nil and l == 255 and serror == "abc") + + print("+") ---[==[ TODO: labeled choice does not work anymore --[[ grammar based on Figure 8 of paper submitted to SCP -S -> S0 /{1} ID /{2} ID '=' Exp /{3} 'unsigned'* 'int' ID /{4} 'unsigned'* ID ID / %error -S0 -> ID S1 / 'unsigned' S2 / 'int' %3 -S1 -> '=' %2 / !. %1 / ID %4 -S2 -> 'unsigned' S2 / ID %4 / 'int' %3 +S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error +S0 -> S1 / S2 / &'int' %3 +S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4 +S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3 ]] - local sp = m.S" \t\n"^0 local eq = sp * m.P"=" g = m.P{ "S", - S = m.Lc( - m.Lc( - m.Lc( - m.Lc(m.V"S0", m.V"ID" * (m.P(1) + ""), 1), + S = m.Rec( + m.Rec( + m.Rec( + m.Rec(m.V"S0", m.V"ID", 1), m.V"ID" * eq * m.V"Exp", 2 ), m.V"U"^0 * m.V"I" * m.V"ID", 3 ), m.V"U"^0 * m.V"ID" * m.V"ID", 4) + m.T(5), -- error - S0 = m.V"ID" * m.V"S1" + m.V"U" * m.V"S2" + m.V"I" * m.T(3), - S1 = eq * m.T(2) + sp * -m.P(1) * m.T(1) + m.V"ID" * m.T(4), - S2 = m.V"U" * m.V"S2" + m.V"ID" * m.T(4) + m.V"I" * m.T(3), + S0 = m.V"S1" + m.V"S2" + #m.V"I" * m.T(3), + S1 = #(m.V"ID" * eq) * m.T(2) + sp * #(m.V"ID" * -m.P(1)) * m.T(1) + #m.V"ID" * m.T(4), + S2 = #(m.V"U"^1 * m.V"ID") * m.T(4) + #(m.V"U"^1 * m.V"I") * m.T(3), ID = sp * m.P"a", U = sp * m.P"unsigned", I = sp * m.P"int", @@ -273,53 +353,59 @@ assert(r == nil and l == 5 and serror == s) print("+") -local re = require 'relabel' -g = re.compile[['a' /{4,9} [a-z] +local re = require 'relabelrec' + +g = re.compile[['a' //{4,9} [a-z] ]] assert(g:match("a") == 2) r, l, serror = g:match("b") assert(r == nil and l == 0 and serror == "b") -g = re.compile[['a' /{4,9} [a-f] /{5, 7} [a-z] +g = re.compile[['a' //{4,9} [a-f] //{5, 7} [a-z] ]] assert(g:match("a") == 2) r, l, serror = g:match("b") assert(r == nil and l == 0 and serror == "b") -g = re.compile[[%{1} /{4,9} [a-z] +g = re.compile[[%{1} //{4,9} [a-z] ]] r, l, serror = g:match("a") assert(r == nil and l == 1 and serror == "a") -g = re.compile[[%{1} /{4,1} [a-f] +g = re.compile[[%{1} //{4,1} [a-f] ]] assert(g:match("a") == 2) r, l, serror = g:match("h") assert(r == nil and l == 0 and serror == "h") -g = re.compile[[[a-f]%{9} /{4,9} [a-c]%{7} /{5, 7} [a-z] ]] -assert(g:match("a") == 2) -assert(g:match("c") == 2) -r, l, serror = g:match("d") +g = re.compile[[[a-f]%{9} //{4,9} [a-c]%{7} //{5, 7} [a-z] ]] +r, l, serror = g:match("a") +assert(r == nil and l == 0 and serror == "") +r, l, serror = g:match("aa") +assert(r == nil and l == 0 and serror == "") +assert(g:match("aaa") == 4) + +r, l, serror = g:match("ad") assert(r == nil and l == 0 and serror == "d") + r, l, serror = g:match("g") assert(r == nil and l == 0 and serror == "g") + --[[ grammar based on Figure 8 of paper submitted to SCP -S -> S0 /{1} ID /{2} ID '=' Exp /{3} 'unsigned'* 'int' ID /{4} 'unsigned'* ID ID / %error -S0 -> ID S1 / 'unsigned' S2 / 'int' %3 -S1 -> '=' %2 / !. %1 / ID %4 -S2 -> 'unsigned' S2 / ID %4 / 'int' %3 +S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error +S0 -> S1 / S2 / &'int' %3 +S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4 +S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3 ]] - g = re.compile([[ - S <- S0 /{1} ID /{2} ID %s* '=' Exp /{3} U* Int ID /{4} U ID ID /{0} %{5} - S0 <- ID S1 / U S2 / Int %{3} - S1 <- %s* '=' %{2} / !. %{1} / ID %{4} - S2 <- U S2 / ID %{4} / Int %{3} + S <- S0 //{1} ID //{2} ID %s* '=' Exp //{3} U* Int ID //{4} U ID ID / %{5} + S0 <- S1 / S2 / &Int %{3} + S1 <- &(ID %s* '=') %{2} / &(ID !.) %{1} / &ID %{4} + S2 <- &(U+ ID) %{4} / &(U+ Int) %{3} ID <- %s* 'a' U <- %s* 'unsigned' Int <- %s* 'int' @@ -349,6 +435,8 @@ s = "unsigned int" r, l, serror = g:match(s) assert(r == nil and l == 5 and serror == s) + + local terror = { ['cmdSeq'] = "Missing ';' in CmdSeq", ['ifExp'] = "Error in expresion of 'if'", ['ifThen'] = "Error matching 'then' keyword", @@ -370,12 +458,12 @@ local terror = { ['cmdSeq'] = "Missing ';' in CmdSeq", ['undefined'] = "Undefined Error"} g = re.compile([[ - Tiny <- CmdSeq /{1} '' -> cmdSeq /{2} '' -> ifExp /{3} '' -> ifThen /{4} '' -> ifThenCmdSeq - /{5} '' -> ifElseCmdSeq /{6} '' -> ifEnd /{7} '' -> repeatCmdSeq - /{8} '' -> repeatUntil /{9} '' -> repeatExp /{10} '' -> assignOp - /{11} '' -> assignExp /{12} '' -> readName /{13} '' -> writeExp - /{14} '' -> simpleExp /{15} '' -> term /{16} '' -> factor - /{17} '' -> openParExp /{18} '' -> closePar /{0} '' -> undefined + Tiny <- CmdSeq //{1} '' -> cmdSeq //{2} '' -> ifExp //{3} '' -> ifThen //{4} '' -> ifThenCmdSeq + //{5} '' -> ifElseCmdSeq //{6} '' -> ifEnd //{7} '' -> repeatCmdSeq + //{8} '' -> repeatUntil //{9} '' -> repeatExp //{10} '' -> assignOp + //{11} '' -> assignExp //{12} '' -> readName //{13} '' -> writeExp + //{14} '' -> simpleExp //{15} '' -> term //{16} '' -> factor + //{17} '' -> openParExp //{18} '' -> closePar / '' -> undefined CmdSeq <- (Cmd (SEMICOLON / %{1})) (Cmd (SEMICOLON / %{1}))* Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd IfCmd <- IF (Exp / %{2}) (THEN / %{3}) (CmdSeq / %{4}) (ELSE (CmdSeq / %{5}) / '') (END / %{6}) @@ -551,21 +639,14 @@ assert(g:match(s) == terror['undefined']) print("+") -]==] --- test recovery operator -p = m.Rec("a", "b") -assert(p:match("a") == 2) ---assert(p:match("b") == 2) -checkeqlab({nil, 0, "b"}, p:match("b")) -checkeqlab({nil, 0, "c"}, p:match("c")) p = m.Rec("a", "b", 3) assert(p:match("a") == 2) checkeqlab({nil, 0, "b"}, p:match("b")) checkeqlab({nil, 0, "c"}, p:match("c")) -p = m.Rec(m.T(3), "b") +p = m.Rec(m.T(3), "b", 1) checkeqlab({nil, 3, "a"}, p:match("a")) checkeqlab({nil, 3, "b"}, p:match("b")) @@ -574,56 +655,26 @@ checkeqlab({nil, 0, "a"}, p:match("a")) assert(p:match("b") == 2) --[[ -S -> (A //{fail} (!c .)*) C -A -> a*b -C -> c+ -]] -g = m.P{ - "S", - S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0) * m.V"C", - A = m.P"a"^0 * "b", - C = m.P"c"^1, -} - -assert(g:match("abc") == 4) -assert(g:match("aabc") == 5) ---assert(g:match("aadc") == 5) --old semantics -checkeqlab({nil, 0, "dc"}, g:match("aadc")) --new semantics -assert(g:match("bc") == 3) -- new semantics -checkeqlab({nil, 0, "bc"}, g:match("bbc")) ---assert(g:match("xxc") == 4) old semantics -checkeqlab({nil, 0, "xxc"}, g:match("xxc")) --new semantics ---assert(g:match("c") == 2) --old semantics -checkeqlab({nil, 0, "c"}, g:match("c")) --new semantics ---checkeqlab({nil, 0, ""}, g:match("fail")) --old semantics -checkeqlab({nil, 0, "fail"}, g:match("fail")) --new semantics ---checkeqlab({nil, 0, ""}, g:match("aaxx")) --old semantics -checkeqlab({nil, 0, "xx"}, g:match("aaxx")) --new semantics - - ---[[ -S -> (A //{0} (!c .)*) C -A -> a*b / ^{0} +S -> (A //{128} (!c .)*) C +A -> a*b / %128 C -> c+ ]] g = m.P{ "S", - S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0, 0) * m.V"C", --explicitly put 0 in Rec - A = m.P"a"^0 * m.P"b" + m.T(0), + S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0, 128) * m.V"C", + A = m.P"a"^0 * "b" + m.T(128), C = m.P"c"^1, } assert(g:match("abc") == 4) assert(g:match("aabc") == 5) -assert(g:match("aadc") == 5) --updated -assert(g:match("bc") == 3) -- new semantics +assert(g:match("aadc") == 5) +assert(g:match("dc") == 3) checkeqlab({nil, 0, "bc"}, g:match("bbc")) assert(g:match("xxc") == 4) -assert(g:match("c") == 2) --old semantics updated -checkeqlab({nil, 0, ""}, g:match("fail")) --old semantics updated -checkeqlab({nil, 0, ""}, g:match("aaxx")) --old semantics updated - - +assert(g:match("c") == 2) +checkeqlab({nil, 0, ""}, g:match("fail")) +checkeqlab({nil, 0, ""}, g:match("aaxx")) --[[ @@ -810,6 +861,120 @@ checkeqlab({nil, 4, "dc"}, g3:match("dc")) checkeqlab({nil, 4, "d"}, g3:match(".d")) +-- testing more captures +local g = re.compile[[ + S <- ( %s* &. {A} )* + A <- [0-9]+ / %{5} +]] -print("OK") +checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")}) +checkeq({nil, 5, "a 123"}, {g:match("44 a 123")}) + +local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5) + +checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")}) +checkeq({"44", "a ", "58", "123"}, {g2:match("44 a 123")}) + + +local g = re.compile[[ + S <- ( %s* &. A )* + A <- {[0-9]+} / %{5} +]] + +checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")}) +checkeq({nil, 5, "a 123"}, {g:match("44 a 123")}) + +local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5) + +checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")}) +checkeq({"44", "58", "123"}, {g2:match("44 a 123")}) + + +local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V +local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt +local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec + +local labels = { + {"NoExp", "no expression found"}, + {"Extra", "extra characters found after the expression"}, + {"ExpTerm", "expected a term after the operator"}, + {"ExpExp", "expected an expression after the parenthesis"}, + {"MisClose", "missing a closing ')' after the expression"}, +} + +local function labelindex(labname) + for i, elem in ipairs(labels) do + if elem[1] == labname then + return i + end + end + error("could not find label: " .. labname) +end + +local errors = {} + +local function expect(patt, labname, recpatt) + local i = labelindex(labname) + function recorderror(input, pos) + table.insert(errors, {i, pos}) + return true + end + if not recpatt then recpatt = P"" end + --return Rec(patt, Cmt("", recorderror) * recpatt) + return patt + T(i) +end + +local num = R("09")^1 / tonumber +local op = S("+-*/") + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + elseif tokens[i] == '*' then + result = result * tokens[i+1] + elseif tokens[i] == '/' then + result = result / tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + +local g = P { +"Exp", +Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute, +Operand = expect(V"Term", "ExpTerm"), +Term = num, +} +local rg = Rec(g, Cc(3), labelindex("ExpTerm")) + +local function eval(input) + local result, label, suffix = rg:match(input) + if #errors == 0 then + return result + else + local out = {} + for i, err in ipairs(errors) do + local pos = err[2] + local msg = labels[err[1]][2] + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + errors = {} + return nil, table.concat(out, "\n") + end +end + +assert(eval("98-76*54/32") == 37.125) +--> 37.125 + +assert(eval("1+") == 4) +--> syntax error: expected a term after the operator (at index 3) + + +print("OK") -- cgit v1.2.3-55-g6feb From 448762908fd822fbc101a4fe66fac9cd8aa913b5 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Mon, 14 Nov 2016 17:15:27 -0300 Subject: Changing documentation and examples with recovery --- README.md | 296 ++++++++++++++++++++++++++++++++++++++--------- examples/listId1.lua | 12 +- examples/listId2.lua | 13 ++- examples/listId2Rec2.lua | 67 +++++++++++ lptree.c | 2 +- lpvm.c | 8 +- 6 files changed, 329 insertions(+), 69 deletions(-) create mode 100644 examples/listId2Rec2.lua diff --git a/README.md b/README.md index b882968..fe7c14d 100644 --- a/README.md +++ b/README.md @@ -10,43 +10,47 @@ LPegLabel is a conservative extension of the [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg) library that provides an implementation of Parsing Expression Grammars (PEGs) with labeled failures. -Labels can be used to signal different kinds of erros -and to specify which alternative in a labeled ordered -choice should handle a given label. Labels can also be -combined with the standard patterns of LPeg. +Labels can be used to signal different kinds of errors +and to specify which recovery pattern should handle a +given label. Labels can also be combined with the standard +patterns of LPeg. This document describes the new functions available in LpegLabel and presents some examples of usage. -For a more detailed discussion about PEGs with labeled failures -please see [A Parsing Machine for Parsing Expression -Grammars with Labeled Failures](https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxzcW1lZGVpcm9zfGd4OjMzZmE3YzM0Y2E2MGM5Y2M). - In LPegLabel, the result of an unsuccessful matching is a triple **nil, lab, sfail**, where **lab** is the label associated with the failure, and **sfail** is the suffix input being matched when -**lab** was thrown. Below there is a brief summary +**lab** was thrown. + +With labeled failures it is possible to distinguish +between a regular failure and an error. Usually, a +regular failure is produced when the matching of a +character fails, and it is caught by an ordered choice. +An error, by its turn, is produced by the throw operator +and may be caught by the recovery operator. + +Below there is a brief summary of the new functions provided by LpegLabel: - - - - + + + + - - - + - + - +
FunctionDescription
lpeglabel.T (l)Throws label l
lpeglabel.Lc (p1, p2, l1, ..., ln)Matches p1 and tries to match p2 - if the matching of p1 gives one of l1, ..., ln -
lpeglabelrec.T (l)Throws a label l to signal an error
lpeglabelrec.Rec (p1, p2, l1, [l2, ..., ln])Specifies a recovery pattern p2 for p1, + when the matching of p1 gives one of the labels l1, ..., ln.
%{l}Syntax of relabel module. Equivalent to lpeg.T(l) + Syntax of relabelrec module. Equivalent to lpeglabelrec.T(l)
p1 /{l1, ..., ln} p2Syntax of relabel module. Equivalent to lpeg.Lc(p1, p2, l1, ..., ln) +
p1 //{l1, ..., ln} p2Syntax of relabelrec module. Equivalent to lpeglabelrec.Rec(p1, p2, l1, ..., ln)
relabel.calcline(subject, i)
relabelrec.calcline(subject, i) Calculates line and column information regarding position i of the subject
relabel.setlabels (tlabel)
relabelrec.setlabels (tlabel) Allows to specicify a table with mnemonic labels.
@@ -55,55 +59,44 @@ of the new functions provided by LpegLabel: ### Functions -#### lpeglabel.T(l) +#### lpeglabelrec.T(l) Returns a pattern that throws the label `l`. -A label must be an integer between 0 and 255. - -The label 0 is equivalent to the regular failure of PEGs. +A label must be an integer between 1 and 255. -#### lpeglabel.Lc(p1, p2, l1, ..., ln)# +#### lpeglabelrec.Rec(p1, p2, l1, ..., ln) -Returns a pattern equivalent to a *labeled ordered choice*. +Returns a *recovery pattern*. If the matching of `p1` gives one of the labels `l1, ..., ln`, -then the matching of `p2` is tried from the same position. Otherwise, -the result of the matching of `p1` is the pattern's result. +then the matching of `p2` is tried from the failure position of `p1`. +Otherwise, the result of the matching of `p1` is the pattern's result. -The labeled ordered choice `lpeg.Lc(p1, p2, 0)` is equivalent to the -regular ordered choice `p1 / p2`. - -Although PEG's ordered choice is associative, the labeled ordered choice is not. -When using this function, the user should take care to build a left-associative -labeled ordered choice pattern. #### %{l} -Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. +Syntax of *relabelrec* module. Equivalent to `lpeg.T(l)`. -#### p1 /{l1, ..., ln} p2 +#### p1 //{l1, ..., ln} p2 -Syntax of *relabel* module. Equivalent to `lpeg.Lc(p1, p2, l1, ..., ln)`. +Syntax of *relabelrec* module. Equivalent to `lpeglabelrec.Rec(p1, p2, l1, ..., ln)`. -The `/{}` operator is left-associative. +The `//{}` operator is left-associative. -A grammar can use both choice operators (`/` and `/{}`), -but a single choice can not mix them. That is, the parser of `relabel` -module will not recognize a pattern as `p1 / p2 /{l1} p3`. -#### relabel.calcline (subject, i) +#### relabelrec.calcline (subject, i) Returns line and column information regarding position i of the subject. -#### relabel.setlabels (tlabel) +#### relabelrec.setlabels (tlabel) Allows to specicify a table with labels. They keys of -`tlabel` must be integers between 0 and 255, +`tlabel` must be integers between 1 and 255, and the associated values should be strings. @@ -122,8 +115,8 @@ is thrown when there is an error matching an identifier or a comma: ```lua -local m = require'lpeglabel' -local re = require'relabel' +local m = require'lpeglabelrec' +local re = require'relabelrec' local g = m.P{ "S", @@ -160,7 +153,7 @@ In this example we could think about writing rule List as follows: List = ((m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)))^0, ``` -but when matching this expression agains the end of input +but when matching this expression against the end of input we would get a failure whose associated label would be **2**, and this would cause the failure of the *whole* repetition. @@ -168,12 +161,12 @@ and this would cause the failure of the *whole* repetition. ##### Mnemonics instead of numbers In the previous example we could have created a table -with the error messages to improve the readbility of the PEG. +with the error messages to improve the readability of the PEG. Below we rewrite the previous grammar following this approach: ```lua -local m = require'lpeglabel' -local re = require'relabel' +local m = require'lpeglabelrec' +local re = require'relabelrec' local terror = {} @@ -210,14 +203,99 @@ print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expec print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' ``` +#### Error Recovery + +By using the recovery operator we can specify a recovery pattern that +should be matched when a label is thrown. After matching this pattern, +and possibly recording the error, the parser can continue parsing to +find more errors. + +Below we rewrite the previous example to illustrate a recovery strategy. +Grammar `g` remains the same, but we add a recovery grammar `grec` that +handles the labels thrown by `g`. + +arithmetic expression example and modify +the `expect` function to use the recovery operator for error recovery: + +```lua +local m = require'lpeglabelrec' +local re = require'relabelrec' + +local terror = {} + +local function newError(s) + table.insert(terror, s) + return #terror +end + +local errUndef = newError("undefined") +local errId = newError("expecting an identifier") +local errComma = newError("expecting ','") + +local id = m.R'az'^1 + +local g = m.P{ + "S", + S = m.V"Id" * m.V"List", + List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", + Id = m.V"Sp" * id + m.T(errId), + Comma = m.V"Sp" * "," + m.T(errComma), + Sp = m.S" \n\t"^0, +} + +local subject, errors + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = terror[lab] }) +end + +function record (lab) + return (m.Cp() * m.Cc(lab)) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +local grec = m.P{ + "S", + S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), + ErrComma = record(errComma) * sync(-m.P(1) + id), + ErrId = record(errId) * sync(-m.P(1) + ",") +} + + +function mymatch (g, s) + errors = {} + subject = s + local r, e, sfail = g:match(s) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg + table.insert(out, msg) + end + return nil, table.concat(out, "\n") + end + return r +end + +print(mymatch(grec, "one,two")) +print(mymatch(grec, "one two three")) +print(mymatch(grec, "1,\n two, \n3,")) +print(mymatch(grec, "one\n two123, \nthree,")) +``` + -##### *relabel* syntax + +##### *relabelrec* syntax Now we rewrite the previous example using the syntax -supported by *relabel*: +supported by *relabelrec*: ```lua -local re = require 'relabel' +local re = require 'relabelrec' local g = re.compile[[ S <- Id List @@ -252,7 +330,7 @@ With the help of function *setlabels* we can also rewrite the previous example t mnemonic labels instead of plain numbers: ```lua -local re = require 'relabel' +local re = require 'relabelrec' local errinfo = { {"errUndef", "undefined"}, @@ -293,6 +371,11 @@ print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expec print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' ``` + + + + + #### Arithmetic Expressions Here's an example of an LPegLabel grammar that make its own function called @@ -420,3 +503,108 @@ print(m.match(g, "one,two")) --> 8 print(m.match(g, "one two")) --> expecting ',' print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier ``` + +#### Error Recovery + +By using labeled ordered choice or the recovery operator, when a label +is thrown, the parser may record the error and still continue parsing +to find more errors. We can even record the error right away without +actually throwing a label (relying on the regular PEG failure instead). +Below we rewrite the arithmetic expression example and modify +the `expect` function to use the recovery operator for error recovery: + +```lua +local lpeg = require"lpeglabel" + +local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V +local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg +local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec + +local labels = { + {"NoExp", "no expression found"}, + {"Extra", "extra characters found after the expression"}, + {"ExpTerm", "expected a term after the operator"}, + {"ExpExp", "expected an expression after the parenthesis"}, + {"MisClose", "missing a closing ')' after the expression"}, +} + +local function labelindex(labname) + for i, elem in ipairs(labels) do + if elem[1] == labname then + return i + end + end + error("could not find label: " .. labname) +end + +local function expect(patt, labname, recpatt) + local i = labelindex(labname) + local function recorderror(input, pos, errors) + table.insert(errors, {i, pos}) + return true + end + if not recpatt then recpatt = P"" end + return Rec(patt, Cmt(Carg(1), recorderror) * recpatt) +end + +local num = R("09")^1 / tonumber +local op = S("+-*/") + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + elseif tokens[i] == '*' then + result = result * tokens[i+1] + elseif tokens[i] == '/' then + result = result / tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + + +local g = P { + "Exp", + Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute; + Operand = expect(V"Term", "ExpTerm", Cc(0)); + Term = num + V"Group"; + Group = "(" * V"InnerExp" * expect(")", "MisClose"); + InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); +} + +g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") + +local function eval(input) + local errors = {} + local result, label, suffix = g:match(input, 1, errors) + if #errors == 0 then + return result + else + local out = {} + for i, err in ipairs(errors) do + local pos = err[2] + local msg = labels[err[1]][2] + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + return nil, table.concat(out, "\n") + end +end + +print(eval "98-76*(54/32)") +--> 37.125 + +print(eval "-1+(1-(1*2))/2") +--> syntax error: no expression found (at index 1) + +print(eval "(1+1-1*(2/2+)-():") +--> syntax error: expected a term after the operator (at index 13) +--> syntax error: expected an expression after the parenthesis (at index 16) +--> syntax error: missing a closing ')' after the expression (at index 17) +--> syntax error: extra characters found after the expression (at index 17) +``` diff --git a/examples/listId1.lua b/examples/listId1.lua index 8976f5f..dee46e9 100644 --- a/examples/listId1.lua +++ b/examples/listId1.lua @@ -1,12 +1,14 @@ -local m = require'lpeglabel' -local re = require'relabel' +local m = require'lpeglabelrec' +local re = require'relabelrec' + +local id = m.R'az'^1 local g = m.P{ "S", S = m.V"Id" * m.V"List", - List = -m.P(1) + (m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)) * m.V"List", - Id = m.V"Sp" * m.R'az'^1, - Comma = m.V"Sp" * ",", + List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", + Id = m.V"Sp" * id + m.T(1), + Comma = m.V"Sp" * "," + m.T(2), Sp = m.S" \n\t"^0, } diff --git a/examples/listId2.lua b/examples/listId2.lua index 509fda4..46f0063 100644 --- a/examples/listId2.lua +++ b/examples/listId2.lua @@ -1,5 +1,5 @@ -local m = require'lpeglabel' -local re = require'relabel' +local m = require'lpeglabelrec' +local re = require'relabelrec' local terror = {} @@ -12,15 +12,18 @@ local errUndef = newError("undefined") local errId = newError("expecting an identifier") local errComma = newError("expecting ','") +local id = m.R'az'^1 + local g = m.P{ "S", S = m.V"Id" * m.V"List", - List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", - Id = m.V"Sp" * m.R'az'^1, - Comma = m.V"Sp" * ",", + List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", + Id = m.V"Sp" * id + m.T(errId), + Comma = m.V"Sp" * "," + m.T(errComma), Sp = m.S" \n\t"^0, } + function mymatch (g, s) local r, e, sfail = g:match(s) if not r then diff --git a/examples/listId2Rec2.lua b/examples/listId2Rec2.lua new file mode 100644 index 0000000..a347a5b --- /dev/null +++ b/examples/listId2Rec2.lua @@ -0,0 +1,67 @@ +local m = require'lpeglabelrec' +local re = require'relabelrec' + +local terror = {} + +local function newError(s) + table.insert(terror, s) + return #terror +end + +local errUndef = newError("undefined") +local errId = newError("expecting an identifier") +local errComma = newError("expecting ','") + +local id = m.R'az'^1 + +local g = m.P{ + "S", + S = m.V"Id" * m.V"List", + List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", + Id = m.V"Sp" * id + m.T(errId), + Comma = m.V"Sp" * "," + m.T(errComma), + Sp = m.S" \n\t"^0, +} + +local subject, errors + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = terror[lab] }) +end + +function record (lab) + return (m.Cp() * m.Cc(lab)) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +local grec = m.P{ + "S", + S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), + ErrComma = record(errComma) * sync(-m.P(1) + id), + ErrId = record(errId) * sync(-m.P(1) + ",") +} + + +function mymatch (g, s) + errors = {} + subject = s + local r, e, sfail = g:match(s) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg + table.insert(out, msg) + end + return nil, table.concat(out, "\n") + end + return r +end + +print(mymatch(grec, "one,two")) +print(mymatch(grec, "one two three")) +print(mymatch(grec, "1,\n two, \n3,")) +print(mymatch(grec, "one\n two123, \nthree,")) diff --git a/lptree.c b/lptree.c index 9861cfe..e15198e 100644 --- a/lptree.c +++ b/lptree.c @@ -28,7 +28,7 @@ const byte numsiblings[] = { 0, 0, 2, 1, /* call, opencall, rule, grammar */ 1, /* behind */ 1, 1, /* capture, runtime capture */ - 0, 2, 2 /* labeled failure throw, labeled choice, recovery */ + 0, 2 /* labeled failure throw, recovery */ }; diff --git a/lpvm.c b/lpvm.c index 122c8f4..a934478 100644 --- a/lpvm.c +++ b/lpvm.c @@ -336,8 +336,8 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, do { /* remove pending calls */ assert(pstack > getstackbase(L, ptop)); auxlab = (--pstack)->ls; - } while (auxlab == NULL || (pstack->p != &giveup && labelf != LFAIL && !testlabel(pstack->ls->cs, *labelf))); - if (pstack->p == &giveup || pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */ + } while (auxlab == NULL || !(pstack->p == &giveup || testlabel(pstack->ls->cs, *labelf))); + if (pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */ stack = pstack; s = stack->s; if (ndyncap > 0) /* is there matchtime captures? */ @@ -349,7 +349,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, stack->s = NULL; stack->p = pk; /* save return address */ stack->ls = NULL; - stack->caplevel = captop; /* TODO: necessary?? */ + stack->caplevel = captop; /* TODO: really necessary?? */ stack++; } p = pstack->p; @@ -366,7 +366,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, res = resdyncaptures(L, fr, s - o, e - o); /* get result */ if (res == -1) { /* fail? */ *labelf = LFAIL; /* labeled failure */ - *sfail = (const char *) s; /* TODO: ??? */ + *sfail = (const char *) s; pk = NULL; goto fail; } -- cgit v1.2.3-55-g6feb From 96284f8b4a6a25efd3c0c5ce9c7595604ba3143f Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Thu, 17 Nov 2016 15:55:24 -0300 Subject: Updating examples using the recovery operator --- examples/listId2Rec2Cap.lua | 79 +++++++++++++++++++++++++++++++++++++++++++++ examples/listIdRe1.lua | 8 ++--- examples/listIdRe2.lua | 50 ++++++++++++++++++++-------- 3 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 examples/listId2Rec2Cap.lua diff --git a/examples/listId2Rec2Cap.lua b/examples/listId2Rec2Cap.lua new file mode 100644 index 0000000..f9fc2bd --- /dev/null +++ b/examples/listId2Rec2Cap.lua @@ -0,0 +1,79 @@ +local m = require'lpeglabelrec' +local re = require'relabelrec' + +local terror = {} + +local function newError(s) + table.insert(terror, s) + return #terror +end + +local errUndef = newError("undefined") +local errId = newError("expecting an identifier") +local errComma = newError("expecting ','") + +local id = m.R'az'^1 + +local g = m.P{ + "S", + S = m.V"Id" * m.V"List", + List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", + Id = m.V"Sp" * m.C(id) + m.T(errId), + Comma = m.V"Sp" * "," + m.T(errComma), + Sp = m.S" \n\t"^0, +} + +local subject, errors + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = terror[lab] }) +end + +function record (lab) + return (m.Cp() * m.Cc(lab)) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue () + return m.Cc"NONE" +end + +local grec = m.P{ + "S", + S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), + ErrComma = record(errComma) * sync(-m.P(1) + id), + ErrId = record(errId) * sync(-m.P(1) + ",") * defaultValue() +} + + +function mymatch (g, s) + errors = {} + subject = s + io.write("Input: ", s, "\n") + local r = { g:match(s) } + io.write("Captures (separated by ';'): ") + for k, v in pairs(r) do + io.write(v .. "; ") + end + io.write("\nSyntactic errors found: " .. #errors) + if #errors > 0 then + io.write("\n") + local out = {} + for i, err in ipairs(errors) do + local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg + table.insert(out, msg) + end + io.write(table.concat(out, "\n")) + end + print("\n") + return r +end + +mymatch(grec, "one,two") +mymatch(grec, "one two three") +mymatch(grec, "1,\n two, \n3,") +mymatch(grec, "one\n two123, \nthree,") diff --git a/examples/listIdRe1.lua b/examples/listIdRe1.lua index d092566..3988a3b 100644 --- a/examples/listIdRe1.lua +++ b/examples/listIdRe1.lua @@ -1,10 +1,10 @@ -local re = require 'relabel' +local re = require 'relabelrec' local g = re.compile[[ S <- Id List - List <- !. / (',' / %{2}) (Id / %{1}) List - Id <- Sp [a-z]+ - Comma <- Sp ',' + List <- !. / Comma Id List + Id <- Sp [a-z]+ / %{2} + Comma <- Sp ',' / %{3} Sp <- %s* ]] diff --git a/examples/listIdRe2.lua b/examples/listIdRe2.lua index fe30535..070bcdb 100644 --- a/examples/listIdRe2.lua +++ b/examples/listIdRe2.lua @@ -1,4 +1,4 @@ -local re = require 'relabel' +local re = require 'relabelrec' local errinfo = { {"errUndef", "undefined"}, @@ -18,23 +18,45 @@ re.setlabels(labels) local g = re.compile[[ S <- Id List - List <- !. / (',' / %{errComma}) (Id / %{errId}) List - Id <- Sp [a-z]+ - Comma <- Sp ',' + List <- !. / Comma Id List + Id <- Sp [a-z]+ / %{errId} + Comma <- Sp ',' / %{errComma} Sp <- %s* ]] +local errors + +function recorderror (subject, pos, label) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = errmsgs[labels[label]] }) + return true +end + +function sync (p) + return '( !(' .. p .. ') .)*' +end + +local grec = re.compile( + "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" .. + "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('!. / [a-z]+') .. "\n" .. + "ErrId <- ('' -> 'errId' => recorderror) (!(!. / ',') .)*" + , {g = g, recorderror = recorderror}) + function mymatch (g, s) - local r, e, sfail = g:match(s) - if not r then - local line, col = re.calcline(s, #s - #sfail) - local msg = "Error at line " .. line .. " (col " .. col .. "): " - return r, msg .. errmsgs[e] .. " before '" .. sfail .. "'" + errors = {} + local r, e, sfail = g:match(s) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg + table.insert(out, msg) + end + return nil, table.concat(out, "\n") end return r end - -print(mymatch(g, "one,two")) -print(mymatch(g, "one two")) -print(mymatch(g, "one,\n two,\nthree,")) - + +print(mymatch(grec, "one,two")) +print(mymatch(grec, "one two three")) +print(mymatch(grec, "1,\n two, \n3,")) +print(mymatch(grec, "one\n two123, \nthree,")) -- cgit v1.2.3-55-g6feb From 5a4165922a1a6e0ed9cdb65446157c453667ed9c Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Fri, 18 Nov 2016 16:42:15 -0300 Subject: Updating examples. Example recoveryFabio is not complete --- examples/listIdCatch.lua | 28 ----------- examples/listIdCatchRe.lua | 34 ------------- examples/recovery.lua | 2 +- examples/recoveryFabio.lua | 119 ++++++++++++++++++++++++++++++++++++++++++++ examples/recoveryOpFail.lua | 2 +- examples/tiny.lua | 84 ++++++++++++++----------------- 6 files changed, 157 insertions(+), 112 deletions(-) delete mode 100644 examples/listIdCatch.lua delete mode 100644 examples/listIdCatchRe.lua create mode 100644 examples/recoveryFabio.lua diff --git a/examples/listIdCatch.lua b/examples/listIdCatch.lua deleted file mode 100644 index 5ad6f2d..0000000 --- a/examples/listIdCatch.lua +++ /dev/null @@ -1,28 +0,0 @@ -local m = require'lpeglabel' - -local terror = {} - -local function newError(s) - table.insert(terror, s) - return #terror -end - -local errUndef = newError("undefined") -local errId = newError("expecting an identifier") -local errComma = newError("expecting ','") - -local g = m.P{ - "S", - S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), - m.V"ErrComma", errComma), - List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", - Id = m.V"Sp" * m.R'az'^1, - Comma = m.V"Sp" * ",", - Sp = m.S" \n\t"^0, - ErrId = m.Cc(errId) / terror, - ErrComma = m.Cc(errComma) / terror -} - -print(m.match(g, "one,two")) -print(m.match(g, "one two")) -print(m.match(g, "one,\n two,\nthree,")) diff --git a/examples/listIdCatchRe.lua b/examples/listIdCatchRe.lua deleted file mode 100644 index 8971191..0000000 --- a/examples/listIdCatchRe.lua +++ /dev/null @@ -1,34 +0,0 @@ -local re = require'relabel' - -local terror = {} - -local function newError(l, msg) - table.insert(terror, { l = l, msg = msg } ) -end - -newError("errId", "Error: expecting an identifier") -newError("errComma", "Error: expecting ','") - -local labelCode = {} -local labelMsg = {} -for k, v in ipairs(terror) do - labelCode[v.l] = k - labelMsg[v.l] = v.msg -end - -re.setlabels(labelCode) - -local p = re.compile([[ - S <- Id List /{errId} ErrId /{errComma} ErrComma - List <- !. / Comma Id List - Id <- [a-z]+ / %{errId} - Comma <- ',' / %{errComma} - ErrId <- '' -> errId - ErrComma <- '' -> errComma -]], labelMsg) - -print(p:match("a,b")) -print(p:match("a b")) -print(p:match(",b")) - - diff --git a/examples/recovery.lua b/examples/recovery.lua index 3272ae7..bb17b54 100644 --- a/examples/recovery.lua +++ b/examples/recovery.lua @@ -1,4 +1,4 @@ -local lpeg = require"lpeglabel" +local lpeg = require"lpeglabelrec" local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt diff --git a/examples/recoveryFabio.lua b/examples/recoveryFabio.lua new file mode 100644 index 0000000..f16c9e7 --- /dev/null +++ b/examples/recoveryFabio.lua @@ -0,0 +1,119 @@ +local lpeg = require"lpeglabelrec" + +local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V +local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt +local T, Rec = lpeg.T, lpeg.Rec + +local labels = { + {"NoExp", "no expression found"}, + {"Extra", "extra characters found after the expression"}, + {"ExpTerm", "expected a term after the operator"}, + {"ExpExp", "expected an expression after the parenthesis"}, + {"MisClose", "missing a closing ')' after the expression"}, +} + +local function labelindex(labname) + for i, elem in ipairs(labels) do + if elem[1] == labname then + return i + end + end + error("could not find label: " .. labname) +end + +local errors = {} + +local function expect(patt, labname, recpatt) + local i = labelindex(labname) + function recorderror(input, pos) + table.insert(errors, {i, pos}) + return true + end + return patt + T(i) +end + +local num = R("09")^1 / tonumber +local op = S("+-") + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + +local g = P { + "Exp", + Exp = Ct(V"Operand" * (C(op) * V"Operand")^0) / compute, + Operand = expect(V"Term", "ExpTerm"), + Term = num + V"Group", + Group = "(" * V"InnerExp" * expect(")", "MisClose", ""); + InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); + +} + +local subject, errors + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = terror[lab] }) +end + +function record (labname) + return (m.Cp() * m.Cc(labelindex(labname))) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue () + return m.Cc"NONE" +end + +local recg = P { + "S", + S = Rec(m.V"A", Cc(0), labelindex("ExpTerm")), -- default value is 0 + A = Rec(m.V"B", Cc(0), labelindex("ExpExp")), + B = Rec(m.V"Sg", Cc(0), labelindex("InnerExp")), + Sg = Rec(g, Cc(0), labelindex("MisClose")), + ErrExpTerm = record(labelindex("ExpTerm")) * sync() * defaultValue() +} + + +local function eval(input) + local result, label, suffix = recg:match(input) + if #errors == 0 then + return result + else + local out = {} + for i, err in ipairs(errors) do + local pos = err[2] + local msg = labels[err[1]][2] + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + errors = {} + return nil, table.concat(out, "\n") + end +end + +print(eval "90-70*5") +--> 20 + +print(eval "2+") +--> 2 + 0 + +print(eval "-2") +--> 0 - 2 + + +print(eval "1+3+-9") +--> 1 + 3 + 0 - 9 + diff --git a/examples/recoveryOpFail.lua b/examples/recoveryOpFail.lua index d65b9e0..bd1beb7 100644 --- a/examples/recoveryOpFail.lua +++ b/examples/recoveryOpFail.lua @@ -1,4 +1,4 @@ -local lpeg = require"lpeglabel" +local lpeg = require"lpeglabelrec" local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt diff --git a/examples/tiny.lua b/examples/tiny.lua index 99c3144..784e031 100644 --- a/examples/tiny.lua +++ b/examples/tiny.lua @@ -1,4 +1,4 @@ -local re = require 'relabel' +local re = require 'relabelrec' local terror = {} @@ -25,21 +25,6 @@ newError("errFactor", "Error: expected '(', ID, or number after '*' or '/'") newError("errExpFac", "Error: expected expression after '('") newError("errClosePar", "Error: expected ')' after expression") -local line - -local function incLine() - line = line + 1 - return true -end - -local function countLine(s, i) - line = 1 - local p = re.compile([[ - S <- (%nl -> incLine / .)* - ]], { incLine = incLine}) - p:match(s:sub(1, i)) - return true -end local labelCode = {} for k, v in ipairs(terror) do @@ -48,7 +33,7 @@ end re.setlabels(labelCode) -local g = re.compile([[ +local g = re.compile[[ Tiny <- CmdSeq CmdSeq <- (Cmd (SEMICOLON / ErrSemi)) (Cmd (SEMICOLON / ErrSemi))* Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd @@ -61,25 +46,24 @@ local g = re.compile([[ SimpleExp <- Term ((ADD / SUB) (Term / ErrTerm))* Term <- Factor ((MUL / DIV) (Factor / ErrFactor))* Factor <- OPENPAR (Exp / ErrExpFac) (CLOSEPAR / ErrClosePar) / NUMBER / NAME - ErrSemi <- ErrCount %{errSemi} - ErrExpIf <- ErrCount %{errExpIf} - ErrThen <- ErrCount %{errThen} - ErrCmdSeq1 <- ErrCount %{errCmdSeq1} - ErrCmdSeq2 <- ErrCount %{errCmdSeq2} - ErrEnd <- ErrCount %{errEnd} - ErrCmdSeqRep <- ErrCount %{errCmdSeqRep} - ErrUntil <- ErrCount %{errUntil} - ErrExpRep <- ErrCount %{errExpRep} - ErrAssignOp <- ErrCount %{errAssignOp} - ErrExpAssign <- ErrCount %{errExpAssign} - ErrReadName <- ErrCount %{errReadName} - ErrWriteExp <- ErrCount %{errWriteExp} - ErrSimpExp <- ErrCount %{errSimpExp} - ErrTerm <- ErrCount %{errTerm} - ErrFactor <- ErrCount %{errFactor} - ErrExpFac <- ErrCount %{errExpFac} - ErrClosePar <- ErrCount %{errClosePar} - ErrCount <- '' => countLine + ErrSemi <- %{errSemi} + ErrExpIf <- %{errExpIf} + ErrThen <- %{errThen} + ErrCmdSeq1 <- %{errCmdSeq1} + ErrCmdSeq2 <- %{errCmdSeq2} + ErrEnd <- %{errEnd} + ErrCmdSeqRep <- %{errCmdSeqRep} + ErrUntil <- %{errUntil} + ErrExpRep <- %{errExpRep} + ErrAssignOp <- %{errAssignOp} + ErrExpAssign <- %{errExpAssign} + ErrReadName <- %{errReadName} + ErrWriteExp <- %{errWriteExp} + ErrSimpExp <- %{errSimpExp} + ErrTerm <- %{errTerm} + ErrFactor <- %{errFactor} + ErrExpFac <- %{errExpFac} + ErrClosePar <- %{errClosePar} ADD <- Sp '+' ASSIGNMENT <- Sp ':=' CLOSEPAR <- Sp ')' @@ -102,12 +86,17 @@ local g = re.compile([[ WRITE <- Sp 'write' RESERVED <- (IF / ELSE / END / READ / REPEAT / THEN / UNTIL / WRITE) ![a-z]+ Sp <- %s* -]], { countLine = countLine }) +]] -local function printError(n, e) - assert(n == nil) - print("Line " .. line .. ": " .. terror[e].msg) +local function mymatch(g, s) + local r, e, sfail = g:match(s) + if not r then + local line, col = re.calcline(s, #s - #sfail) + local msg = "Error at line " .. line .. " (col " .. col .. "): " + return r, msg .. terror[e].msg + end + return r end local s = [[ @@ -118,7 +107,7 @@ repeat n := n - 1 until (n < 1); write f;]] -printError(g:match(s)) +print(mymatch(g, s)) s = [[ n := 5; @@ -128,14 +117,14 @@ repeat n := n - 1; until (n < 1); read ;]] -printError(g:match(s)) +print(mymatch(g, s)) s = [[ if a < 1 then b := 2; else b := 3;]] -printError(g:match(s)) +print(mymatch(g, s)) s = [[ n := 5; @@ -145,7 +134,7 @@ repeat n := n - 1; untill (n < 1); ]] -printError(g:match(s)) +print(mymatch(g, s)) s = [[ n := 5; @@ -155,9 +144,8 @@ repeat n := n - 1; 3 (n < 1); ]] -printError(g:match(s)) - -printError(g:match("a : 2")) -printError(g:match("a := (2")) +print(mymatch(g, s)) +print(mymatch(g, "a : 2")) +print(mymatch(g, "a := (2")) -- cgit v1.2.3-55-g6feb From 3a5f5683bf6d676ea4118c3a8d0fc25de317d91f Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Wed, 23 Nov 2016 15:22:46 -0300 Subject: Updating examples to use the recovery operator --- examples/listId2Rec2.lua | 4 +- examples/listId2Rec2Cap.lua | 4 +- examples/recoveryFabio.lua | 119 ------------------------------------------- examples/recoveryRecCap.lua | 121 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 123 deletions(-) delete mode 100644 examples/recoveryFabio.lua create mode 100644 examples/recoveryRecCap.lua diff --git a/examples/listId2Rec2.lua b/examples/listId2Rec2.lua index a347a5b..ab8f1dd 100644 --- a/examples/listId2Rec2.lua +++ b/examples/listId2Rec2.lua @@ -41,8 +41,8 @@ end local grec = m.P{ "S", S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), - ErrComma = record(errComma) * sync(-m.P(1) + id), - ErrId = record(errId) * sync(-m.P(1) + ",") + ErrComma = record(errComma) * sync(id), + ErrId = record(errId) * sync(m.P",") } diff --git a/examples/listId2Rec2Cap.lua b/examples/listId2Rec2Cap.lua index f9fc2bd..1c22c88 100644 --- a/examples/listId2Rec2Cap.lua +++ b/examples/listId2Rec2Cap.lua @@ -45,8 +45,8 @@ end local grec = m.P{ "S", S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), - ErrComma = record(errComma) * sync(-m.P(1) + id), - ErrId = record(errId) * sync(-m.P(1) + ",") * defaultValue() + ErrComma = record(errComma) * sync(id), + ErrId = record(errId) * sync(m.P",") * defaultValue(), } diff --git a/examples/recoveryFabio.lua b/examples/recoveryFabio.lua deleted file mode 100644 index f16c9e7..0000000 --- a/examples/recoveryFabio.lua +++ /dev/null @@ -1,119 +0,0 @@ -local lpeg = require"lpeglabelrec" - -local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V -local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt -local T, Rec = lpeg.T, lpeg.Rec - -local labels = { - {"NoExp", "no expression found"}, - {"Extra", "extra characters found after the expression"}, - {"ExpTerm", "expected a term after the operator"}, - {"ExpExp", "expected an expression after the parenthesis"}, - {"MisClose", "missing a closing ')' after the expression"}, -} - -local function labelindex(labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return i - end - end - error("could not find label: " .. labname) -end - -local errors = {} - -local function expect(patt, labname, recpatt) - local i = labelindex(labname) - function recorderror(input, pos) - table.insert(errors, {i, pos}) - return true - end - return patt + T(i) -end - -local num = R("09")^1 / tonumber -local op = S("+-") - -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - -local g = P { - "Exp", - Exp = Ct(V"Operand" * (C(op) * V"Operand")^0) / compute, - Operand = expect(V"Term", "ExpTerm"), - Term = num + V"Group", - Group = "(" * V"InnerExp" * expect(")", "MisClose", ""); - InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); - -} - -local subject, errors - -function recorderror(pos, lab) - local line, col = re.calcline(subject, pos) - table.insert(errors, { line = line, col = col, msg = terror[lab] }) -end - -function record (labname) - return (m.Cp() * m.Cc(labelindex(labname))) / recorderror -end - -function sync (p) - return (-p * m.P(1))^0 -end - -function defaultValue () - return m.Cc"NONE" -end - -local recg = P { - "S", - S = Rec(m.V"A", Cc(0), labelindex("ExpTerm")), -- default value is 0 - A = Rec(m.V"B", Cc(0), labelindex("ExpExp")), - B = Rec(m.V"Sg", Cc(0), labelindex("InnerExp")), - Sg = Rec(g, Cc(0), labelindex("MisClose")), - ErrExpTerm = record(labelindex("ExpTerm")) * sync() * defaultValue() -} - - -local function eval(input) - local result, label, suffix = recg:match(input) - if #errors == 0 then - return result - else - local out = {} - for i, err in ipairs(errors) do - local pos = err[2] - local msg = labels[err[1]][2] - table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") - end - errors = {} - return nil, table.concat(out, "\n") - end -end - -print(eval "90-70*5") ---> 20 - -print(eval "2+") ---> 2 + 0 - -print(eval "-2") ---> 0 - 2 - - -print(eval "1+3+-9") ---> 1 + 3 + 0 - 9 - diff --git a/examples/recoveryRecCap.lua b/examples/recoveryRecCap.lua new file mode 100644 index 0000000..c1b3a53 --- /dev/null +++ b/examples/recoveryRecCap.lua @@ -0,0 +1,121 @@ +local m = require"lpeglabelrec" +local re = require"relabelrec" + +local R, S, P, V = m.R, m.S, m.P, m.V +local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt +local T, Rec = m.T, m.Rec + +local labels = { + {"ExpTermFirst", "expected an expression"}, + {"ExpTermOp", "expected a term after the operator"}, + {"MisClose", "missing a closing ')' after the expression"}, +} + +local function labelindex(labname) + for i, elem in ipairs(labels) do + if elem[1] == labname then + return i + end + end + error("could not find label: " .. labname) +end + +local errors, subject + +local function expect(patt, labname, recpatt) + local i = labelindex(labname) + return patt + T(i) +end + + +local num = R("09")^1 / tonumber +local op = S("+-") + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + +local g = P { + "Exp", + Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, + OperandFirst = expect(V"Term", "ExpTermFirst"), + Operand = expect(V"Term", "ExpTermOp"), + Term = num + V"Group", + Group = "(" * V"Exp" * expect(")", "MisClose"), +} + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) +end + +function record (labname) + return (m.Cp() * m.Cc(labelindex(labname))) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue (p) + return p or m.Cc(0) +end + +local recg = P { + "S", + S = Rec(V"A", V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 + A = Rec(V"Sg", V"ErrExpTermOp", labelindex("ExpTermOp")), + Sg = Rec(g, V"ErrMisClose", labelindex("MisClose")), + ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), + ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), + ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), +} + + +local function eval(input) + errors = {} + subject = input + local result, label, suffix = recg:match(input) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local pos = err.col + local msg = err.msg + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + print(table.concat(out, "\n")) + end + return result +end + +print(eval "90-70*5") +--> 20 + +print(eval "2+") +--> 2 + 0 + +print(eval "-2") +--> 0 - 2 + +print(eval "1+3+-9") +--> 1 + 3 + [0] - 9 + +print(eval "1+()3+") +--> 1 + ([0]) [+] 3 + [0] + +print(eval "8-(2+)-5") +--> 8 - (2 + [0]) - 5 + +print(eval "()") + +print(eval "") -- cgit v1.2.3-55-g6feb From e7e17699870f0bd6ba43b4e946297fb581d28b48 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Tue, 29 Nov 2016 18:06:10 -0300 Subject: Updating examples --- examples/expressionRecovery.lua | 127 ++++++++++++++++++++++++++++++++++++++++ examples/recoveryOpFail.lua | 108 ---------------------------------- examples/recoveryOpLab.lua | 107 --------------------------------- examples/recoveryRecCap.lua | 121 -------------------------------------- examples/typedlua/test.lua | 9 ++- examples/typedlua/tllexer.lua | 11 ++-- examples/typedlua/tlparser.lua | 2 +- testlabel.lua | 9 +++ 8 files changed, 148 insertions(+), 346 deletions(-) create mode 100644 examples/expressionRecovery.lua delete mode 100644 examples/recoveryOpFail.lua delete mode 100644 examples/recoveryOpLab.lua delete mode 100644 examples/recoveryRecCap.lua diff --git a/examples/expressionRecovery.lua b/examples/expressionRecovery.lua new file mode 100644 index 0000000..a4a3288 --- /dev/null +++ b/examples/expressionRecovery.lua @@ -0,0 +1,127 @@ +local m = require"lpeglabelrec" +local re = require"relabelrec" + +local R, S, P, V = m.R, m.S, m.P, m.V +local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt +local T, Rec = m.T, m.Rec + +local labels = { + {"ExpTermFirst", "expected an expression"}, + {"ExpTermOp", "expected a term after the operator"}, + {"MisClose", "missing a closing ')' after the expression"}, +} + +local function labelindex(labname) + for i, elem in ipairs(labels) do + if elem[1] == labname then + return i + end + end + error("could not find label: " .. labname) +end + +local errors, subject + +local function expect(patt, labname, recpatt) + local i = labelindex(labname) + return patt + T(i) +end + + +local num = R("09")^1 / tonumber +local op = S("+-") + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + +local g = P { + "Exp", + Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, + OperandFirst = expect(V"Term", "ExpTermFirst"), + Operand = expect(V"Term", "ExpTermOp"), + Term = num + V"Group", + Group = "(" * V"Exp" * expect(")", "MisClose"), +} + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) +end + +function record (labname) + return (m.Cp() * m.Cc(labelindex(labname))) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue (p) + return p or m.Cc(1000) +end + +local recg = P { + "S", + S = Rec(V"A", V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 + A = Rec(V"Sg", V"ErrExpTermOp", labelindex("ExpTermOp")), + Sg = Rec(g, V"ErrMisClose", labelindex("MisClose")), + ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), + ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), + ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), +} + + +local function eval(input) + errors = {} + subject = input + local result, label, suffix = recg:match(input) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local pos = err.col + local msg = err.msg + print("sub", subject) + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + print(table.concat(out, "\n")) + end + return result +end + +print(eval "90-70*5") +--> 20 + +print(eval "2+") +--> 2 + 0 + +print(eval "-2") +--> 0 - 2 + +print(eval "1+3+-9") +--> 1 + 3 + [0] - 9 + +print(eval "1+()3+") +--> 1 + ([0]) [3 +] [0] + +print(eval "8-(2+)-5") +--> 8 - (2 + [0]) - 5 + +print(eval "()") + +print(eval "") + +print(eval "1+()+") + +print(eval "3)") + diff --git a/examples/recoveryOpFail.lua b/examples/recoveryOpFail.lua deleted file mode 100644 index bd1beb7..0000000 --- a/examples/recoveryOpFail.lua +++ /dev/null @@ -1,108 +0,0 @@ -local lpeg = require"lpeglabelrec" - -local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V -local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt -local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec - -local labels = { - {"NoExp", "no expression found"}, - {"Extra", "extra characters found after the expression"}, - {"ExpTerm", "expected a term after the operator"}, - {"ExpExp", "expected an expression after the parenthesis"}, - {"MisClose", "missing a closing ')' after the expression"}, -} - -local function labelindex(labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return i - end - end - error("could not find label: " .. labname) -end - -local errors = {} - -local function expect(patt, labname, recpatt) - local i = labelindex(labname) - function recorderror(input, pos) - table.insert(errors, {i, pos}) - return true - end - if not recpatt then recpatt = P"" end - return Rec(patt, Cmt("", recorderror) * recpatt) -end - -local num = R("09")^1 / tonumber -local op = S("+-*/") - -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - elseif tokens[i] == '*' then - result = result * tokens[i+1] - elseif tokens[i] == '/' then - result = result / tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - - -local g = P { - "Exp", - Exp = Ct(V"Term" * (C(op) * V"OpRecov")^0) / compute; - OpRecov = V"Operand"; - Operand = expect(V"Term", "ExpTerm", Cc(0)); - Term = num + V"Group"; - Group = "(" * V"InnerExp" * expect(")", "MisClose", ""); - InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); -} - -g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") - -local function eval(input) - local result, label, suffix = g:match(input) - if #errors == 0 then - return result - else - local out = {} - for i, err in ipairs(errors) do - local pos = err[2] - local msg = labels[err[1]][2] - table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") - end - errors = {} - return nil, table.concat(out, "\n") - end -end - -print(eval "98-76*(54/32)") ---> 37.125 - -print(eval "(1+1-1*2/2") ---> syntax error: missing a closing ')' after the expression (at index 11) - -print(eval "(1+)-1*(2/2)") ---> syntax error: expected a term after the operator (at index 4) - -print(eval "(1+1)-1*(/2)") ---> syntax error: expected an expression after the parenthesis (at index 10) - -print(eval "1+(1-(1*2))/2x") ---> syntax error: extra chracters found after the expression (at index 14) - -print(eval "-1+(1-(1*2))/2") ---> syntax error: no expression found (at index 1) - -print(eval "(1+1-1*(2/2+)-():") ---> syntax error: expected a term after the operator (at index 13) ---> syntax error: expected an expression after the parenthesis (at index 16) ---> syntax error: missing a closing ')' after the expression (at index 17) ---> syntax error: extra characters found after the expression (at index 17) diff --git a/examples/recoveryOpLab.lua b/examples/recoveryOpLab.lua deleted file mode 100644 index 6697f8b..0000000 --- a/examples/recoveryOpLab.lua +++ /dev/null @@ -1,107 +0,0 @@ -local lpeg = require"lpeglabel" - -local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V -local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt -local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec - -local labels = { - {"NoExp", "no expression found"}, - {"Extra", "extra characters found after the expression"}, - {"ExpTerm", "expected a term after the operator"}, - {"ExpExp", "expected an expression after the parenthesis"}, - {"MisClose", "missing a closing ')' after the expression"}, -} - -local function labelindex(labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return i - end - end - error("could not find label: " .. labname) -end - -local errors = {} - -local function expect(patt, labname) - local i = labelindex(labname) - function recorderror(input, pos) - table.insert(errors, {i, pos}) - return true - end - return patt + Cmt("", recorderror) * T(i) -end - -local num = R("09")^1 / tonumber -local op = S("+-*/") - -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - elseif tokens[i] == '*' then - result = result * tokens[i+1] - elseif tokens[i] == '/' then - result = result / tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - - -local g = P { - "Exp", - Exp = Ct(V"Term" * (C(op) * V"OpRecov")^0) / compute; - OpRecov = Rec(V"Operand", Cc(0), labelindex("ExpTerm")); - Operand = expect(V"Term", "ExpTerm"); - Term = num + Rec(V"Group", P"", labelindex("MisClose")); - Group = "(" * Rec(V"InnerExp", (P(1) - ")")^0 * Cc(0), labelindex("ExpExp")) * expect(")", "MisClose"); - InnerExp = expect(V"Exp", "ExpExp"); -} - -g = expect(g, "NoExp") * expect(-P(1), "Extra") - -local function eval(input) - local result, label, suffix = g:match(input) - if #errors == 0 then - return result - else - local out = {} - for i, err in ipairs(errors) do - local pos = err[2] - local msg = labels[err[1]][2] - table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") - end - errors = {} - return nil, table.concat(out, "\n") - end -end - -print(eval "98-76*(54/32)") ---> 37.125 - -print(eval "(1+1-1*2/2") ---> syntax error: missing a closing ')' after the expression (at index 11) - -print(eval "(1+)-1*(2/2)") ---> syntax error: expected a term after the operator (at index 4) - -print(eval "(1+1)-1*(/2)") ---> syntax error: expected an expression after the parenthesis (at index 10) - -print(eval "1+(1-(1*2))/2x") ---> syntax error: extra chracters found after the expression (at index 14) - -print(eval "-1+(1-(1*2))/2") ---> syntax error: no expression found (at index 1) - -print(eval "(1+1-1*(2/2+)-():") ---> syntax error: expected a term after the operator (at index 13) ---> syntax error: expected an expression after the parenthesis (at index 16) ---> syntax error: missing a closing ')' after the expression (at index 17) ---> syntax error: extra characters found after the expression (at index 17) diff --git a/examples/recoveryRecCap.lua b/examples/recoveryRecCap.lua deleted file mode 100644 index c1b3a53..0000000 --- a/examples/recoveryRecCap.lua +++ /dev/null @@ -1,121 +0,0 @@ -local m = require"lpeglabelrec" -local re = require"relabelrec" - -local R, S, P, V = m.R, m.S, m.P, m.V -local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt -local T, Rec = m.T, m.Rec - -local labels = { - {"ExpTermFirst", "expected an expression"}, - {"ExpTermOp", "expected a term after the operator"}, - {"MisClose", "missing a closing ')' after the expression"}, -} - -local function labelindex(labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return i - end - end - error("could not find label: " .. labname) -end - -local errors, subject - -local function expect(patt, labname, recpatt) - local i = labelindex(labname) - return patt + T(i) -end - - -local num = R("09")^1 / tonumber -local op = S("+-") - -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - -local g = P { - "Exp", - Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, - OperandFirst = expect(V"Term", "ExpTermFirst"), - Operand = expect(V"Term", "ExpTermOp"), - Term = num + V"Group", - Group = "(" * V"Exp" * expect(")", "MisClose"), -} - -function recorderror(pos, lab) - local line, col = re.calcline(subject, pos) - table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) -end - -function record (labname) - return (m.Cp() * m.Cc(labelindex(labname))) / recorderror -end - -function sync (p) - return (-p * m.P(1))^0 -end - -function defaultValue (p) - return p or m.Cc(0) -end - -local recg = P { - "S", - S = Rec(V"A", V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 - A = Rec(V"Sg", V"ErrExpTermOp", labelindex("ExpTermOp")), - Sg = Rec(g, V"ErrMisClose", labelindex("MisClose")), - ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), - ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), - ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), -} - - -local function eval(input) - errors = {} - subject = input - local result, label, suffix = recg:match(input) - if #errors > 0 then - local out = {} - for i, err in ipairs(errors) do - local pos = err.col - local msg = err.msg - table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") - end - print(table.concat(out, "\n")) - end - return result -end - -print(eval "90-70*5") ---> 20 - -print(eval "2+") ---> 2 + 0 - -print(eval "-2") ---> 0 - 2 - -print(eval "1+3+-9") ---> 1 + 3 + [0] - 9 - -print(eval "1+()3+") ---> 1 + ([0]) [+] 3 + [0] - -print(eval "8-(2+)-5") ---> 8 - (2 + [0]) - 5 - -print(eval "()") - -print(eval "") diff --git a/examples/typedlua/test.lua b/examples/typedlua/test.lua index ed4e7a1..95474ba 100755 --- a/examples/typedlua/test.lua +++ b/examples/typedlua/test.lua @@ -401,15 +401,18 @@ assert(m == e) -- unfinished comments s = [=[ ---[[ testing +--[[ + +testing unfinished + comment -]=] + ]=] --[=[ test.lua:3:1: syntax error, unexpected 'comment', expecting '=', ',', 'String', '{', '(', ':', '[', '.' ]=] e = [=[ -test.lua:1:1: unfinished long comment +test.lua:1:2: unfinished long comment ]=] r, m = parse(s) diff --git a/examples/typedlua/tllexer.lua b/examples/typedlua/tllexer.lua index 6517ba5..d6033ec 100644 --- a/examples/typedlua/tllexer.lua +++ b/examples/typedlua/tllexer.lua @@ -1,6 +1,6 @@ local tllexer = {} -local lpeg = require "lpeglabel" +local lpeg = require "lpeglabelrec" lpeg.locale(lpeg) local tlerror = require "tlerror" @@ -9,10 +9,6 @@ function tllexer.try (pat, label) return pat + lpeg.T(tlerror.labels[label]) end -function tllexer.catch (pat, label) - return lpeg.Lc(pat, lpeg.P(false), tlerror.labels[label]) -end - local function setffp (s, i, t, n) if not t.ffp or i > t.ffp then t.ffp = i @@ -45,7 +41,10 @@ local CloseEQ = lpeg.Cmt(Close * lpeg.Cb("init"), local LongString = Open * (lpeg.P(1) - CloseEQ)^0 * tllexer.try(Close, "LongString") / function (s, o) return s end -local Comment = lpeg.Lc(lpeg.P("--") * LongString / function () return end, +local LongStringCm1 = Open * (lpeg.P(1) - CloseEQ)^0 * Close / + function (s, o) return s end + +local Comment = lpeg.Rec(lpeg.P"--" * #Open * (LongStringCm1 / function() return end + lpeg.T(tlerror.labels["LongString"])), lpeg.T(tlerror.labels["LongComment"]), tlerror.labels["LongString"]) + lpeg.P("--") * (lpeg.P(1) - lpeg.P("\n"))^0 diff --git a/examples/typedlua/tlparser.lua b/examples/typedlua/tlparser.lua index a301fa6..fe4fd5e 100644 --- a/examples/typedlua/tlparser.lua +++ b/examples/typedlua/tlparser.lua @@ -1,6 +1,6 @@ local tlparser = {} -local lpeg = require "lpeglabel" +local lpeg = require "lpeglabelrec" lpeg.locale(lpeg) local tllexer = require "tllexer" diff --git a/testlabel.lua b/testlabel.lua index cb92657..d9bac64 100644 --- a/testlabel.lua +++ b/testlabel.lua @@ -173,6 +173,15 @@ p = m.Rec(#m.T(22), m.P"a", 22) r, l, serror = p:match("bbc") assert(r == nil and l == 0 and serror == "bbc") +p = m.Rec(#m.P("a") * m.T(22), m.T(15), 22) +r, l, serror = p:match("abc") +assert(r == nil and l == 15 and serror == "abc") + +p = m.Rec(#(m.P("a") * m.T(22)), m.T(15), 22) +r, l, serror = p:match("abc") +assert(r == nil and l == 15 and serror == "bc") + + -- tests related to repetition p = m.T(1)^0 -- cgit v1.2.3-55-g6feb From a4bdec34353ad1849f99e6ab0e5f2ad5e74c83b1 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Tue, 29 Nov 2016 18:38:55 -0300 Subject: New example shows how to build recovery grammar semiautomatically --- examples/expressionRecAut.lua | 132 ++++++++++++++++++++++++++++++++++++++++ examples/expressionRecovery.lua | 3 +- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 examples/expressionRecAut.lua diff --git a/examples/expressionRecAut.lua b/examples/expressionRecAut.lua new file mode 100644 index 0000000..e098078 --- /dev/null +++ b/examples/expressionRecAut.lua @@ -0,0 +1,132 @@ +local m = require"lpeglabelrec" +local re = require"relabelrec" + +local R, S, P, V = m.R, m.S, m.P, m.V +local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt +local T, Rec = m.T, m.Rec + +local num = R("09")^1 / tonumber +local op = S("+-") + +local labels = {} +local nlabels = 0 + +local function newError(lab, msg, psync, pcap) + nlabels = nlabels + 1 + psync = psync or m.P(-1) + pcap = pcap or m.P"" + labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap } +end + +newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000)) +newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000)) +newError("MisClose", "missing a closing ')' after the expression", m.P")") +newError("Extra", "extra characters found after the expression") + +local errors, subject + +local function expect(patt, labname) + local i = labels[labname].id + return patt + T(i) +end + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + +local g = P { + "Exp", + Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, + OperandFirst = expect(V"Term", "ExpTermFirst"), + Operand = expect(V"Term", "ExpTermOp"), + Term = num + V"Group", + Group = "(" * V"Exp" * expect(")", "MisClose"), +} + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = labels[lab].msg }) +end + +function record (labname) + return (m.Cp() * m.Cc(labname)) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue (p) + return p or m.Cc(1000) +end + +local recg2 = g +for k, v in pairs(labels) do + recg2 = Rec(recg2, record(k) * sync(v.psync) * v.pcap, v.id) +end + +local recg = P { + "S", + S = Rec(V"A", V"ErrExpTermFirst", labels["ExpTermFirst"].id), -- default value is 0 + A = Rec(V"Sg", V"ErrExpTermOp", labels["ExpTermOp"].id), + Sg = Rec(g, V"ErrMisClose", labels["MisClose"].id), + ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), + ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), + ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), +} + + +local function eval(input) + errors = {} + subject = input + local result, label, suffix = recg2:match(input) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local pos = err.col + local msg = err.msg + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + print(table.concat(out, "\n")) + end + return result +end + +print(eval "90-70*5") +--> 20 + +print(eval "2+") +--> 2 + 0 + +print(eval "-2") +--> 0 - 2 + +print(eval "1+3+-9") +--> 1 + 3 + [0] - 9 + +print(eval "1+()3+") +--> 1 + ([0]) [+] 3 + [0] + +print(eval "8-(2+)-5") +--> 8 - (2 + [0]) - 5 + +print(eval "()") + +print(eval "") + +print(eval "1+()+") + +print(eval "1+(") + +print(eval "3)") + diff --git a/examples/expressionRecovery.lua b/examples/expressionRecovery.lua index a4a3288..c5cbcca 100644 --- a/examples/expressionRecovery.lua +++ b/examples/expressionRecovery.lua @@ -91,7 +91,6 @@ local function eval(input) for i, err in ipairs(errors) do local pos = err.col local msg = err.msg - print("sub", subject) table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") end print(table.concat(out, "\n")) @@ -123,5 +122,7 @@ print(eval "") print(eval "1+()+") +print(eval "1+(") + print(eval "3)") -- cgit v1.2.3-55-g6feb From 2ea15094a1d4a9f422c983480aab7231951ee20e Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Wed, 7 Dec 2016 08:50:03 -0300 Subject: Renaming files and removing example based on another version --- examples/expRec.lua | 128 ++++++++++++++++++++++++++++++++++++++ examples/expRecAut.lua | 132 ++++++++++++++++++++++++++++++++++++++++ examples/expect.lua | 98 ----------------------------- examples/expressionRecAut.lua | 132 ---------------------------------------- examples/expressionRecovery.lua | 128 -------------------------------------- 5 files changed, 260 insertions(+), 358 deletions(-) create mode 100644 examples/expRec.lua create mode 100644 examples/expRecAut.lua delete mode 100644 examples/expect.lua delete mode 100644 examples/expressionRecAut.lua delete mode 100644 examples/expressionRecovery.lua diff --git a/examples/expRec.lua b/examples/expRec.lua new file mode 100644 index 0000000..c5cbcca --- /dev/null +++ b/examples/expRec.lua @@ -0,0 +1,128 @@ +local m = require"lpeglabelrec" +local re = require"relabelrec" + +local R, S, P, V = m.R, m.S, m.P, m.V +local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt +local T, Rec = m.T, m.Rec + +local labels = { + {"ExpTermFirst", "expected an expression"}, + {"ExpTermOp", "expected a term after the operator"}, + {"MisClose", "missing a closing ')' after the expression"}, +} + +local function labelindex(labname) + for i, elem in ipairs(labels) do + if elem[1] == labname then + return i + end + end + error("could not find label: " .. labname) +end + +local errors, subject + +local function expect(patt, labname, recpatt) + local i = labelindex(labname) + return patt + T(i) +end + + +local num = R("09")^1 / tonumber +local op = S("+-") + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + +local g = P { + "Exp", + Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, + OperandFirst = expect(V"Term", "ExpTermFirst"), + Operand = expect(V"Term", "ExpTermOp"), + Term = num + V"Group", + Group = "(" * V"Exp" * expect(")", "MisClose"), +} + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) +end + +function record (labname) + return (m.Cp() * m.Cc(labelindex(labname))) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue (p) + return p or m.Cc(1000) +end + +local recg = P { + "S", + S = Rec(V"A", V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 + A = Rec(V"Sg", V"ErrExpTermOp", labelindex("ExpTermOp")), + Sg = Rec(g, V"ErrMisClose", labelindex("MisClose")), + ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), + ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), + ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), +} + + +local function eval(input) + errors = {} + subject = input + local result, label, suffix = recg:match(input) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local pos = err.col + local msg = err.msg + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + print(table.concat(out, "\n")) + end + return result +end + +print(eval "90-70*5") +--> 20 + +print(eval "2+") +--> 2 + 0 + +print(eval "-2") +--> 0 - 2 + +print(eval "1+3+-9") +--> 1 + 3 + [0] - 9 + +print(eval "1+()3+") +--> 1 + ([0]) [3 +] [0] + +print(eval "8-(2+)-5") +--> 8 - (2 + [0]) - 5 + +print(eval "()") + +print(eval "") + +print(eval "1+()+") + +print(eval "1+(") + +print(eval "3)") + diff --git a/examples/expRecAut.lua b/examples/expRecAut.lua new file mode 100644 index 0000000..e098078 --- /dev/null +++ b/examples/expRecAut.lua @@ -0,0 +1,132 @@ +local m = require"lpeglabelrec" +local re = require"relabelrec" + +local R, S, P, V = m.R, m.S, m.P, m.V +local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt +local T, Rec = m.T, m.Rec + +local num = R("09")^1 / tonumber +local op = S("+-") + +local labels = {} +local nlabels = 0 + +local function newError(lab, msg, psync, pcap) + nlabels = nlabels + 1 + psync = psync or m.P(-1) + pcap = pcap or m.P"" + labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap } +end + +newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000)) +newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000)) +newError("MisClose", "missing a closing ')' after the expression", m.P")") +newError("Extra", "extra characters found after the expression") + +local errors, subject + +local function expect(patt, labname) + local i = labels[labname].id + return patt + T(i) +end + +local function compute(tokens) + local result = tokens[1] + for i = 2, #tokens, 2 do + if tokens[i] == '+' then + result = result + tokens[i+1] + elseif tokens[i] == '-' then + result = result - tokens[i+1] + else + error('unknown operation: ' .. tokens[i]) + end + end + return result +end + +local g = P { + "Exp", + Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, + OperandFirst = expect(V"Term", "ExpTermFirst"), + Operand = expect(V"Term", "ExpTermOp"), + Term = num + V"Group", + Group = "(" * V"Exp" * expect(")", "MisClose"), +} + +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = labels[lab].msg }) +end + +function record (labname) + return (m.Cp() * m.Cc(labname)) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue (p) + return p or m.Cc(1000) +end + +local recg2 = g +for k, v in pairs(labels) do + recg2 = Rec(recg2, record(k) * sync(v.psync) * v.pcap, v.id) +end + +local recg = P { + "S", + S = Rec(V"A", V"ErrExpTermFirst", labels["ExpTermFirst"].id), -- default value is 0 + A = Rec(V"Sg", V"ErrExpTermOp", labels["ExpTermOp"].id), + Sg = Rec(g, V"ErrMisClose", labels["MisClose"].id), + ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), + ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), + ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), +} + + +local function eval(input) + errors = {} + subject = input + local result, label, suffix = recg2:match(input) + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local pos = err.col + local msg = err.msg + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + print(table.concat(out, "\n")) + end + return result +end + +print(eval "90-70*5") +--> 20 + +print(eval "2+") +--> 2 + 0 + +print(eval "-2") +--> 0 - 2 + +print(eval "1+3+-9") +--> 1 + 3 + [0] - 9 + +print(eval "1+()3+") +--> 1 + ([0]) [+] 3 + [0] + +print(eval "8-(2+)-5") +--> 8 - (2 + [0]) - 5 + +print(eval "()") + +print(eval "") + +print(eval "1+()+") + +print(eval "1+(") + +print(eval "3)") + diff --git a/examples/expect.lua b/examples/expect.lua deleted file mode 100644 index cb68d38..0000000 --- a/examples/expect.lua +++ /dev/null @@ -1,98 +0,0 @@ -local lpeg = require"lpeglabel" - -local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T - --- The `labels` table contains the list of labels that we will be using --- as well as the corresponding error message for each label, which will --- be used in our error reporting later on. -local labels = { - {"NoExp", "no expression found"}, - {"Extra", "extra characters found after the expression"}, - {"ExpTerm", "expected a term after the operator"}, - {"ExpExp", "expected an expression after the parenthesis"}, - {"MisClose", "missing a closing ')' after the expression"}, -} - --- The `expect` function takes a pattern and a label defined in --- the `labels` table and returns a pattern that throws the specified --- label if the original pattern fails to match. --- Note: LPegLabel requires us to use integers for the labels, so we --- use the index of the label in the `labels` table to represent it. -local function expect(patt, labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return patt + T(i) - end - end - - error("could not find label: " .. labname) -end - -local num = R("09")^1 / tonumber -local op = S("+-*/") - --- The `compute` function takes an alternating list of numbers and --- operators and computes the result of applying the operations --- to the numbers in a left to right order (no operator precedence). -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - elseif tokens[i] == '*' then - result = result * tokens[i+1] - elseif tokens[i] == '/' then - result = result / tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - --- Our grammar is a simple arithmetic expression of integers that --- does not take operator precedence into account but allows grouping --- via parenthesis. -local g = P { - "Exp", - Exp = Ct(V"Term" * (C(op) * expect(V"Term", "ExpTerm"))^0) / compute; - Term = num + V"Group"; - Group = "(" * expect(V"Exp", "ExpExp") * expect(")", "MisClose"); -} - -g = expect(g, "NoExp") * expect(-P(1), "Extra") - --- The `eval` function takes an input string to match against the grammar --- we've just defined. If the input string matches, then the result of the --- computation is returned, otherwise we return the error message and --- position of the first failure encountered. -local function eval(input) - local result, label, suffix = g:match(input) - if result ~= nil then - return result - else - local pos = input:len() - suffix:len() + 1 - local msg = labels[label][2] - return nil, "syntax error: " .. msg .. " (at index " .. pos .. ")" - end -end - -print(eval "98-76*(54/32)") ---> 37.125 - -print(eval "(1+1-1*2/2") ---> syntax error: missing a closing ')' after the expression (at index 11) - -print(eval "(1+)-1*(2/2)") ---> syntax error: expected a term after the operator (at index 4) - -print(eval "(1+1)-1*(/2)") ---> syntax error: expected an expression after the parenthesis (at index 10) - -print(eval "1+(1-(1*2))/2x") ---> syntax error: extra chracters found after the expression (at index 14) - -print(eval "-1+(1-(1*2))/2") ---> syntax error: no expression found (at index 1) diff --git a/examples/expressionRecAut.lua b/examples/expressionRecAut.lua deleted file mode 100644 index e098078..0000000 --- a/examples/expressionRecAut.lua +++ /dev/null @@ -1,132 +0,0 @@ -local m = require"lpeglabelrec" -local re = require"relabelrec" - -local R, S, P, V = m.R, m.S, m.P, m.V -local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt -local T, Rec = m.T, m.Rec - -local num = R("09")^1 / tonumber -local op = S("+-") - -local labels = {} -local nlabels = 0 - -local function newError(lab, msg, psync, pcap) - nlabels = nlabels + 1 - psync = psync or m.P(-1) - pcap = pcap or m.P"" - labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap } -end - -newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000)) -newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000)) -newError("MisClose", "missing a closing ')' after the expression", m.P")") -newError("Extra", "extra characters found after the expression") - -local errors, subject - -local function expect(patt, labname) - local i = labels[labname].id - return patt + T(i) -end - -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - -local g = P { - "Exp", - Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, - OperandFirst = expect(V"Term", "ExpTermFirst"), - Operand = expect(V"Term", "ExpTermOp"), - Term = num + V"Group", - Group = "(" * V"Exp" * expect(")", "MisClose"), -} - -function recorderror(pos, lab) - local line, col = re.calcline(subject, pos) - table.insert(errors, { line = line, col = col, msg = labels[lab].msg }) -end - -function record (labname) - return (m.Cp() * m.Cc(labname)) / recorderror -end - -function sync (p) - return (-p * m.P(1))^0 -end - -function defaultValue (p) - return p or m.Cc(1000) -end - -local recg2 = g -for k, v in pairs(labels) do - recg2 = Rec(recg2, record(k) * sync(v.psync) * v.pcap, v.id) -end - -local recg = P { - "S", - S = Rec(V"A", V"ErrExpTermFirst", labels["ExpTermFirst"].id), -- default value is 0 - A = Rec(V"Sg", V"ErrExpTermOp", labels["ExpTermOp"].id), - Sg = Rec(g, V"ErrMisClose", labels["MisClose"].id), - ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), - ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), - ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), -} - - -local function eval(input) - errors = {} - subject = input - local result, label, suffix = recg2:match(input) - if #errors > 0 then - local out = {} - for i, err in ipairs(errors) do - local pos = err.col - local msg = err.msg - table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") - end - print(table.concat(out, "\n")) - end - return result -end - -print(eval "90-70*5") ---> 20 - -print(eval "2+") ---> 2 + 0 - -print(eval "-2") ---> 0 - 2 - -print(eval "1+3+-9") ---> 1 + 3 + [0] - 9 - -print(eval "1+()3+") ---> 1 + ([0]) [+] 3 + [0] - -print(eval "8-(2+)-5") ---> 8 - (2 + [0]) - 5 - -print(eval "()") - -print(eval "") - -print(eval "1+()+") - -print(eval "1+(") - -print(eval "3)") - diff --git a/examples/expressionRecovery.lua b/examples/expressionRecovery.lua deleted file mode 100644 index c5cbcca..0000000 --- a/examples/expressionRecovery.lua +++ /dev/null @@ -1,128 +0,0 @@ -local m = require"lpeglabelrec" -local re = require"relabelrec" - -local R, S, P, V = m.R, m.S, m.P, m.V -local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt -local T, Rec = m.T, m.Rec - -local labels = { - {"ExpTermFirst", "expected an expression"}, - {"ExpTermOp", "expected a term after the operator"}, - {"MisClose", "missing a closing ')' after the expression"}, -} - -local function labelindex(labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return i - end - end - error("could not find label: " .. labname) -end - -local errors, subject - -local function expect(patt, labname, recpatt) - local i = labelindex(labname) - return patt + T(i) -end - - -local num = R("09")^1 / tonumber -local op = S("+-") - -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - -local g = P { - "Exp", - Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, - OperandFirst = expect(V"Term", "ExpTermFirst"), - Operand = expect(V"Term", "ExpTermOp"), - Term = num + V"Group", - Group = "(" * V"Exp" * expect(")", "MisClose"), -} - -function recorderror(pos, lab) - local line, col = re.calcline(subject, pos) - table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) -end - -function record (labname) - return (m.Cp() * m.Cc(labelindex(labname))) / recorderror -end - -function sync (p) - return (-p * m.P(1))^0 -end - -function defaultValue (p) - return p or m.Cc(1000) -end - -local recg = P { - "S", - S = Rec(V"A", V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 - A = Rec(V"Sg", V"ErrExpTermOp", labelindex("ExpTermOp")), - Sg = Rec(g, V"ErrMisClose", labelindex("MisClose")), - ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), - ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), - ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), -} - - -local function eval(input) - errors = {} - subject = input - local result, label, suffix = recg:match(input) - if #errors > 0 then - local out = {} - for i, err in ipairs(errors) do - local pos = err.col - local msg = err.msg - table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") - end - print(table.concat(out, "\n")) - end - return result -end - -print(eval "90-70*5") ---> 20 - -print(eval "2+") ---> 2 + 0 - -print(eval "-2") ---> 0 - 2 - -print(eval "1+3+-9") ---> 1 + 3 + [0] - 9 - -print(eval "1+()3+") ---> 1 + ([0]) [3 +] [0] - -print(eval "8-(2+)-5") ---> 8 - (2 + [0]) - 5 - -print(eval "()") - -print(eval "") - -print(eval "1+()+") - -print(eval "1+(") - -print(eval "3)") - -- cgit v1.2.3-55-g6feb From d946a371072cf728f90a4f3d8007288e1dc875c9 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Wed, 7 Dec 2016 15:20:07 -0300 Subject: Updating examples of README --- README.md | 125 ++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index fe7c14d..5e27796 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,7 @@ character fails, and it is caught by an ordered choice. An error, by its turn, is produced by the throw operator and may be caught by the recovery operator. -Below there is a brief summary -of the new functions provided by LpegLabel: +Below there is a brief summary of the new functions provided by LpegLabel: @@ -112,57 +111,11 @@ in the *examples* directory. The following example defines a grammar that matches a list of identifiers separated by commas. A label is thrown when there is an error matching an identifier -or a comma: +or a comma. -```lua -local m = require'lpeglabelrec' -local re = require'relabelrec' - -local g = m.P{ - "S", - S = m.V"Id" * m.V"List", - List = -m.P(1) + (m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)) * m.V"List", - Id = m.V"Sp" * m.R'az'^1, - Comma = m.V"Sp" * ",", - Sp = m.S" \n\t"^0, -} - -function mymatch (g, s) - local r, e, sfail = g:match(s) - if not r then - local line, col = re.calcline(s, #s - #sfail) - local msg = "Error at line " .. line .. " (col " .. col .. ")" - if e == 1 then - return r, msg .. ": expecting an identifier before '" .. sfail .. "'" - elseif e == 2 then - return r, msg .. ": expecting ',' before '" .. sfail .. "'" - else - return r, msg - end - end - return r -end - -print(mymatch(g, "one,two")) --> 8 -print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' -print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' -``` +We use function `newError` to store error messages in a +table and to return the index associated with each error message. -In this example we could think about writing rule List as follows: -```lua -List = ((m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)))^0, -``` - -but when matching this expression against the end of input -we would get a failure whose associated label would be **2**, -and this would cause the failure of the *whole* repetition. - - -##### Mnemonics instead of numbers - -In the previous example we could have created a table -with the error messages to improve the readability of the PEG. -Below we rewrite the previous grammar following this approach: ```lua local m = require'lpeglabelrec' @@ -203,19 +156,71 @@ print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expec print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' ``` +In this example we could think about writing rule List as follows: +```lua +List = ((m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)))^0, +``` + +but when matching this expression against the end of input +we would get a failure whose associated label would be **errComma**, +and this would cause the failure of the *whole* repetition. + + + #### Error Recovery -By using the recovery operator we can specify a recovery pattern that -should be matched when a label is thrown. After matching this pattern, -and possibly recording the error, the parser can continue parsing to -find more errors. +By using the `Rec` function we can specify a recovery pattern that +should be matched when a label is thrown. After matching the recovery +pattern, and possibly recording the error, the parser will resume +the regular matching. For example, in the example below +we expect to match rule `A`, but in case label 42 is thrown +then we will try to match `recp`: +```lua +local m = require'lpeglabelrec' -Below we rewrite the previous example to illustrate a recovery strategy. -Grammar `g` remains the same, but we add a recovery grammar `grec` that -handles the labels thrown by `g`. +local recp = m.P"oast" -arithmetic expression example and modify -the `expect` function to use the recovery operator for error recovery: +local g = m.P{ + "S", + S = m.Rec(m.V"A", recp, 42) * ".", + A = m.P"t" * (m.P("est") + m.T(42)) +} + +print(g:match("test.")) --> 6 + +print(g:match("toast.")) --> 7 + +print(g:match("oast.")) --> nil 0 oast. + +print(g:match("toward.")) --> nil 0 ward. +``` +When trying to match 'toast.', in rule `A` the first +'t' is matched, and then label 42 is thrown, with the associated +inpux suffix 'oast.'. In rule `S` this label is caught +and the recovery pattern matches 'oast', so pattern `'.'` +matches the rest of the input. + +When matching 'oast.', pattern `m.P"t"` fails, and +the result of the matching is nil, 0, oast.. + +When matching 'toward.', label 42 is throw, with the associated +input suffix 'oward.'. The matching of the recovery pattern fails to, +so the result of the matching is nil, 0, ward.. + +Usually, the recovery pattern is an expression that never fails. +In the previous example, we could have used `(m.P(1) - m.P".")^0` +as the recovery pattern. + +Below we rewrite the grammar that describes a list of identifiers +to use a recovery strategy. Grammar `g` remains the same, but we add a +recovery grammar `grec` that handles the labels thrown by `g`. + +In grammar `grec` we use functions `record` and `sync`. +Function `record` gives us a pattern that captures two +values: the current subject position (where a label was thrown) +and the label itself. These values will be used to record +all the errors found. Function `sync` give us synchronization +pattern, that macthes the input ```lua local m = require'lpeglabelrec' -- cgit v1.2.3-55-g6feb From 8e3c0330defb4b5da81f88c9b45bc5fc9361eb34 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 9 Dec 2016 09:02:02 -0300 Subject: Minor bug when printing a recovery pattern --- lpprint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lpprint.c b/lpprint.c index ff69a6a..8c488ea 100644 --- a/lpprint.c +++ b/lpprint.c @@ -61,7 +61,7 @@ void printinst (const Instruction *op, const Instruction *p) { "choice", "jmp", "call", "open_call", "commit", "partial_commit", "back_commit", "failtwice", "fail", "giveup", "fullcapture", "opencapture", "closecapture", "closeruntime", - "throw", "labeled_choice", "recovery" /* labeled failure */ + "throw", "recovery" /* labeled failure */ }; printf("%02ld: %s ", (long)(p - op), names[p->i.code]); switch ((Opcode)p->i.code) { -- cgit v1.2.3-55-g6feb From 2f1e173c3d7670f802a087cd2c80afc5b0ed23c3 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 9 Dec 2016 10:50:34 -0300 Subject: Updating documentantion and polishing examples --- README.md | 542 +++++++++++++++++++++++++++-------------------- examples/expRec.lua | 50 ++--- examples/expRecAut.lua | 52 ++--- examples/listId2Rec2.lua | 2 +- examples/listIdRe2.lua | 31 ++- 5 files changed, 376 insertions(+), 301 deletions(-) diff --git a/README.md b/README.md index 5e27796..9bc162d 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,13 @@ Below there is a brief summary of the new functions provided by LpegLabel: - + - + @@ -173,41 +173,38 @@ By using the `Rec` function we can specify a recovery pattern that should be matched when a label is thrown. After matching the recovery pattern, and possibly recording the error, the parser will resume the regular matching. For example, in the example below -we expect to match rule `A`, but in case label 42 is thrown -then we will try to match `recp`: +we expect to match rule `A`, but when a failure occur the label 42 +is thrown and then we will try to match the recovery pattern `recp`: ```lua local m = require'lpeglabelrec' local recp = m.P"oast" local g = m.P{ - "S", - S = m.Rec(m.V"A", recp, 42) * ".", - A = m.P"t" * (m.P("est") + m.T(42)) + "S", + S = m.Rec(m.V"A", recp, 42) * ".", + A = m.P"t" * (m.P"est" + m.T(42)) } print(g:match("test.")) --> 6 - print(g:match("toast.")) --> 7 - -print(g:match("oast.")) --> nil 0 oast. - -print(g:match("toward.")) --> nil 0 ward. +print(g:match("oast.")) --> nil 0 oast. +print(g:match("toward.")) --> nil 0 ward. ``` -When trying to match 'toast.', in rule `A` the first -'t' is matched, and then label 42 is thrown, with the associated -inpux suffix 'oast.'. In rule `S` this label is caught -and the recovery pattern matches 'oast', so pattern `'.'` -matches the rest of the input. +When trying to match subject 'toast.', in rule `A` the first +'t' is matched, then the matching of `m.P"est"` fails and label 42 +is thrown, with the associated inpux suffix 'oast.'. In rule +`S` label 42 is caught and the recovery pattern matches 'oast', +so pattern `'.'` matches the rest of the input. -When matching 'oast.', pattern `m.P"t"` fails, and -the result of the matching is nil, 0, oast.. +When matching subject 'oast.', pattern `m.P"t"` fails, and +the result of the matching is nil, 0, oast.. -When matching 'toward.', label 42 is throw, with the associated -input suffix 'oward.'. The matching of the recovery pattern fails to, -so the result of the matching is nil, 0, ward.. +When matching 'toward.', label 42 is thrown after matching 't', +with the associated input suffix 'oward.'. As the matching of the +recovery pattern fails, the result is nil, 0, ward.. -Usually, the recovery pattern is an expression that never fails. +Usually, the recovery pattern is an expression that does not fail. In the previous example, we could have used `(m.P(1) - m.P".")^0` as the recovery pattern. @@ -216,11 +213,14 @@ to use a recovery strategy. Grammar `g` remains the same, but we add a recovery grammar `grec` that handles the labels thrown by `g`. In grammar `grec` we use functions `record` and `sync`. -Function `record` gives us a pattern that captures two -values: the current subject position (where a label was thrown) -and the label itself. These values will be used to record -all the errors found. Function `sync` give us synchronization -pattern, that macthes the input +Function `record`, plus function `recorderror`, will help +us to save the input position where a label was thrown, +while function `sync` will give us a synchronization pattern, +that consumes the input while is not possible to match a given +pattern `p`. + +When the matching of an identifier fails, a defaul value ('NONE') +is provided. ```lua local m = require'lpeglabelrec' @@ -266,8 +266,8 @@ end local grec = m.P{ "S", S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), - ErrComma = record(errComma) * sync(-m.P(1) + id), - ErrId = record(errId) * sync(-m.P(1) + ",") + ErrComma = record(errComma) * sync(id), + ErrId = record(errId) * sync(m.P",") } @@ -281,58 +281,41 @@ function mymatch (g, s) local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg table.insert(out, msg) end - return nil, table.concat(out, "\n") + return nil, table.concat(out, "\n") .. "\n" end return r end print(mymatch(grec, "one,two")) +-- Captures (separated by ';'): one; two; +-- Syntactic errors found: 0 + print(mymatch(grec, "one two three")) +-- Captures (separated by ';'): one; two; three; +-- Syntactic errors found: 2 +-- Error at line 1 (col 4): expecting ',' +-- Error at line 1 (col 8): expecting ',' + print(mymatch(grec, "1,\n two, \n3,")) +-- Captures (separated by ';'): NONE; two; NONE; NONE; +-- Syntactic errors found: 3 +-- Error at line 1 (col 1): expecting an identifier +-- Error at line 2 (col 6): expecting an identifier +-- Error at line 3 (col 2): expecting an identifier + print(mymatch(grec, "one\n two123, \nthree,")) +-- Captures (separated by ';'): one; two; three; NONE; +-- Syntactic errors found: 3 +-- Error at line 2 (col 1): expecting ',' +-- Error at line 2 (col 5): expecting ',' +-- Error at line 3 (col 6): expecting an identifier ``` - - ##### *relabelrec* syntax -Now we rewrite the previous example using the syntax -supported by *relabelrec*: - -```lua -local re = require 'relabelrec' - -local g = re.compile[[ - S <- Id List - List <- !. / (',' / %{2}) (Id / %{1}) List - Id <- Sp [a-z]+ - Comma <- Sp ',' - Sp <- %s* -]] - -function mymatch (g, s) - local r, e, sfail = g:match(s) - if not r then - local line, col = re.calcline(s, #s - #sfail) - local msg = "Error at line " .. line .. " (col " .. col .. ")" - if e == 1 then - return r, msg .. ": expecting an identifier before '" .. sfail .. "'" - elseif e == 2 then - return r, msg .. ": expecting ',' before '" .. sfail .. "'" - else - return r, msg - end - end - return r -end - -print(mymatch(g, "one,two")) --> 8 -print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' -print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' -``` - -With the help of function *setlabels* we can also rewrite the previous example to use -mnemonic labels instead of plain numbers: +Below we describe again a grammar that matches a list of identifiers, +now using the syntax supported by *relabelrec*, where `//{}` is the +recovery operator, and `%{}` is the throw operator: ```lua local re = require 'relabelrec' @@ -355,64 +338,124 @@ re.setlabels(labels) local g = re.compile[[ S <- Id List - List <- !. / (',' / %{errComma}) (Id / %{errId}) List - Id <- Sp [a-z]+ - Comma <- Sp ',' + List <- !. / Comma Id List + Id <- Sp {[a-z]+} / %{errId} + Comma <- Sp ',' / %{errComma} Sp <- %s* ]] +local errors + +function recorderror (subject, pos, label) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = errmsgs[labels[label]] }) + return true +end + +function sync (p) + return '( !(' .. p .. ') .)*' +end + +local grec = re.compile( + "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" .. + "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('[a-z]+') .. "\n" .. + "ErrId <- ('' -> 'errId' => recorderror) " .. sync('","') .. "-> default" + , {g = g, recorderror = recorderror, default = "NONE"} +) + function mymatch (g, s) - local r, e, sfail = g:match(s) - if not r then - local line, col = re.calcline(s, #s - #sfail) - local msg = "Error at line " .. line .. " (col " .. col .. "): " - return r, msg .. errmsgs[e] .. " before '" .. sfail .. "'" + errors = {} + subject = s + io.write("Input: ", s, "\n") + local r = { g:match(s) } + io.write("Captures (separated by ';'): ") + for k, v in pairs(r) do + io.write(v .. "; ") + end + io.write("\nSyntactic errors found: " .. #errors) + if #errors > 0 then + io.write("\n") + local out = {} + for i, err in ipairs(errors) do + local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg + table.insert(out, msg) + end + io.write(table.concat(out, "\n")) end + print("\n") return r end -print(mymatch(g, "one,two")) --> 8 -print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' -print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' -``` - +print(mymatch(grec, "one,two")) +-- Captures (separated by ';'): one; two; +-- Syntactic errors found: 0 +print(mymatch(grec, "one two three")) +-- Captures (separated by ';'): one; two; three; +-- Syntactic errors found: 2 +-- Error at line 1 (col 4): expecting ',' +-- Error at line 1 (col 8): expecting ',' +print(mymatch(grec, "1,\n two, \n3,")) +-- Captures (separated by ';'): NONE; two; NONE; NONE; +-- Syntactic errors found: 3 +-- Error at line 1 (col 1): expecting an identifier +-- Error at line 2 (col 6): expecting an identifier +-- Error at line 3 (col 2): expecting an identifier +print(mymatch(grec, "one\n two123, \nthree,")) +-- Captures (separated by ';'): one; two; three; NONE; +-- Syntactic errors found: 3 +-- Error at line 2 (col 1): expecting ',' +-- Error at line 2 (col 5): expecting ',' +-- Error at line 3 (col 6): expecting an identifier +``` #### Arithmetic Expressions -Here's an example of an LPegLabel grammar that make its own function called -'expect', which takes a pattern and a label as parameters and throws the label -if the pattern fails to be matched. This function can be extended later on to -record all errors encountered once error recovery is implemented. +Here's an example of an LPegLabel grammar that matches an expression. +We have used a function `expect`, that takes a pattern `patt` and a label as +parameters and builds a new pattern that throws this label when `patt` +fails. -```lua -local lpeg = require"lpeglabel" +When a subexpression is syntactically invalid, a default value of 1000 +is provided by the recovery pattern, so the evaluation of an expression +should always produce a numeric value. -local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T +In this example, we can see that it may be a tedious and error prone +task to build manually the recovery grammar `grec`. In the next example +we will show how to build the recovery grammar in a more automatic way. + +```lua +local m = require"lpeglabelrec" +local re = require"relabelrec" local labels = { - {"NoExp", "no expression found"}, - {"Extra", "extra characters found after the expression"}, - {"ExpTerm", "expected a term after the operator"}, - {"ExpExp", "expected an expression after the parenthesis"}, + {"ExpTermFirst", "expected an expression"}, + {"ExpTermOp", "expected a term after the operator"}, {"MisClose", "missing a closing ')' after the expression"}, } -local function expect(patt, labname) +local function labelindex(labname) for i, elem in ipairs(labels) do if elem[1] == labname then - return patt + T(i) + return i end end - error("could not find label: " .. labname) end -local num = R("09")^1 / tonumber -local op = S("+-*/") +local errors, subject + +local function expect(patt, labname) + local i = labelindex(labname) + return patt + m.T(i) +end + + +local num = m.R("09")^1 / tonumber +local op = m.S("+-") local function compute(tokens) local result = tokens[1] @@ -421,10 +464,6 @@ local function compute(tokens) result = result + tokens[i+1] elseif tokens[i] == '-' then result = result - tokens[i+1] - elseif tokens[i] == '*' then - result = result * tokens[i+1] - elseif tokens[i] == '/' then - result = result / tokens[i+1] else error('unknown operation: ' .. tokens[i]) end @@ -432,128 +471,128 @@ local function compute(tokens) return result end -local g = P { +local g = m.P { "Exp", - Exp = Ct(V"Term" * (C(op) * expect(V"Term", "ExpTerm"))^0) / compute; - Term = num + V"Group"; - Group = "(" * expect(V"Exp", "ExpExp") * expect(")", "MisClose"); + Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, + OperandFirst = expect(m.V"Term", "ExpTermFirst"), + Operand = expect(m.V"Term", "ExpTermOp"), + Term = num + m.V"Group", + Group = "(" * m.V"Exp" * expect(")", "MisClose"), } -g = expect(g, "NoExp") * expect(-P(1), "Extra") - -local function eval(input) - local result, label, suffix = g:match(input) - if result ~= nil then - return result - else - local pos = input:len() - suffix:len() + 1 - local msg = labels[label][2] - return nil, "syntax error: " .. msg .. " (at index " .. pos .. ")" - end +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) end -print(eval "98-76*(54/32)") ---> 37.125 - -print(eval "(1+1-1*2/2") ---> syntax error: missing a closing ')' after the expression (at index 11) - -print(eval "(1+)-1*(2/2)") ---> syntax error: expected a term after the operator (at index 4) - -print(eval "(1+1)-1*(/2)") ---> syntax error: expected an expression after the parenthesis (at index 10) - -print(eval "1+(1-(1*2))/2x") ---> syntax error: extra chracters found after the expression (at index 14) - -print(eval "-1+(1-(1*2))/2") ---> syntax error: no expression found (at index 1) -``` - -#### Catching labels - -When a label is thrown, the grammar itself can handle this label -by using the labeled ordered choice. Below we rewrite the example -of the list of identifiers to show this feature: - - -```lua -local m = require'lpeglabel' - -local terror = {} +function record (labname) + return (m.Cp() * m.Cc(labelindex(labname))) / recorderror +end -local function newError(s) - table.insert(terror, s) - return #terror +function sync (p) + return (-p * m.P(1))^0 end -local errUndef = newError("undefined") -local errId = newError("expecting an identifier") -local errComma = newError("expecting ','") +function defaultValue (p) + return p or m.Cc(1000) +end -local g = m.P{ +local grec = m.P { "S", - S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), - m.V"ErrComma", errComma), - List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", - Id = m.V"Sp" * m.R'az'^1, - Comma = m.V"Sp" * ",", - Sp = m.S" \n\t"^0, - ErrId = m.Cc(errId) / terror, - ErrComma = m.Cc(errComma) / terror + S = m.Rec(m.V"A", m.V"ErrExpTermFirst", labelindex("ExpTermFirst")), + A = m.Rec(m.V"Sg", m.V"ErrExpTermOp", labelindex("ExpTermOp")), + Sg = m.Rec(g, m.V"ErrMisClose", labelindex("MisClose")), + ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), + ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), + ErrMisClose = record("MisClose") * sync(m.P")") * defaultValue(m.P""), } + +local function eval(input) + errors = {} + io.write("Input: ", input, "\n") + subject = input + local result, label, suffix = grec:match(input) + io.write("Syntactic errors found: " .. #errors, "\n") + if #errors > 0 then + local out = {} + for i, err in ipairs(errors) do + local pos = err.col + local msg = err.msg + table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") + end + print(table.concat(out, "\n")) + end + io.write("Result = ") + return result +end -print(m.match(g, "one,two")) --> 8 -print(m.match(g, "one two")) --> expecting ',' -print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier +print(eval "90-70-(5)+3") +-- Syntactic errors found: 0 +-- Result = 18 + +print(eval "15+") +-- Syntactic errors found: 1 +-- syntax error: expected a term after the operator (at index 3) +-- Result = 1015 + +print(eval "-2") +-- Syntactic errors found: 1 +-- syntax error: expected an expression (at index 1) +-- Result = 998 + +print(eval "1+()+") +-- Syntactic errors found: 2 +-- syntax error: expected an expression (at index 4) +-- syntax error: expected a term after the operator (at index 5) +-- Result = 2001 + +print(eval "1+(") +-- Syntactic errors found: 2 +-- syntax error: expected an expression (at index 3) +-- syntax error: missing a closing ')' after the expression (at index 3) +-- Result = 1001 + +print(eval "3)") +-- Syntactic errors found: 0 +-- Result = 3 ``` -#### Error Recovery +#### Automatically Building the Recovery Grammar -By using labeled ordered choice or the recovery operator, when a label -is thrown, the parser may record the error and still continue parsing -to find more errors. We can even record the error right away without -actually throwing a label (relying on the regular PEG failure instead). -Below we rewrite the arithmetic expression example and modify -the `expect` function to use the recovery operator for error recovery: +Below we rewrite the previous example to automatically +build the recovery grammar based on information provided +by the user for each label (error message, recovery pattern, etc). +In the example below we also throw an error when the grammar +does not match the whole subject. ```lua -local lpeg = require"lpeglabel" +local m = require"lpeglabelrec" +local re = require"relabelrec" -local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V -local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg -local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec +local num = m.R("09")^1 / tonumber +local op = m.S("+-") -local labels = { - {"NoExp", "no expression found"}, - {"Extra", "extra characters found after the expression"}, - {"ExpTerm", "expected a term after the operator"}, - {"ExpExp", "expected an expression after the parenthesis"}, - {"MisClose", "missing a closing ')' after the expression"}, -} +local labels = {} +local nlabels = 0 -local function labelindex(labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return i - end - end - error("could not find label: " .. labname) +local function newError(lab, msg, psync, pcap) + nlabels = nlabels + 1 + psync = psync or m.P(-1) + pcap = pcap or m.P"" + labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap } end -local function expect(patt, labname, recpatt) - local i = labelindex(labname) - local function recorderror(input, pos, errors) - table.insert(errors, {i, pos}) - return true - end - if not recpatt then recpatt = P"" end - return Rec(patt, Cmt(Carg(1), recorderror) * recpatt) -end +newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000)) +newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000)) +newError("MisClose", "missing a closing ')' after the expression", m.P")") +newError("Extra", "extra characters found after the expression") -local num = R("09")^1 / tonumber -local op = S("+-*/") +local errors, subject + +local function expect(patt, labname) + local i = labels[labname].id + return patt + m.T(i) +end local function compute(tokens) local result = tokens[1] @@ -562,10 +601,6 @@ local function compute(tokens) result = result + tokens[i+1] elseif tokens[i] == '-' then result = result - tokens[i+1] - elseif tokens[i] == '*' then - result = result * tokens[i+1] - elseif tokens[i] == '/' then - result = result / tokens[i+1] else error('unknown operation: ' .. tokens[i]) end @@ -573,43 +608,84 @@ local function compute(tokens) return result end - -local g = P { +local g = m.P { "Exp", - Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute; - Operand = expect(V"Term", "ExpTerm", Cc(0)); - Term = num + V"Group"; - Group = "(" * V"InnerExp" * expect(")", "MisClose"); - InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); + Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, + OperandFirst = expect(m.V"Term", "ExpTermFirst"), + Operand = expect(m.V"Term", "ExpTermOp"), + Term = num + m.V"Group", + Group = "(" * m.V"Exp" * expect(")", "MisClose"), } -g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") +function recorderror(pos, lab) + local line, col = re.calcline(subject, pos) + table.insert(errors, { line = line, col = col, msg = labels[lab].msg }) +end + +function record (labname) + return (m.Cp() * m.Cc(labname)) / recorderror +end + +function sync (p) + return (-p * m.P(1))^0 +end + +function defaultValue (p) + return p or m.Cc(1000) +end + +local grec = g * expect(m.P(-1), "Extra") +for k, v in pairs(labels) do + grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id) +end local function eval(input) - local errors = {} - local result, label, suffix = g:match(input, 1, errors) - if #errors == 0 then - return result - else + errors = {} + io.write("Input: ", input, "\n") + subject = input + local result, label, suffix = grec:match(input) + io.write("Syntactic errors found: " .. #errors, "\n") + if #errors > 0 then local out = {} for i, err in ipairs(errors) do - local pos = err[2] - local msg = labels[err[1]][2] + local pos = err.col + local msg = err.msg table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") end - return nil, table.concat(out, "\n") + print(table.concat(out, "\n")) end + io.write("Result = ") + return result end -print(eval "98-76*(54/32)") ---> 37.125 - -print(eval "-1+(1-(1*2))/2") ---> syntax error: no expression found (at index 1) - -print(eval "(1+1-1*(2/2+)-():") ---> syntax error: expected a term after the operator (at index 13) ---> syntax error: expected an expression after the parenthesis (at index 16) ---> syntax error: missing a closing ')' after the expression (at index 17) ---> syntax error: extra characters found after the expression (at index 17) +print(eval "90-70-(5)+3") +-- Syntactic errors found: 0 +-- Result = 18 + +print(eval "15+") +-- Syntactic errors found: 1 +-- syntax error: expected a term after the operator (at index 3) +-- Result = 1015 + +print(eval "-2") +-- Syntactic errors found: 1 +-- syntax error: expected an expression (at index 1) +-- Result = 998 + +print(eval "1+()+") +-- Syntactic errors found: 2 +-- syntax error: expected an expression (at index 4) +-- syntax error: expected a term after the operator (at index 5) +-- Result = 2001 + +print(eval "1+(") +-- Syntactic errors found: 2 +-- syntax error: expected an expression (at index 3) +-- syntax error: missing a closing ')' after the expression (at index 3) +-- Result = 1001 + +print(eval "3)") +-- Syntactic errors found: 1 +-- syntax error: extra characters found after the expression (at index 2) +-- Result = 3 ``` diff --git a/examples/expRec.lua b/examples/expRec.lua index c5cbcca..5c5fd7d 100644 --- a/examples/expRec.lua +++ b/examples/expRec.lua @@ -1,10 +1,6 @@ local m = require"lpeglabelrec" local re = require"relabelrec" -local R, S, P, V = m.R, m.S, m.P, m.V -local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt -local T, Rec = m.T, m.Rec - local labels = { {"ExpTermFirst", "expected an expression"}, {"ExpTermOp", "expected a term after the operator"}, @@ -22,14 +18,14 @@ end local errors, subject -local function expect(patt, labname, recpatt) +local function expect(patt, labname) local i = labelindex(labname) - return patt + T(i) + return patt + m.T(i) end -local num = R("09")^1 / tonumber -local op = S("+-") +local num = m.R("09")^1 / tonumber +local op = m.S("+-") local function compute(tokens) local result = tokens[1] @@ -45,13 +41,13 @@ local function compute(tokens) return result end -local g = P { +local g = m.P { "Exp", - Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, - OperandFirst = expect(V"Term", "ExpTermFirst"), - Operand = expect(V"Term", "ExpTermOp"), - Term = num + V"Group", - Group = "(" * V"Exp" * expect(")", "MisClose"), + Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, + OperandFirst = expect(m.V"Term", "ExpTermFirst"), + Operand = expect(m.V"Term", "ExpTermOp"), + Term = num + m.V"Group", + Group = "(" * m.V"Exp" * expect(")", "MisClose"), } function recorderror(pos, lab) @@ -71,22 +67,23 @@ function defaultValue (p) return p or m.Cc(1000) end -local recg = P { +local grec = m.P { "S", - S = Rec(V"A", V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 - A = Rec(V"Sg", V"ErrExpTermOp", labelindex("ExpTermOp")), - Sg = Rec(g, V"ErrMisClose", labelindex("MisClose")), + S = m.Rec(m.V"A", m.V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 + A = m.Rec(m.V"Sg", m.V"ErrExpTermOp", labelindex("ExpTermOp")), + Sg = m.Rec(g, m.V"ErrMisClose", labelindex("MisClose")), ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), - ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), + ErrMisClose = record("MisClose") * sync(m.P")") * defaultValue(m.P""), } - - + local function eval(input) errors = {} + io.write("Input: ", input, "\n") subject = input - local result, label, suffix = recg:match(input) - if #errors > 0 then + local result, label, suffix = grec:match(input) + io.write("Syntactic errors found: " .. #errors, "\n") + if #errors > 0 then local out = {} for i, err in ipairs(errors) do local pos = err.col @@ -95,13 +92,14 @@ local function eval(input) end print(table.concat(out, "\n")) end + io.write("Result = ") return result end -print(eval "90-70*5") +print(eval "90-70-(5)+3") --> 20 -print(eval "2+") +print(eval "15+") --> 2 + 0 print(eval "-2") @@ -126,3 +124,5 @@ print(eval "1+(") print(eval "3)") +print(eval "11+())3") + diff --git a/examples/expRecAut.lua b/examples/expRecAut.lua index e098078..f870d73 100644 --- a/examples/expRecAut.lua +++ b/examples/expRecAut.lua @@ -1,12 +1,8 @@ local m = require"lpeglabelrec" local re = require"relabelrec" -local R, S, P, V = m.R, m.S, m.P, m.V -local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt -local T, Rec = m.T, m.Rec - -local num = R("09")^1 / tonumber -local op = S("+-") +local num = m.R("09")^1 / tonumber +local op = m.S("+-") local labels = {} local nlabels = 0 @@ -27,7 +23,7 @@ local errors, subject local function expect(patt, labname) local i = labels[labname].id - return patt + T(i) + return patt + m.T(i) end local function compute(tokens) @@ -44,13 +40,13 @@ local function compute(tokens) return result end -local g = P { +local g = m.P { "Exp", - Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, - OperandFirst = expect(V"Term", "ExpTermFirst"), - Operand = expect(V"Term", "ExpTermOp"), - Term = num + V"Group", - Group = "(" * V"Exp" * expect(")", "MisClose"), + Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, + OperandFirst = expect(m.V"Term", "ExpTermFirst"), + Operand = expect(m.V"Term", "ExpTermOp"), + Term = num + m.V"Group", + Group = "(" * m.V"Exp" * expect(")", "MisClose"), } function recorderror(pos, lab) @@ -70,27 +66,18 @@ function defaultValue (p) return p or m.Cc(1000) end -local recg2 = g +local grec = g * expect(m.P(-1), "Extra") for k, v in pairs(labels) do - recg2 = Rec(recg2, record(k) * sync(v.psync) * v.pcap, v.id) + grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id) end -local recg = P { - "S", - S = Rec(V"A", V"ErrExpTermFirst", labels["ExpTermFirst"].id), -- default value is 0 - A = Rec(V"Sg", V"ErrExpTermOp", labels["ExpTermOp"].id), - Sg = Rec(g, V"ErrMisClose", labels["MisClose"].id), - ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), - ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), - ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), -} - - local function eval(input) errors = {} + io.write("Input: ", input, "\n") subject = input - local result, label, suffix = recg2:match(input) - if #errors > 0 then + local result, label, suffix = grec:match(input) + io.write("Syntactic errors found: " .. #errors, "\n") + if #errors > 0 then local out = {} for i, err in ipairs(errors) do local pos = err.col @@ -99,13 +86,14 @@ local function eval(input) end print(table.concat(out, "\n")) end + io.write("Result = ") return result end -print(eval "90-70*5") ---> 20 +print(eval "90-70-(5)+3") +--> 18 -print(eval "2+") +print(eval "15+") --> 2 + 0 print(eval "-2") @@ -130,3 +118,5 @@ print(eval "1+(") print(eval "3)") +print(eval "11+()3") +--> 1 + ([0]) [+] 3 + [0] diff --git a/examples/listId2Rec2.lua b/examples/listId2Rec2.lua index ab8f1dd..c6705dd 100644 --- a/examples/listId2Rec2.lua +++ b/examples/listId2Rec2.lua @@ -56,7 +56,7 @@ function mymatch (g, s) local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg table.insert(out, msg) end - return nil, table.concat(out, "\n") + return nil, table.concat(out, "\n") .. "\n" end return r end diff --git a/examples/listIdRe2.lua b/examples/listIdRe2.lua index 070bcdb..6bab6ba 100644 --- a/examples/listIdRe2.lua +++ b/examples/listIdRe2.lua @@ -19,7 +19,7 @@ re.setlabels(labels) local g = re.compile[[ S <- Id List List <- !. / Comma Id List - Id <- Sp [a-z]+ / %{errId} + Id <- Sp {[a-z]+} / %{errId} Comma <- Sp ',' / %{errComma} Sp <- %s* ]] @@ -38,25 +38,34 @@ end local grec = re.compile( "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" .. - "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('!. / [a-z]+') .. "\n" .. - "ErrId <- ('' -> 'errId' => recorderror) (!(!. / ',') .)*" - , {g = g, recorderror = recorderror}) + "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('[a-z]+') .. "\n" .. + "ErrId <- ('' -> 'errId' => recorderror) " .. sync('","') .. "-> default" + , {g = g, recorderror = recorderror, default = "NONE"}) function mymatch (g, s) errors = {} - local r, e, sfail = g:match(s) + subject = s + io.write("Input: ", s, "\n") + local r = { g:match(s) } + io.write("Captures (separated by ';'): ") + for k, v in pairs(r) do + io.write(v .. "; ") + end + io.write("\nSyntactic errors found: " .. #errors) if #errors > 0 then + io.write("\n") local out = {} for i, err in ipairs(errors) do local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg table.insert(out, msg) end - return nil, table.concat(out, "\n") + io.write(table.concat(out, "\n")) end - return r + print("\n") + return r end -print(mymatch(grec, "one,two")) -print(mymatch(grec, "one two three")) -print(mymatch(grec, "1,\n two, \n3,")) -print(mymatch(grec, "one\n two123, \nthree,")) +mymatch(grec, "one,two") +mymatch(grec, "one two three") +mymatch(grec, "1,\n two, \n3,") +mymatch(grec, "one\n two123, \nthree,") -- cgit v1.2.3-55-g6feb From 1322d612d72ac658f2aa443dca94954b819c0993 Mon Sep 17 00:00:00 2001 From: Sergio Queiroz Date: Tue, 13 Dec 2016 13:30:36 -0300 Subject: Removing example "recovery.lua" --- examples/recovery.lua | 134 -------------------------------------------------- 1 file changed, 134 deletions(-) delete mode 100644 examples/recovery.lua diff --git a/examples/recovery.lua b/examples/recovery.lua deleted file mode 100644 index bb17b54..0000000 --- a/examples/recovery.lua +++ /dev/null @@ -1,134 +0,0 @@ -local lpeg = require"lpeglabelrec" - -local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V -local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt -local T, Lc = lpeg.T, lpeg.Lc - --- The `labels` table contains the list of labels that we will be using --- as well as the corresponding error message for each label, which will --- be used in our error reporting later on. -local labels = { - {"NoExp", "no expression found"}, - {"Extra", "extra characters found after the expression"}, - {"ExpTerm", "expected a term after the operator"}, - {"ExpExp", "expected an expression after the parenthesis"}, - {"MisClose", "missing a closing ')' after the expression"}, -} - --- The `labelindex` function gives us the index of a label in the --- `labels` table, which serves as the integer representation of the label. --- We need this because LPegLabel requires us to use integers for the labels. -local function labelindex(labname) - for i, elem in ipairs(labels) do - if elem[1] == labname then - return i - end - end - error("could not find label: " .. labname) -end - --- The `errors` table will hold the list of errors recorded during parsing -local errors = {} - --- The `expect` function takes a pattern and a label and returns a pattern --- that throws the specified label if the original pattern fails to match. --- Before throwing the label, it records the label to be thrown along with --- the position of the failure (index in input string) into the `errors` table. -local function expect(patt, labname) - local i = labelindex(labname) - function recorderror(input, pos) - table.insert(errors, {i, pos}) - return true - end - return patt + Cmt("", recorderror) * T(i) -end - -local num = R("09")^1 / tonumber -local op = S("+-*/") - --- The `compute` function takes an alternating list of numbers and --- operators and computes the result of applying the operations --- to the numbers in a left to right order (no operator precedence). -local function compute(tokens) - local result = tokens[1] - for i = 2, #tokens, 2 do - if tokens[i] == '+' then - result = result + tokens[i+1] - elseif tokens[i] == '-' then - result = result - tokens[i+1] - elseif tokens[i] == '*' then - result = result * tokens[i+1] - elseif tokens[i] == '/' then - result = result / tokens[i+1] - else - error('unknown operation: ' .. tokens[i]) - end - end - return result -end - --- Our grammar is a simple arithmetic expression of integers that --- does not take operator precedence into account but allows grouping --- via parenthesis. We have incorporated some error recovery startegies --- to our grammar so that it may resume parsing even after encountering --- an error, which allows us to report more errors. -local g = P { - "Exp", - Exp = Ct(V"Term" * (C(op) * V"OpRecov")^0) / compute; - -- `OpRecov` handles missing terms/operands by returning a dummy (zero). - OpRecov = Lc(V"Operand", Cc(0), labelindex("ExpTerm")); - Operand = expect(V"Term", "ExpTerm"); - Term = num + V"Group"; - -- `Group` handles missing closing parenthesis by simply ignoring it. - -- Like all the others, the error is still recorded of course. - Group = "(" * V"InnerExp" * Lc(expect(")", "MisClose"), P"", labelindex("MisClose")); - -- `InnerExp` handles missing expressions by skipping to the next closing - -- parenthesis. A dummy (zero) is returned in place of the expression. - InnerExp = Lc(expect(V"Exp", "ExpExp"), (P(1) - ")")^0 * Cc(0), labelindex("ExpExp")); -} - -g = expect(g, "NoExp") * expect(-P(1), "Extra") - --- The `eval` function takes an input string to match against the grammar --- we've just defined. If the input string matches, then the result of the --- computation is returned, otherwise we return the error messages and --- positions of all the failures encountered. -local function eval(input) - local result, label, suffix = g:match(input) - if #errors == 0 then - return result - else - local out = {} - for i, err in ipairs(errors) do - local pos = err[2] - local msg = labels[err[1]][2] - table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") - end - errors = {} - return nil, table.concat(out, "\n") - end -end - -print(eval "98-76*(54/32)") ---> 37.125 - -print(eval "(1+1-1*2/2") ---> syntax error: missing a closing ')' after the expression (at index 11) - -print(eval "(1+)-1*(2/2)") ---> syntax error: expected a term after the operator (at index 4) - -print(eval "(1+1)-1*(/2)") ---> syntax error: expected an expression after the parenthesis (at index 10) - -print(eval "1+(1-(1*2))/2x") ---> syntax error: extra chracters found after the expression (at index 14) - -print(eval "-1+(1-(1*2))/2") ---> syntax error: no expression found (at index 1) - -print(eval "(1+1-1*(2/2+)-():") ---> syntax error: expected a term after the operator (at index 13) ---> syntax error: expected an expression after the parenthesis (at index 16) ---> syntax error: missing a closing ')' after the expression (at index 17) ---> syntax error: extra characters found after the expression (at index 17) -- cgit v1.2.3-55-g6feb
FunctionDescription
FunctionDescription
lpeglabelrec.T (l) Throws a label l to signal an error
lpeglabelrec.Rec (p1, p2, l1, [l2, ..., ln])
lpeglabelrec.Rec (p1, p2, l1 [, l2, ..., ln]) Specifies a recovery pattern p2 for p1, when the matching of p1 gives one of the labels l1, ..., ln.
%{l} Syntax of relabelrec module. Equivalent to lpeglabelrec.T(l)
p1 //{l1, ..., ln} p2
p1 //{l1 [, l2, ..., ln} p2 Syntax of relabelrec module. Equivalent to lpeglabelrec.Rec(p1, p2, l1, ..., ln)
relabelrec.calcline(subject, i)