diff options
-rw-r--r-- | README.md | 296 | ||||
-rw-r--r-- | examples/listId1.lua | 12 | ||||
-rw-r--r-- | examples/listId2.lua | 13 | ||||
-rw-r--r-- | examples/listId2Rec2.lua | 67 | ||||
-rw-r--r-- | lptree.c | 2 | ||||
-rw-r--r-- | lpvm.c | 8 |
6 files changed, 329 insertions, 69 deletions
@@ -10,43 +10,47 @@ LPegLabel is a conservative extension of the | |||
10 | [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg) | 10 | [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg) |
11 | library that provides an implementation of Parsing | 11 | library that provides an implementation of Parsing |
12 | Expression Grammars (PEGs) with labeled failures. | 12 | Expression Grammars (PEGs) with labeled failures. |
13 | Labels can be used to signal different kinds of erros | 13 | Labels can be used to signal different kinds of errors |
14 | and to specify which alternative in a labeled ordered | 14 | and to specify which recovery pattern should handle a |
15 | choice should handle a given label. Labels can also be | 15 | given label. Labels can also be combined with the standard |
16 | combined with the standard patterns of LPeg. | 16 | patterns of LPeg. |
17 | 17 | ||
18 | This document describes the new functions available | 18 | This document describes the new functions available |
19 | in LpegLabel and presents some examples of usage. | 19 | in LpegLabel and presents some examples of usage. |
20 | For a more detailed discussion about PEGs with labeled failures | ||
21 | please see [A Parsing Machine for Parsing Expression | ||
22 | Grammars with Labeled Failures](https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxzcW1lZGVpcm9zfGd4OjMzZmE3YzM0Y2E2MGM5Y2M). | ||
23 | |||
24 | 20 | ||
25 | In LPegLabel, the result of an unsuccessful matching | 21 | In LPegLabel, the result of an unsuccessful matching |
26 | is a triple **nil, lab, sfail**, where **lab** | 22 | is a triple **nil, lab, sfail**, where **lab** |
27 | is the label associated with the failure, and | 23 | is the label associated with the failure, and |
28 | **sfail** is the suffix input being matched when | 24 | **sfail** is the suffix input being matched when |
29 | **lab** was thrown. Below there is a brief summary | 25 | **lab** was thrown. |
26 | |||
27 | With labeled failures it is possible to distinguish | ||
28 | between a regular failure and an error. Usually, a | ||
29 | regular failure is produced when the matching of a | ||
30 | character fails, and it is caught by an ordered choice. | ||
31 | An error, by its turn, is produced by the throw operator | ||
32 | and may be caught by the recovery operator. | ||
33 | |||
34 | Below there is a brief summary | ||
30 | of the new functions provided by LpegLabel: | 35 | of the new functions provided by LpegLabel: |
31 | 36 | ||
32 | <table border="1"> | 37 | <table border="1"> |
33 | <tbody><tr><td><b>Function</b></td><td><b>Description</b></td></tr> | 38 | <tbody><tr><td><b>Function</b></td><td><b>Description</b></td></tr> |
34 | <tr><td><a href="#f-t"><code>lpeglabel.T (l)</code></a></td> | 39 | <tr><td><a href="#f-t"><code>lpeglabelrec.T (l)</code></a></td> |
35 | <td>Throws label <code>l</code></td></tr> | 40 | <td>Throws a label <code>l</code> to signal an error</td></tr> |
36 | <tr><td><a href="#f-lc"><code>lpeglabel.Lc (p1, p2, l1, ..., ln)</code></a></td> | 41 | <tr><td><a href="#f-rec"><code>lpeglabelrec.Rec (p1, p2, l1, [l2, ..., ln])</code></a></td> |
37 | <td>Matches <code>p1</code> and tries to match <code>p2</code> | 42 | <td>Specifies a recovery pattern <code>p2</code> for <code>p1</code>, |
38 | if the matching of <code>p1</code> gives one of l<sub>1</sub>, ..., l<sub>n</sub> | 43 | when the matching of <code>p1</code> gives one of the labels l1, ..., ln.</td></tr> |
39 | </td></tr> | ||
40 | <tr><td><a href="#re-t"><code>%{l}</code></a></td> | 44 | <tr><td><a href="#re-t"><code>%{l}</code></a></td> |
41 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.T(l)</code> | 45 | <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.T(l)</code> |
42 | </td></tr> | 46 | </td></tr> |
43 | <tr><td><a href="#re-lc"><code>p1 /{l1, ..., ln} p2</code></a></td> | 47 | <tr><td><a href="#re-rec"><code>p1 //{l1, ..., ln} p2</code></a></td> |
44 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.Lc(p1, p2, l1, ..., ln)</code> | 48 | <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.Rec(p1, p2, l1, ..., ln)</code> |
45 | </td></tr> | 49 | </td></tr> |
46 | <tr><td><a href="#re-line"><code>relabel.calcline(subject, i)</code></a></td> | 50 | <tr><td><a href="#re-line"><code>relabelrec.calcline(subject, i)</code></a></td> |
47 | <td>Calculates line and column information regarding position <i>i</i> of the subject</code> | 51 | <td>Calculates line and column information regarding position <i>i</i> of the subject</code> |
48 | </td></tr> | 52 | </td></tr> |
49 | <tr><td><a href="#re-setl"><code>relabel.setlabels (tlabel)</code></a></td> | 53 | <tr><td><a href="#re-setl"><code>relabelrec.setlabels (tlabel)</code></a></td> |
50 | <td>Allows to specicify a table with mnemonic labels. | 54 | <td>Allows to specicify a table with mnemonic labels. |
51 | </td></tr> | 55 | </td></tr> |
52 | </tbody></table> | 56 | </tbody></table> |
@@ -55,55 +59,44 @@ of the new functions provided by LpegLabel: | |||
55 | ### Functions | 59 | ### Functions |
56 | 60 | ||
57 | 61 | ||
58 | #### <a name="f-t"></a><code>lpeglabel.T(l)</code> | 62 | #### <a name="f-t"></a><code>lpeglabelrec.T(l)</code> |
59 | 63 | ||
60 | 64 | ||
61 | Returns a pattern that throws the label `l`. | 65 | Returns a pattern that throws the label `l`. |
62 | A label must be an integer between 0 and 255. | 66 | A label must be an integer between 1 and 255. |
63 | |||
64 | The label 0 is equivalent to the regular failure of PEGs. | ||
65 | 67 | ||
66 | 68 | ||
67 | #### <a name="f-lc"></a><code>lpeglabel.Lc(p1, p2, l1, ..., ln)</code># | 69 | #### <a name="f-rec"></a><code>lpeglabelrec.Rec(p1, p2, l1, ..., ln)</code> |
68 | 70 | ||
69 | Returns a pattern equivalent to a *labeled ordered choice*. | 71 | Returns a *recovery pattern*. |
70 | If the matching of `p1` gives one of the labels `l1, ..., ln`, | 72 | If the matching of `p1` gives one of the labels `l1, ..., ln`, |
71 | then the matching of `p2` is tried from the same position. Otherwise, | 73 | then the matching of `p2` is tried from the failure position of `p1`. |
72 | the result of the matching of `p1` is the pattern's result. | 74 | Otherwise, the result of the matching of `p1` is the pattern's result. |
73 | 75 | ||
74 | The labeled ordered choice `lpeg.Lc(p1, p2, 0)` is equivalent to the | ||
75 | regular ordered choice `p1 / p2`. | ||
76 | |||
77 | Although PEG's ordered choice is associative, the labeled ordered choice is not. | ||
78 | When using this function, the user should take care to build a left-associative | ||
79 | labeled ordered choice pattern. | ||
80 | 76 | ||
81 | 77 | ||
82 | #### <a name="re-t"></a><code>%{l}</code> | 78 | #### <a name="re-t"></a><code>%{l}</code> |
83 | 79 | ||
84 | Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. | 80 | Syntax of *relabelrec* module. Equivalent to `lpeg.T(l)`. |
85 | 81 | ||
86 | 82 | ||
87 | #### <a name="re-lc"></a><code>p1 /{l1, ..., ln} p2</code> | 83 | #### <a name="re-lc"></a><code>p1 //{l1, ..., ln} p2</code> |
88 | 84 | ||
89 | Syntax of *relabel* module. Equivalent to `lpeg.Lc(p1, p2, l1, ..., ln)`. | 85 | Syntax of *relabelrec* module. Equivalent to `lpeglabelrec.Rec(p1, p2, l1, ..., ln)`. |
90 | 86 | ||
91 | The `/{}` operator is left-associative. | 87 | The `//{}` operator is left-associative. |
92 | 88 | ||
93 | A grammar can use both choice operators (`/` and `/{}`), | ||
94 | but a single choice can not mix them. That is, the parser of `relabel` | ||
95 | module will not recognize a pattern as `p1 / p2 /{l1} p3`. | ||
96 | 89 | ||
97 | 90 | ||
98 | #### <a name="re-line"></a><code>relabel.calcline (subject, i)</code> | 91 | #### <a name="re-line"></a><code>relabelrec.calcline (subject, i)</code> |
99 | 92 | ||
100 | Returns line and column information regarding position <i>i</i> of the subject. | 93 | Returns line and column information regarding position <i>i</i> of the subject. |
101 | 94 | ||
102 | 95 | ||
103 | #### <a name="re-setl"></a><code>relabel.setlabels (tlabel)</code> | 96 | #### <a name="re-setl"></a><code>relabelrec.setlabels (tlabel)</code> |
104 | 97 | ||
105 | Allows to specicify a table with labels. They keys of | 98 | Allows to specicify a table with labels. They keys of |
106 | `tlabel` must be integers between 0 and 255, | 99 | `tlabel` must be integers between 1 and 255, |
107 | and the associated values should be strings. | 100 | and the associated values should be strings. |
108 | 101 | ||
109 | 102 | ||
@@ -122,8 +115,8 @@ is thrown when there is an error matching an identifier | |||
122 | or a comma: | 115 | or a comma: |
123 | 116 | ||
124 | ```lua | 117 | ```lua |
125 | local m = require'lpeglabel' | 118 | local m = require'lpeglabelrec' |
126 | local re = require'relabel' | 119 | local re = require'relabelrec' |
127 | 120 | ||
128 | local g = m.P{ | 121 | local g = m.P{ |
129 | "S", | 122 | "S", |
@@ -160,7 +153,7 @@ In this example we could think about writing rule <em>List</em> as follows: | |||
160 | List = ((m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)))^0, | 153 | List = ((m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)))^0, |
161 | ``` | 154 | ``` |
162 | 155 | ||
163 | but when matching this expression agains the end of input | 156 | but when matching this expression against the end of input |
164 | we would get a failure whose associated label would be **2**, | 157 | we would get a failure whose associated label would be **2**, |
165 | and this would cause the failure of the *whole* repetition. | 158 | and this would cause the failure of the *whole* repetition. |
166 | 159 | ||
@@ -168,12 +161,12 @@ and this would cause the failure of the *whole* repetition. | |||
168 | ##### Mnemonics instead of numbers | 161 | ##### Mnemonics instead of numbers |
169 | 162 | ||
170 | In the previous example we could have created a table | 163 | In the previous example we could have created a table |
171 | with the error messages to improve the readbility of the PEG. | 164 | with the error messages to improve the readability of the PEG. |
172 | Below we rewrite the previous grammar following this approach: | 165 | Below we rewrite the previous grammar following this approach: |
173 | 166 | ||
174 | ```lua | 167 | ```lua |
175 | local m = require'lpeglabel' | 168 | local m = require'lpeglabelrec' |
176 | local re = require'relabel' | 169 | local re = require'relabelrec' |
177 | 170 | ||
178 | local terror = {} | 171 | local terror = {} |
179 | 172 | ||
@@ -210,14 +203,99 @@ print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expec | |||
210 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | 203 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' |
211 | ``` | 204 | ``` |
212 | 205 | ||
206 | #### Error Recovery | ||
207 | |||
208 | By using the recovery operator we can specify a recovery pattern that | ||
209 | should be matched when a label is thrown. After matching this pattern, | ||
210 | and possibly recording the error, the parser can continue parsing to | ||
211 | find more errors. | ||
212 | |||
213 | Below we rewrite the previous example to illustrate a recovery strategy. | ||
214 | Grammar `g` remains the same, but we add a recovery grammar `grec` that | ||
215 | handles the labels thrown by `g`. | ||
216 | |||
217 | arithmetic expression example and modify | ||
218 | the `expect` function to use the recovery operator for error recovery: | ||
219 | |||
220 | ```lua | ||
221 | local m = require'lpeglabelrec' | ||
222 | local re = require'relabelrec' | ||
223 | |||
224 | local terror = {} | ||
225 | |||
226 | local function newError(s) | ||
227 | table.insert(terror, s) | ||
228 | return #terror | ||
229 | end | ||
230 | |||
231 | local errUndef = newError("undefined") | ||
232 | local errId = newError("expecting an identifier") | ||
233 | local errComma = newError("expecting ','") | ||
234 | |||
235 | local id = m.R'az'^1 | ||
236 | |||
237 | local g = m.P{ | ||
238 | "S", | ||
239 | S = m.V"Id" * m.V"List", | ||
240 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", | ||
241 | Id = m.V"Sp" * id + m.T(errId), | ||
242 | Comma = m.V"Sp" * "," + m.T(errComma), | ||
243 | Sp = m.S" \n\t"^0, | ||
244 | } | ||
245 | |||
246 | local subject, errors | ||
247 | |||
248 | function recorderror(pos, lab) | ||
249 | local line, col = re.calcline(subject, pos) | ||
250 | table.insert(errors, { line = line, col = col, msg = terror[lab] }) | ||
251 | end | ||
252 | |||
253 | function record (lab) | ||
254 | return (m.Cp() * m.Cc(lab)) / recorderror | ||
255 | end | ||
256 | |||
257 | function sync (p) | ||
258 | return (-p * m.P(1))^0 | ||
259 | end | ||
260 | |||
261 | local grec = m.P{ | ||
262 | "S", | ||
263 | S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), | ||
264 | ErrComma = record(errComma) * sync(-m.P(1) + id), | ||
265 | ErrId = record(errId) * sync(-m.P(1) + ",") | ||
266 | } | ||
267 | |||
268 | |||
269 | function mymatch (g, s) | ||
270 | errors = {} | ||
271 | subject = s | ||
272 | local r, e, sfail = g:match(s) | ||
273 | if #errors > 0 then | ||
274 | local out = {} | ||
275 | for i, err in ipairs(errors) do | ||
276 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg | ||
277 | table.insert(out, msg) | ||
278 | end | ||
279 | return nil, table.concat(out, "\n") | ||
280 | end | ||
281 | return r | ||
282 | end | ||
283 | |||
284 | print(mymatch(grec, "one,two")) | ||
285 | print(mymatch(grec, "one two three")) | ||
286 | print(mymatch(grec, "1,\n two, \n3,")) | ||
287 | print(mymatch(grec, "one\n two123, \nthree,")) | ||
288 | ``` | ||
289 | |||
213 | 290 | ||
214 | ##### *relabel* syntax | 291 | |
292 | ##### *relabelrec* syntax | ||
215 | 293 | ||
216 | Now we rewrite the previous example using the syntax | 294 | Now we rewrite the previous example using the syntax |
217 | supported by *relabel*: | 295 | supported by *relabelrec*: |
218 | 296 | ||
219 | ```lua | 297 | ```lua |
220 | local re = require 'relabel' | 298 | local re = require 'relabelrec' |
221 | 299 | ||
222 | local g = re.compile[[ | 300 | local g = re.compile[[ |
223 | S <- Id List | 301 | S <- Id List |
@@ -252,7 +330,7 @@ With the help of function *setlabels* we can also rewrite the previous example t | |||
252 | mnemonic labels instead of plain numbers: | 330 | mnemonic labels instead of plain numbers: |
253 | 331 | ||
254 | ```lua | 332 | ```lua |
255 | local re = require 'relabel' | 333 | local re = require 'relabelrec' |
256 | 334 | ||
257 | local errinfo = { | 335 | local errinfo = { |
258 | {"errUndef", "undefined"}, | 336 | {"errUndef", "undefined"}, |
@@ -293,6 +371,11 @@ print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expec | |||
293 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | 371 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' |
294 | ``` | 372 | ``` |
295 | 373 | ||
374 | |||
375 | |||
376 | |||
377 | |||
378 | |||
296 | #### Arithmetic Expressions | 379 | #### Arithmetic Expressions |
297 | 380 | ||
298 | Here's an example of an LPegLabel grammar that make its own function called | 381 | 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 | |||
420 | print(m.match(g, "one two")) --> expecting ',' | 503 | print(m.match(g, "one two")) --> expecting ',' |
421 | print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier | 504 | print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier |
422 | ``` | 505 | ``` |
506 | |||
507 | #### Error Recovery | ||
508 | |||
509 | By using labeled ordered choice or the recovery operator, when a label | ||
510 | is thrown, the parser may record the error and still continue parsing | ||
511 | to find more errors. We can even record the error right away without | ||
512 | actually throwing a label (relying on the regular PEG failure instead). | ||
513 | Below we rewrite the arithmetic expression example and modify | ||
514 | the `expect` function to use the recovery operator for error recovery: | ||
515 | |||
516 | ```lua | ||
517 | local lpeg = require"lpeglabel" | ||
518 | |||
519 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | ||
520 | local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg | ||
521 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec | ||
522 | |||
523 | local labels = { | ||
524 | {"NoExp", "no expression found"}, | ||
525 | {"Extra", "extra characters found after the expression"}, | ||
526 | {"ExpTerm", "expected a term after the operator"}, | ||
527 | {"ExpExp", "expected an expression after the parenthesis"}, | ||
528 | {"MisClose", "missing a closing ')' after the expression"}, | ||
529 | } | ||
530 | |||
531 | local function labelindex(labname) | ||
532 | for i, elem in ipairs(labels) do | ||
533 | if elem[1] == labname then | ||
534 | return i | ||
535 | end | ||
536 | end | ||
537 | error("could not find label: " .. labname) | ||
538 | end | ||
539 | |||
540 | local function expect(patt, labname, recpatt) | ||
541 | local i = labelindex(labname) | ||
542 | local function recorderror(input, pos, errors) | ||
543 | table.insert(errors, {i, pos}) | ||
544 | return true | ||
545 | end | ||
546 | if not recpatt then recpatt = P"" end | ||
547 | return Rec(patt, Cmt(Carg(1), recorderror) * recpatt) | ||
548 | end | ||
549 | |||
550 | local num = R("09")^1 / tonumber | ||
551 | local op = S("+-*/") | ||
552 | |||
553 | local function compute(tokens) | ||
554 | local result = tokens[1] | ||
555 | for i = 2, #tokens, 2 do | ||
556 | if tokens[i] == '+' then | ||
557 | result = result + tokens[i+1] | ||
558 | elseif tokens[i] == '-' then | ||
559 | result = result - tokens[i+1] | ||
560 | elseif tokens[i] == '*' then | ||
561 | result = result * tokens[i+1] | ||
562 | elseif tokens[i] == '/' then | ||
563 | result = result / tokens[i+1] | ||
564 | else | ||
565 | error('unknown operation: ' .. tokens[i]) | ||
566 | end | ||
567 | end | ||
568 | return result | ||
569 | end | ||
570 | |||
571 | |||
572 | local g = P { | ||
573 | "Exp", | ||
574 | Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute; | ||
575 | Operand = expect(V"Term", "ExpTerm", Cc(0)); | ||
576 | Term = num + V"Group"; | ||
577 | Group = "(" * V"InnerExp" * expect(")", "MisClose"); | ||
578 | InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); | ||
579 | } | ||
580 | |||
581 | g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") | ||
582 | |||
583 | local function eval(input) | ||
584 | local errors = {} | ||
585 | local result, label, suffix = g:match(input, 1, errors) | ||
586 | if #errors == 0 then | ||
587 | return result | ||
588 | else | ||
589 | local out = {} | ||
590 | for i, err in ipairs(errors) do | ||
591 | local pos = err[2] | ||
592 | local msg = labels[err[1]][2] | ||
593 | table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") | ||
594 | end | ||
595 | return nil, table.concat(out, "\n") | ||
596 | end | ||
597 | end | ||
598 | |||
599 | print(eval "98-76*(54/32)") | ||
600 | --> 37.125 | ||
601 | |||
602 | print(eval "-1+(1-(1*2))/2") | ||
603 | --> syntax error: no expression found (at index 1) | ||
604 | |||
605 | print(eval "(1+1-1*(2/2+)-():") | ||
606 | --> syntax error: expected a term after the operator (at index 13) | ||
607 | --> syntax error: expected an expression after the parenthesis (at index 16) | ||
608 | --> syntax error: missing a closing ')' after the expression (at index 17) | ||
609 | --> syntax error: extra characters found after the expression (at index 17) | ||
610 | ``` | ||
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 @@ | |||
1 | local m = require'lpeglabel' | 1 | local m = require'lpeglabelrec' |
2 | local re = require'relabel' | 2 | local re = require'relabelrec' |
3 | |||
4 | local id = m.R'az'^1 | ||
3 | 5 | ||
4 | local g = m.P{ | 6 | local g = m.P{ |
5 | "S", | 7 | "S", |
6 | S = m.V"Id" * m.V"List", | 8 | S = m.V"Id" * m.V"List", |
7 | List = -m.P(1) + (m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)) * m.V"List", | 9 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", |
8 | Id = m.V"Sp" * m.R'az'^1, | 10 | Id = m.V"Sp" * id + m.T(1), |
9 | Comma = m.V"Sp" * ",", | 11 | Comma = m.V"Sp" * "," + m.T(2), |
10 | Sp = m.S" \n\t"^0, | 12 | Sp = m.S" \n\t"^0, |
11 | } | 13 | } |
12 | 14 | ||
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 @@ | |||
1 | local m = require'lpeglabel' | 1 | local m = require'lpeglabelrec' |
2 | local re = require'relabel' | 2 | local re = require'relabelrec' |
3 | 3 | ||
4 | local terror = {} | 4 | local terror = {} |
5 | 5 | ||
@@ -12,15 +12,18 @@ local errUndef = newError("undefined") | |||
12 | local errId = newError("expecting an identifier") | 12 | local errId = newError("expecting an identifier") |
13 | local errComma = newError("expecting ','") | 13 | local errComma = newError("expecting ','") |
14 | 14 | ||
15 | local id = m.R'az'^1 | ||
16 | |||
15 | local g = m.P{ | 17 | local g = m.P{ |
16 | "S", | 18 | "S", |
17 | S = m.V"Id" * m.V"List", | 19 | S = m.V"Id" * m.V"List", |
18 | List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", | 20 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", |
19 | Id = m.V"Sp" * m.R'az'^1, | 21 | Id = m.V"Sp" * id + m.T(errId), |
20 | Comma = m.V"Sp" * ",", | 22 | Comma = m.V"Sp" * "," + m.T(errComma), |
21 | Sp = m.S" \n\t"^0, | 23 | Sp = m.S" \n\t"^0, |
22 | } | 24 | } |
23 | 25 | ||
26 | |||
24 | function mymatch (g, s) | 27 | function mymatch (g, s) |
25 | local r, e, sfail = g:match(s) | 28 | local r, e, sfail = g:match(s) |
26 | if not r then | 29 | 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 @@ | |||
1 | local m = require'lpeglabelrec' | ||
2 | local re = require'relabelrec' | ||
3 | |||
4 | local terror = {} | ||
5 | |||
6 | local function newError(s) | ||
7 | table.insert(terror, s) | ||
8 | return #terror | ||
9 | end | ||
10 | |||
11 | local errUndef = newError("undefined") | ||
12 | local errId = newError("expecting an identifier") | ||
13 | local errComma = newError("expecting ','") | ||
14 | |||
15 | local id = m.R'az'^1 | ||
16 | |||
17 | local g = m.P{ | ||
18 | "S", | ||
19 | S = m.V"Id" * m.V"List", | ||
20 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", | ||
21 | Id = m.V"Sp" * id + m.T(errId), | ||
22 | Comma = m.V"Sp" * "," + m.T(errComma), | ||
23 | Sp = m.S" \n\t"^0, | ||
24 | } | ||
25 | |||
26 | local subject, errors | ||
27 | |||
28 | function recorderror(pos, lab) | ||
29 | local line, col = re.calcline(subject, pos) | ||
30 | table.insert(errors, { line = line, col = col, msg = terror[lab] }) | ||
31 | end | ||
32 | |||
33 | function record (lab) | ||
34 | return (m.Cp() * m.Cc(lab)) / recorderror | ||
35 | end | ||
36 | |||
37 | function sync (p) | ||
38 | return (-p * m.P(1))^0 | ||
39 | end | ||
40 | |||
41 | local grec = m.P{ | ||
42 | "S", | ||
43 | S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), | ||
44 | ErrComma = record(errComma) * sync(-m.P(1) + id), | ||
45 | ErrId = record(errId) * sync(-m.P(1) + ",") | ||
46 | } | ||
47 | |||
48 | |||
49 | function mymatch (g, s) | ||
50 | errors = {} | ||
51 | subject = s | ||
52 | local r, e, sfail = g:match(s) | ||
53 | if #errors > 0 then | ||
54 | local out = {} | ||
55 | for i, err in ipairs(errors) do | ||
56 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg | ||
57 | table.insert(out, msg) | ||
58 | end | ||
59 | return nil, table.concat(out, "\n") | ||
60 | end | ||
61 | return r | ||
62 | end | ||
63 | |||
64 | print(mymatch(grec, "one,two")) | ||
65 | print(mymatch(grec, "one two three")) | ||
66 | print(mymatch(grec, "1,\n two, \n3,")) | ||
67 | print(mymatch(grec, "one\n two123, \nthree,")) | ||
@@ -28,7 +28,7 @@ const byte numsiblings[] = { | |||
28 | 0, 0, 2, 1, /* call, opencall, rule, grammar */ | 28 | 0, 0, 2, 1, /* call, opencall, rule, grammar */ |
29 | 1, /* behind */ | 29 | 1, /* behind */ |
30 | 1, 1, /* capture, runtime capture */ | 30 | 1, 1, /* capture, runtime capture */ |
31 | 0, 2, 2 /* labeled failure throw, labeled choice, recovery */ | 31 | 0, 2 /* labeled failure throw, recovery */ |
32 | }; | 32 | }; |
33 | 33 | ||
34 | 34 | ||
@@ -336,8 +336,8 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
336 | do { /* remove pending calls */ | 336 | do { /* remove pending calls */ |
337 | assert(pstack > getstackbase(L, ptop)); | 337 | assert(pstack > getstackbase(L, ptop)); |
338 | auxlab = (--pstack)->ls; | 338 | auxlab = (--pstack)->ls; |
339 | } while (auxlab == NULL || (pstack->p != &giveup && labelf != LFAIL && !testlabel(pstack->ls->cs, *labelf))); | 339 | } while (auxlab == NULL || !(pstack->p == &giveup || testlabel(pstack->ls->cs, *labelf))); |
340 | if (pstack->p == &giveup || pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */ | 340 | if (pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */ |
341 | stack = pstack; | 341 | stack = pstack; |
342 | s = stack->s; | 342 | s = stack->s; |
343 | if (ndyncap > 0) /* is there matchtime captures? */ | 343 | 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, | |||
349 | stack->s = NULL; | 349 | stack->s = NULL; |
350 | stack->p = pk; /* save return address */ | 350 | stack->p = pk; /* save return address */ |
351 | stack->ls = NULL; | 351 | stack->ls = NULL; |
352 | stack->caplevel = captop; /* TODO: necessary?? */ | 352 | stack->caplevel = captop; /* TODO: really necessary?? */ |
353 | stack++; | 353 | stack++; |
354 | } | 354 | } |
355 | p = pstack->p; | 355 | p = pstack->p; |
@@ -366,7 +366,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e, | |||
366 | res = resdyncaptures(L, fr, s - o, e - o); /* get result */ | 366 | res = resdyncaptures(L, fr, s - o, e - o); /* get result */ |
367 | if (res == -1) { /* fail? */ | 367 | if (res == -1) { /* fail? */ |
368 | *labelf = LFAIL; /* labeled failure */ | 368 | *labelf = LFAIL; /* labeled failure */ |
369 | *sfail = (const char *) s; /* TODO: ??? */ | 369 | *sfail = (const char *) s; |
370 | pk = NULL; | 370 | pk = NULL; |
371 | goto fail; | 371 | goto fail; |
372 | } | 372 | } |