diff --git a/3rd/lpeg/lpcap.c b/3rd/lpeg/lpcap.c index d90b935d..b6911cb1 100644 --- a/3rd/lpeg/lpcap.c +++ b/3rd/lpeg/lpcap.c @@ -1,5 +1,5 @@ /* -** $Id: lpcap.c,v 1.4 2013/03/21 20:25:12 roberto Exp $ +** $Id: lpcap.c,v 1.5 2014/12/12 16:58:47 roberto Exp $ ** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license) */ @@ -462,7 +462,7 @@ static int pushcapture (CapState *cs) { case Carg: { int arg = (cs->cap++)->idx; if (arg + FIXEDARGS > cs->ptop) - return luaL_error(L, "reference to absent argument #%d", arg); + return luaL_error(L, "reference to absent extra argument #%d", arg); lua_pushvalue(L, arg + FIXEDARGS); return 1; } diff --git a/3rd/lpeg/lpcode.c b/3rd/lpeg/lpcode.c index 2cc0e0d7..93c0d2aa 100644 --- a/3rd/lpeg/lpcode.c +++ b/3rd/lpeg/lpcode.c @@ -1,5 +1,5 @@ /* -** $Id: lpcode.c,v 1.18 2013/04/12 16:30:33 roberto Exp $ +** $Id: lpcode.c,v 1.21 2014/12/12 17:01:29 roberto Exp $ ** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license) */ @@ -33,26 +33,30 @@ static const Charset *fullset = &fullset_; */ /* -** Check whether a charset is empty (IFail), singleton (IChar), -** full (IAny), or none of those (ISet). +** Check whether a charset is empty (returns IFail), singleton (IChar), +** full (IAny), or none of those (ISet). When singleton, '*c' returns +** which character it is. (When generic set, the set was the input, +** so there is no need to return it.) */ static Opcode charsettype (const byte *cs, int *c) { - int count = 0; + int count = 0; /* number of characters in the set */ int i; - int candidate = -1; /* candidate position for a char */ - for (i = 0; i < CHARSETSIZE; i++) { + int candidate = -1; /* candidate position for the singleton char */ + for (i = 0; i < CHARSETSIZE; i++) { /* for each byte */ int b = cs[i]; - if (b == 0) { - if (count > 1) return ISet; /* else set is still empty */ + if (b == 0) { /* is byte empty? */ + if (count > 1) /* was set neither empty nor singleton? */ + return ISet; /* neither full nor empty nor singleton */ + /* else set is still empty or singleton */ } - else if (b == 0xFF) { - if (count < (i * BITSPERCHAR)) - return ISet; + else if (b == 0xFF) { /* is byte full? */ + if (count < (i * BITSPERCHAR)) /* was set not full? */ + return ISet; /* neither full nor empty nor singleton */ else count += BITSPERCHAR; /* set is still full */ } - else if ((b & (b - 1)) == 0) { /* byte has only one bit? */ - if (count > 0) - return ISet; /* set is neither full nor empty */ + else if ((b & (b - 1)) == 0) { /* has byte only one bit? */ + if (count > 0) /* was set not empty? */ + return ISet; /* neither full nor empty nor singleton */ else { /* set has only one char till now; track it */ count++; candidate = i; @@ -77,6 +81,7 @@ static Opcode charsettype (const byte *cs, int *c) { } } + /* ** A few basic operations on Charsets */ @@ -84,16 +89,11 @@ static void cs_complement (Charset *cs) { loopset(i, cs->cs[i] = ~cs->cs[i]); } - static int cs_equal (const byte *cs1, const byte *cs2) { loopset(i, if (cs1[i] != cs2[i]) return 0); return 1; } - -/* -** computes whether sets cs1 and cs2 are disjoint -*/ static int cs_disjoint (const Charset *cs1, const Charset *cs2) { loopset(i, if ((cs1->cs[i] & cs2->cs[i]) != 0) return 0;) return 1; @@ -101,7 +101,8 @@ static int cs_disjoint (const Charset *cs1, const Charset *cs2) { /* -** Convert a 'char' pattern (TSet, TChar, TAny) to a charset +** If 'tree' is a 'char' pattern (TSet, TChar, TAny), convert it into a +** charset and return 1; else return 0. */ int tocharset (TTree *tree, Charset *cs) { switch (tree->tag) { @@ -116,7 +117,7 @@ int tocharset (TTree *tree, Charset *cs) { return 1; } case TAny: { - loopset(i, cs->cs[i] = 0xFF); /* add all to the set */ + loopset(i, cs->cs[i] = 0xFF); /* add all characters to the set */ return 1; } default: return 0; @@ -125,13 +126,16 @@ int tocharset (TTree *tree, Charset *cs) { /* -** Checks whether a pattern has captures +** Check whether a pattern tree has captures */ int hascaptures (TTree *tree) { tailcall: switch (tree->tag) { case TCapture: case TRunTime: return 1; + case TCall: + tree = sib2(tree); goto tailcall; /* return hascaptures(sib2(tree)); */ + case TOpenCall: assert(0); default: { switch (numsiblings[tree->tag]) { case 1: /* return hascaptures(sib1(tree)); */ @@ -161,7 +165,7 @@ int hascaptures (TTree *tree) { ** p is nullable => nullable(p) ** nofail(p) => p cannot fail ** The function assumes that TOpenCall is not nullable; -** this will be checked again when the grammar is fixed.) +** this will be checked again when the grammar is fixed. ** Run-time captures can do whatever they want, so the result ** is conservative. */ @@ -198,7 +202,7 @@ int checkaux (TTree *tree, int pred) { case TCall: /* return checkaux(sib2(tree), pred); */ tree = sib2(tree); goto tailcall; default: assert(0); return 0; - }; + } } @@ -245,16 +249,20 @@ int fixedlenx (TTree *tree, int count, int len) { /* ** Computes the 'first set' of a pattern. ** The result is a conservative aproximation: -** match p ax -> x' for some x ==> a in first(p). +** match p ax -> x (for some x) ==> a belongs to first(p) +** or +** a not in first(p) ==> match p ax -> fail (for all x) +** ** The set 'follow' is the first set of what follows the ** pattern (full set if nothing follows it). -** The function returns 0 when this set can be used for -** tests that avoid the pattern altogether. +** +** The function returns 0 when this resulting set can be used for +** test instructions that avoid the pattern altogether. ** A non-zero return can happen for two reasons: -** 1) match p '' -> '' ==> returns 1. -** (tests cannot be used because they always fail for an empty input) -** 2) there is a match-time capture ==> returns 2. -** (match-time captures should not be avoided by optimizations) +** 1) match p '' -> '' ==> return has bit 1 set +** (tests cannot be used because they would always fail for an empty input); +** 2) there is a match-time capture ==> return has bit 2 set +** (optimizations should not bypass match-time captures). */ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { tailcall: @@ -265,7 +273,7 @@ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { } case TTrue: { loopset(i, firstset->cs[i] = follow->cs[i]); - return 1; + return 1; /* accepts the empty string */ } case TFalse: { loopset(i, firstset->cs[i] = 0); @@ -280,7 +288,8 @@ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { } case TSeq: { if (!nullable(sib1(tree))) { - /* return getfirst(sib1(tree), fullset, firstset); */ + /* when p1 is not nullable, p2 has nothing to contribute; + return getfirst(sib1(tree), fullset, firstset); */ tree = sib1(tree); follow = fullset; goto tailcall; } else { /* FIRST(p1 p2, fl) = FIRST(p1, FIRST(p2, fl)) */ @@ -324,7 +333,7 @@ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { /* else go through */ } case TBehind: { /* instruction gives no new information */ - /* call 'getfirst' to check for math-time captures */ + /* call 'getfirst' only to check for math-time captures */ int e = getfirst(sib1(tree), follow, firstset); loopset(i, firstset->cs[i] = follow->cs[i]); /* uses follow */ return e | 1; /* always can accept the empty string */ @@ -335,8 +344,8 @@ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { /* -** If it returns true, then pattern can fail only depending on the next -** character of the subject +** If 'headfail(tree)' true, then 'tree' can fail only depending on the +** next character of the subject. */ static int headfail (TTree *tree) { tailcall: @@ -403,9 +412,9 @@ int sizei (const Instruction *i) { switch((Opcode)i->i.code) { case ISet: case ISpan: return CHARSETINSTSIZE; case ITestSet: return CHARSETINSTSIZE + 1; - case ITestChar: case ITestAny: case IChoice: case IJmp: - case ICall: case IOpenCall: case ICommit: case IPartialCommit: - case IBackCommit: return 2; + case ITestChar: case ITestAny: case IChoice: case IJmp: case ICall: + case IOpenCall: case ICommit: case IPartialCommit: case IBackCommit: + return 2; default: return 1; } } @@ -423,7 +432,8 @@ typedef struct CompileState { /* ** code generation is recursive; 'opt' indicates that the code is -** being generated under a 'IChoice' operator jumping to its end. +** being generated under a 'IChoice' operator jumping to its end +** (that is, the match is "optional"). ** 'tt' points to a previous test protecting this code. 'fl' is ** the follow set of the pattern. */ @@ -431,7 +441,7 @@ static void codegen (CompileState *compst, TTree *tree, int opt, int tt, const Charset *fl); -void reallocprog (lua_State *L, Pattern *p, int nsize) { +void realloccode (lua_State *L, Pattern *p, int nsize) { void *ud; lua_Alloc f = lua_getallocf(L, &ud); void *newblock = f(ud, p->code, p->codesize * sizeof(Instruction), @@ -446,7 +456,7 @@ void reallocprog (lua_State *L, Pattern *p, int nsize) { static int nextinstruction (CompileState *compst) { int size = compst->p->codesize; if (compst->ncode >= size) - reallocprog(compst->L, compst->p, size * 2); + realloccode(compst->L, compst->p, size * 2); return compst->ncode++; } @@ -462,6 +472,9 @@ static int addinstruction (CompileState *compst, Opcode op, int aux) { } +/* +** Add an instruction followed by space for an offset (to be set later) +*/ static int addoffsetinst (CompileState *compst, Opcode op) { int i = addinstruction(compst, op, 0); /* instruction */ addinstruction(compst, (Opcode)0, 0); /* open space for offset */ @@ -470,6 +483,9 @@ static int addoffsetinst (CompileState *compst, Opcode op) { } +/* +** Set the offset of an instruction +*/ static void setoffset (CompileState *compst, int instruction, int offset) { getinstr(compst, instruction + 1).offset = offset; } @@ -478,7 +494,7 @@ static void setoffset (CompileState *compst, int instruction, int offset) { /* ** Add a capture instruction: ** 'op' is the capture instruction; 'cap' the capture kind; -** 'key' the key into ktable; 'aux' is optional offset +** 'key' the key into ktable; 'aux' is the optional capture offset ** */ static int addinstcap (CompileState *compst, Opcode op, int cap, int key, @@ -494,12 +510,18 @@ static int addinstcap (CompileState *compst, Opcode op, int cap, int key, #define target(code,i) ((i) + code[i + 1].offset) +/* +** Patch 'instruction' to jump to 'target' +*/ static void jumptothere (CompileState *compst, int instruction, int target) { if (instruction >= 0) setoffset(compst, instruction, target - instruction); } +/* +** Patch 'instruction' to jump to current position +*/ static void jumptohere (CompileState *compst, int instruction) { jumptothere(compst, instruction, gethere(compst)); } @@ -863,7 +885,8 @@ static int codeseq1 (CompileState *compst, TTree *p1, TTree *p2, /* ** Main code-generation function: dispatch to auxiliar functions -** according to kind of tree +** according to kind of tree. ('needfollow' should return true +** only for consructions that use 'fl'.) */ static void codegen (CompileState *compst, TTree *tree, int opt, int tt, const Charset *fl) { @@ -906,6 +929,7 @@ static void peephole (CompileState *compst) { Instruction *code = compst->p->code; int i; for (i = 0; i < compst->ncode; i += sizei(&code[i])) { + redo: switch (code[i].i.code) { case IChoice: case ICall: case ICommit: case IPartialCommit: case IBackCommit: case ITestChar: case ITestSet: @@ -927,8 +951,7 @@ static void peephole (CompileState *compst) { int fft = finallabel(code, ft); code[i] = code[ft]; /* jump becomes that instruction... */ jumptothere(compst, i, fft); /* but must correct its offset */ - i--; /* reoptimize its label */ - break; + goto redo; /* reoptimize its label */ } default: { jumptothere(compst, i, ft); /* optimize label */ @@ -950,10 +973,10 @@ static void peephole (CompileState *compst) { Instruction *compile (lua_State *L, Pattern *p) { CompileState compst; compst.p = p; compst.ncode = 0; compst.L = L; - reallocprog(L, p, 2); /* minimum initial size */ + realloccode(L, p, 2); /* minimum initial size */ codegen(&compst, p->tree, 0, NOINST, fullset); addinstruction(&compst, IEnd, 0); - reallocprog(L, p, compst.ncode); /* set final size */ + realloccode(L, p, compst.ncode); /* set final size */ peephole(&compst); return p->code; } diff --git a/3rd/lpeg/lpcode.h b/3rd/lpeg/lpcode.h index 5c9d54f9..72d2bb94 100644 --- a/3rd/lpeg/lpcode.h +++ b/3rd/lpeg/lpcode.h @@ -1,5 +1,5 @@ /* -** $Id: lpcode.h,v 1.5 2013/04/04 21:24:45 roberto Exp $ +** $Id: lpcode.h,v 1.6 2013/11/28 14:56:02 roberto Exp $ */ #if !defined(lpcode_h) @@ -17,7 +17,7 @@ int fixedlenx (TTree *tree, int count, int len); int hascaptures (TTree *tree); int lp_gc (lua_State *L); Instruction *compile (lua_State *L, Pattern *p); -void reallocprog (lua_State *L, Pattern *p, int nsize); +void realloccode (lua_State *L, Pattern *p, int nsize); int sizei (const Instruction *i); diff --git a/3rd/lpeg/lpeg.html b/3rd/lpeg/lpeg.html index 4747e304..0eb1747f 100644 --- a/3rd/lpeg/lpeg.html +++ b/3rd/lpeg/lpeg.html @@ -10,7 +10,7 @@ - +
@@ -1375,13 +1375,13 @@ and the new term for each repetition.

Download

LPeg -source code.

+source code.

License

-Copyright © 2013 Lua.org, PUC-Rio. +Copyright © 2014 Lua.org, PUC-Rio.

Permission is hereby granted, free of charge, @@ -1419,7 +1419,7 @@ THE SOFTWARE.

-$Id: lpeg.html,v 1.71 2013/04/11 19:17:41 roberto Exp $ +$Id: lpeg.html,v 1.72 2014/12/12 17:11:35 roberto Exp $

diff --git a/3rd/lpeg/lptree.c b/3rd/lpeg/lptree.c index 4f68906b..7c5b8200 100644 --- a/3rd/lpeg/lptree.c +++ b/3rd/lpeg/lptree.c @@ -1,5 +1,5 @@ /* -** $Id: lptree.c,v 1.10 2013/04/12 16:30:33 roberto Exp $ +** $Id: lptree.c,v 1.13 2014/12/12 16:59:10 roberto Exp $ ** Copyright 2013, Lua.org & PUC-Rio (see 'lpeg.html' for license) */ @@ -209,9 +209,11 @@ static TTree *seqaux (TTree *tree, TTree *sib, int sibsize) { /* ** Add element 'idx' to 'ktable' of pattern at the top of the stack; ** create new 'ktable' if necessary. Return index of new element. +** If new element is nil, does not add it to table (as it would be +** useless) and returns 0, as ktable[0] is always nil. */ static int addtoktable (lua_State *L, int idx) { - if (idx == 0 || lua_isnil(L, idx)) /* no actual value to insert? */ + if (idx == 0) /* no actual value to insert? */ return 0; else { int n; @@ -220,11 +222,15 @@ static int addtoktable (lua_State *L, int idx) { if (n == 0) { /* is it empty/non-existent? */ lua_pop(L, 1); /* remove it */ lua_createtable(L, 1, 0); /* create a fresh table */ + lua_pushvalue(L, -1); /* make a copy */ + lua_setfenv(L, -3); /* set it as 'ktable' for pattern */ } - lua_pushvalue(L, idx); /* element to be added */ - lua_rawseti(L, -2, n + 1); - lua_setfenv(L, -2); /* set it as ktable for pattern */ - return n + 1; + if (!lua_isnil(L, idx)) { /* non-nil value? */ + lua_pushvalue(L, idx); /* element to be added */ + lua_rawseti(L, -2, ++n); + } + lua_pop(L, 1); /* remove 'ktable' */ + return n; } } @@ -525,7 +531,7 @@ static int lp_choice (lua_State *L) { static int lp_star (lua_State *L) { int size1; int n = (int)luaL_checkinteger(L, 2); - TTree *tree1 = gettree(L, 1, &size1); + TTree *tree1 = getpatt(L, 1, &size1); if (n >= 0) { /* seq tree1 (seq tree1 ... (seq tree1 (rep tree1))) */ TTree *tree = newtree(L, (n + 1) * (size1 + 1)); if (nullable(tree1)) @@ -634,8 +640,8 @@ static int lp_behind (lua_State *L) { TTree *tree; TTree *tree1 = getpatt(L, 1, NULL); int n = fixedlen(tree1); - luaL_argcheck(L, !hascaptures(tree1), 1, "pattern have captures"); luaL_argcheck(L, n > 0, 1, "pattern may not have fixed length"); + luaL_argcheck(L, !hascaptures(tree1), 1, "pattern have captures"); luaL_argcheck(L, n <= MAXBEHIND, 1, "pattern too long to look behind"); tree = newroot1sib(L, TBehind); tree->u.n = n; @@ -1139,7 +1145,7 @@ static int lp_type (lua_State *L) { int lp_gc (lua_State *L) { Pattern *p = getpattern(L, 1); if (p->codesize > 0) - reallocprog(L, p, 0); + realloccode(L, p, 0); return 0; } diff --git a/3rd/lpeg/lptypes.h b/3rd/lpeg/lptypes.h index d96554d0..b85c0c97 100644 --- a/3rd/lpeg/lptypes.h +++ b/3rd/lpeg/lptypes.h @@ -1,7 +1,7 @@ /* -** $Id: lptypes.h,v 1.8 2013/04/12 16:26:38 roberto Exp $ +** $Id: lptypes.h,v 1.10 2014/12/12 17:11:35 roberto Exp $ ** LPeg - PEG pattern matching for Lua -** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license) +** Copyright 2007-2014, Lua.org & PUC-Rio (see 'lpeg.html' for license) ** written by Roberto Ierusalimschy */ @@ -19,7 +19,7 @@ #include "lua.h" -#define VERSION "0.12" +#define VERSION "0.12.1" #define PATTERN_T "lpeg-pattern" @@ -56,7 +56,9 @@ /* maximum number of rules in a grammar */ -#define MAXRULES 200 +#if !defined(MAXRULES) +#define MAXRULES 1000 +#endif diff --git a/3rd/lpeg/lpvm.h b/3rd/lpeg/lpvm.h index 6a2a558d..757b9e13 100644 --- a/3rd/lpeg/lpvm.h +++ b/3rd/lpeg/lpvm.h @@ -1,5 +1,5 @@ /* -** $Id: lpvm.h,v 1.2 2013/04/03 20:37:18 roberto Exp $ +** $Id: lpvm.h,v 1.3 2014/02/21 13:06:41 roberto Exp $ */ #if !defined(lpvm_h) @@ -49,14 +49,9 @@ typedef union Instruction { } Instruction; -int getposition (lua_State *L, int t, int i); void printpatt (Instruction *p, int n); const char *match (lua_State *L, const char *o, const char *s, const char *e, Instruction *op, Capture *capture, int ptop); -int verify (lua_State *L, Instruction *op, const Instruction *p, - Instruction *e, int postable, int rule); -void checkrule (lua_State *L, Instruction *op, int from, int to, - int postable, int rule); #endif diff --git a/3rd/lpeg/makefile b/3rd/lpeg/makefile index 57a18fb3..7a8463e3 100644 --- a/3rd/lpeg/makefile +++ b/3rd/lpeg/makefile @@ -1,5 +1,5 @@ LIBNAME = lpeg -LUADIR = /usr/include/lua5.1/ +LUADIR = ../lua/ COPT = -O2 # COPT = -DLPEG_DEBUG -g @@ -22,7 +22,7 @@ CWARNS = -Wall -Wextra -pedantic \ # -Wunreachable-code \ -CFLAGS = $(CWARNS) $(COPT) -ansi -I$(LUADIR) -fPIC +CFLAGS = $(CWARNS) $(COPT) -std=c99 -I$(LUADIR) -fPIC CC = gcc FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o diff --git a/3rd/lpeg/test.lua b/3rd/lpeg/test.lua index 1d107ca5..fec5a85b 100755 --- a/3rd/lpeg/test.lua +++ b/3rd/lpeg/test.lua @@ -1,6 +1,6 @@ #!/usr/bin/env lua5.1 --- $Id: test.lua,v 1.101 2013/04/12 16:30:33 roberto Exp $ +-- $Id: test.lua,v 1.105 2014/12/12 17:00:39 roberto Exp $ -- require"strict" -- just to be pedantic @@ -170,8 +170,8 @@ assert(m.match( basiclookfor((#m.P(b) * 1) * m.Cp()), " ( (a)") == 7) a = {m.match(m.C(digit^1 * m.Cc"d") + m.C(letter^1 * m.Cc"l"), "123")} checkeq(a, {"123", "d"}) -a = {m.match(m.C(digit^1) * "d" * -1 + m.C(letter^1 * m.Cc"l"), "123d")} -checkeq(a, {"123"}) +-- bug in LPeg 0.12 (nil value does not create a 'ktable') +assert(m.match(m.Cc(nil), "") == nil) a = {m.match(m.C(digit^1 * m.Cc"d") + m.C(letter^1 * m.Cc"l"), "abcd")} checkeq(a, {"abcd", "l"}) @@ -194,6 +194,16 @@ checkeq(a, {1, 5}) t = {m.match({[1] = m.C(m.C(1) * m.V(1) + -1)}, "abc")} checkeq(t, {"abc", "a", "bc", "b", "c", "c", ""}) +-- bug in 0.12 ('hascapture' did not check for captures inside a rule) +do + local pat = m.P{ + 'S'; + S1 = m.C('abc') + 3, + S = #m.V('S1') -- rule has capture, but '#' must ignore it + } + assert(pat:match'abc' == 1) +end + -- test for small capture boundary for i = 250,260 do @@ -201,9 +211,8 @@ for i = 250,260 do assert(#m.match(m.C(m.C(i)), string.rep('a', i)) == i) end - -- tests for any*n and any*-n -for n = 1, 550 do +for n = 1, 550, 13 do local x_1 = string.rep('x', n - 1) local x = x_1 .. 'a' assert(not m.P(n):match(x_1)) @@ -345,8 +354,9 @@ checkeq(t, {hi = 10, ho = 20, 'a', 'b', 'c'}) -- test for error messages -local function checkerr (msg, ...) - assert(m.match({ m.P(msg) + 1 * m.V(1) }, select(2, pcall(...)))) +local function checkerr (msg, f, ...) + local st, err = pcall(f, ...) + assert(not st and m.match({ m.P(msg) + 1 * m.V(1) }, err)) end checkerr("rule '1' may be left recursive", m.match, { m.V(1) * 'a' }, "a") @@ -370,6 +380,13 @@ p = {'a', } checkerr("rule 'a' may be left recursive", m.match, p, "a") +-- Bug in peephole optimization of LPeg 0.12 (IJmp -> ICommit) +-- the next grammar has an original sequence IJmp -> ICommit -> IJmp L1 +-- that is optimized to ICommit L1 + +p = m.P { (m.P {m.P'abc'} + 'ayz') * m.V'y'; y = m.P'x' } +assert(p:match('abcx') == 5 and p:match('ayzx') == 5 and not p:match'abc') + -- tests for non-pattern as arguments to pattern functions @@ -488,7 +505,10 @@ assert(m.match(1 * m.B(1), 'a') == 2) assert(m.match(-m.B(1), 'a') == 1) assert(m.match(m.B(250), string.rep('a', 250)) == nil) assert(m.match(250 * m.B(250), string.rep('a', 250)) == 251) -assert(not pcall(m.B, 260)) + +-- look-behind with an open call +checkerr("pattern may not have fixed length", m.B, m.V'S1') +checkerr("too long to look behind", m.B, 260) B = #letter * -m.B(letter) + -letter * m.B(letter) x = m.Ct({ (B * m.Cp())^-1 * (1 * m.V(1) + m.P(true)) }) @@ -555,11 +575,11 @@ assert(not p:match(string.rep("011", 10001))) -- this grammar does need backtracking info. local lim = 10000 p = m.P{ '0' * m.V(1) + '0' } -assert(not pcall(m.match, p, string.rep("0", lim))) +checkerr("too many pending", m.match, p, string.rep("0", lim)) m.setmaxstack(2*lim) -assert(not pcall(m.match, p, string.rep("0", lim))) +checkerr("too many pending", m.match, p, string.rep("0", lim)) m.setmaxstack(2*lim + 4) -assert(pcall(m.match, p, string.rep("0", lim))) +assert(m.match(p, string.rep("0", lim)) == lim + 1) -- this repetition should not need stack space (only the call does) p = m.P{ ('a' * m.V(1))^0 * 'b' + 'c' } @@ -588,10 +608,10 @@ print("+") -- tests for argument captures -assert(not pcall(m.Carg, 0)) -assert(not pcall(m.Carg, -1)) -assert(not pcall(m.Carg, 2^18)) -assert(not pcall(m.match, m.Carg(1), 'a', 1)) +checkerr("invalid argument", m.Carg, 0) +checkerr("invalid argument", m.Carg, -1) +checkerr("invalid argument", m.Carg, 2^18) +checkerr("absent extra argument #1", m.match, m.Carg(1), 'a', 1) assert(m.match(m.Carg(1), 'a', 1, print) == print) x = {m.match(m.Carg(1) * m.Carg(2), '', 1, 10, 20)} checkeq(x, {10, 20}) @@ -644,14 +664,16 @@ assert(m.match(p, "aaaa") == 5) assert(m.match(p, "abaa") == 2) assert(not m.match(p, "baaa")) -assert(not pcall(m.match, function () return 2^20 end, s)) -assert(not pcall(m.match, function () return 0 end, s)) -assert(not pcall(m.match, function (s, i) return i - 1 end, s)) -assert(not pcall(m.match, m.P(1)^0 * function (_, i) return i - 1 end, s)) +checkerr("invalid position", m.match, function () return 2^20 end, s) +checkerr("invalid position", m.match, function () return 0 end, s) +checkerr("invalid position", m.match, function (s, i) return i - 1 end, s) +checkerr("invalid position", m.match, + m.P(1)^0 * function (_, i) return i - 1 end, s) assert(m.match(m.P(1)^0 * function (_, i) return i end * -1, s)) -assert(not pcall(m.match, m.P(1)^0 * function (_, i) return i + 1 end, s)) +checkerr("invalid position", m.match, + m.P(1)^0 * function (_, i) return i + 1 end, s) assert(m.match(m.P(function (s, i) return s:len() + 1 end) * -1, s)) -assert(not pcall(m.match, m.P(function (s, i) return s:len() + 2 end) * -1, s)) +checkerr("invalid position", m.match, m.P(function (s, i) return s:len() + 2 end) * -1, s) assert(not m.match(m.P(function (s, i) return s:len() end) * -1, s)) assert(m.match(m.P(1)^0 * function (_, i) return true end, s) == string.len(s) + 1) @@ -734,9 +756,9 @@ assert(m.match(m.Cs((m.P(1) / ".xx")^0), "abcd") == ".xx.xx.xx.xx") assert(m.match(m.Cp() * m.P(3) * m.Cp()/"%2%1%1 - %0 ", "abcde") == "411 - abc ") -assert(pcall(m.match, m.P(1)/"%0", "abc")) -assert(not pcall(m.match, m.P(1)/"%1", "abc")) -- out of range -assert(not pcall(m.match, m.P(1)/"%9", "abc")) -- out of range +assert(m.match(m.P(1)/"%0", "abc") == "a") +checkerr("invalid capture index", m.match, m.P(1)/"%1", "abc") +checkerr("invalid capture index", m.match, m.P(1)/"%9", "abc") p = m.C(1) p = p * p; p = p * p; p = p * p * m.C(1) / "%9 - %1" @@ -754,7 +776,7 @@ assert(m.match(m.C(1)^0 / "%9-%1-%0-%3", s) == "9-1-" .. s .. "-3") p = m.Cc('alo') * m.C(1) / "%1 - %2 - %1" assert(p:match'x' == 'alo - x - alo') -assert(not pcall(m.match, m.Cc(true) / "%1", "a")) +checkerr("invalid capture value (a boolean)", m.match, m.Cc(true) / "%1", "a") -- long strings for string capture l = 10000 @@ -782,35 +804,37 @@ checkeq(t, {a="b", c="du", xux="yuy"}) -- errors in accumulator capture --- very long match (forces fold to be a pair open-close) producing with -- no initial capture -assert(not pcall(m.match, m.Cf(m.P(500), print), string.rep('a', 600))) +checkerr("no initial value", m.match, m.Cf(m.P(5), print), 'aaaaaa') +-- no initial capture (very long match forces fold to be a pair open-close) +checkerr("no initial value", m.match, m.Cf(m.P(500), print), + string.rep('a', 600)) -- nested capture produces no initial value -assert(not pcall(m.match, m.Cf(m.P(1) / {}, print), "alo")) +checkerr("no initial value", m.match, m.Cf(m.P(1) / {}, print), "alo") -- tests for loop checker -local function haveloop (p) - assert(not pcall(function (p) return p^0 end, m.P(p))) +local function isnullable (p) + checkerr("may accept empty string", function (p) return p^0 end, m.P(p)) end -haveloop(m.P("x")^-4) +isnullable(m.P("x")^-4) assert(m.match(((m.P(0) + 1) * m.S"al")^0, "alo") == 3) assert(m.match((("x" + #m.P(1))^-4 * m.S"al")^0, "alo") == 3) -haveloop("") -haveloop(m.P("x")^0) -haveloop(m.P("x")^-1) -haveloop(m.P("x") + 1 + 2 + m.P("a")^-1) -haveloop(-m.P("ab")) -haveloop(- -m.P("ab")) -haveloop(# #(m.P("ab") + "xy")) -haveloop(- #m.P("ab")^0) -haveloop(# -m.P("ab")^1) -haveloop(#m.V(3)) -haveloop(m.V(3) + m.V(1) + m.P('a')^-1) -haveloop({[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(0)}) +isnullable("") +isnullable(m.P("x")^0) +isnullable(m.P("x")^-1) +isnullable(m.P("x") + 1 + 2 + m.P("a")^-1) +isnullable(-m.P("ab")) +isnullable(- -m.P("ab")) +isnullable(# #(m.P("ab") + "xy")) +isnullable(- #m.P("ab")^0) +isnullable(# -m.P("ab")^1) +isnullable(#m.V(3)) +isnullable(m.V(3) + m.V(1) + m.P('a')^-1) +isnullable({[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(0)}) assert(m.match(m.P{[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(1)}^0, "abc") == 3) assert(m.match(m.P""^-3, "a") == 1) @@ -894,8 +918,8 @@ print"+" -- tests for back references -assert(not pcall(m.match, m.Cb('x'), '')) -assert(not pcall(m.match, m.Cg(1, 'a') * m.Cb('b'), 'a')) +checkerr("back reference 'x' not found", m.match, m.Cb('x'), '') +checkerr("back reference 'b' not found", m.match, m.Cg(1, 'a') * m.Cb('b'), 'a') p = m.Cg(m.C(1) * m.C(1), "k") * m.Ct(m.Cb("k")) t = p:match("ab") @@ -1370,8 +1394,7 @@ assert(rev:match"0123456789" == "9876543210") -- testing error messages in re local function errmsg (p, err) - local s, msg = pcall(re.compile, p) - assert(not s and string.find(msg, err)) + checkerr(err, re.compile, p) end errmsg('aaaa', "rule 'aaaa'")