diff options
author | Sérgio Medeiros <sqmedeiros@gmail.com> | 2016-09-01 15:19:28 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-01 15:19:28 -0300 |
commit | 63e712a2fe81d917389463b7d4b069b0d18a2ffa (patch) | |
tree | 4ba8b110d8a783329c707ea25f15c6326a96c097 | |
parent | 727ee89b490f91a59cd4147c717aa35939940a57 (diff) | |
parent | d0fbb9d070d5675fba11a7f8c7d0ce95b55dcb3e (diff) | |
download | lpeglabel-63e712a2fe81d917389463b7d4b069b0d18a2ffa.tar.gz lpeglabel-63e712a2fe81d917389463b7d4b069b0d18a2ffa.tar.bz2 lpeglabel-63e712a2fe81d917389463b7d4b069b0d18a2ffa.zip |
Merge pull request #14 from undecidabot/master
Adding documentation for the recovery operator
-rw-r--r-- | README.md | 122 | ||||
-rw-r--r-- | examples/recoveryOpFail.lua | 19 |
2 files changed, 128 insertions, 13 deletions
@@ -35,7 +35,11 @@ of the new functions provided by LpegLabel: | |||
35 | <td>Throws label <code>l</code></td></tr> | 35 | <td>Throws label <code>l</code></td></tr> |
36 | <tr><td><a href="#f-lc"><code>lpeglabel.Lc (p1, p2, l1, ..., ln)</code></a></td> | 36 | <tr><td><a href="#f-lc"><code>lpeglabel.Lc (p1, p2, l1, ..., ln)</code></a></td> |
37 | <td>Matches <code>p1</code> and tries to match <code>p2</code> | 37 | <td>Matches <code>p1</code> and tries to match <code>p2</code> |
38 | if the matching of <code>p1</code> gives one of l<sub>1</sub>, ..., l<sub>n</sub> | 38 | if the matching of <code>p1</code> gives one of l<sub>1</sub>, ..., l<sub>n</sub> |
39 | </td></tr> | ||
40 | <tr><td><a href="#f-rec"><code>lpeglabel.Rec (p1, p2 [, l1, ..., ln])</code></a></td> | ||
41 | <td>Like <code>Lc</code> but does not reset the position of the parser | ||
42 | when trying <code>p2</code>. By default, it catches regular PEG failures | ||
39 | </td></tr> | 43 | </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>relabel</em> module. Equivalent to <code>lpeg.T(l)</code> |
@@ -64,7 +68,7 @@ A label must be an integer between 0 and 255. | |||
64 | The label 0 is equivalent to the regular failure of PEGs. | 68 | The label 0 is equivalent to the regular failure of PEGs. |
65 | 69 | ||
66 | 70 | ||
67 | #### <a name="f-lc"></a><code>lpeglabel.Lc(p1, p2, l1, ..., ln)</code># | 71 | #### <a name="f-lc"></a><code>lpeglabel.Lc(p1, p2, l1, ..., ln)</code> |
68 | 72 | ||
69 | Returns a pattern equivalent to a *labeled ordered choice*. | 73 | Returns a pattern equivalent to a *labeled ordered choice*. |
70 | If the matching of `p1` gives one of the labels `l1, ..., ln`, | 74 | If the matching of `p1` gives one of the labels `l1, ..., ln`, |
@@ -79,6 +83,15 @@ When using this function, the user should take care to build a left-associative | |||
79 | labeled ordered choice pattern. | 83 | labeled ordered choice pattern. |
80 | 84 | ||
81 | 85 | ||
86 | #### <a name="f-rec"></a><code>lpeglabel.Rec(p1, p2 [, l1, ..., ln])</code> | ||
87 | |||
88 | The *recovery operator* is similar to labeled order choice except | ||
89 | that the matching of `p2` is tried from the failure position of `p1`. | ||
90 | |||
91 | If no label is provided, the regular PEG failure is caught | ||
92 | i.e. `lpeg.Rec(p1, p2)` is equivalent to `lpeg.Rec(p1, p2, 0)`. | ||
93 | |||
94 | |||
82 | #### <a name="re-t"></a><code>%{l}</code> | 95 | #### <a name="re-t"></a><code>%{l}</code> |
83 | 96 | ||
84 | Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. | 97 | Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. |
@@ -420,3 +433,108 @@ print(m.match(g, "one,two")) --> 8 | |||
420 | print(m.match(g, "one two")) --> expecting ',' | 433 | print(m.match(g, "one two")) --> expecting ',' |
421 | print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier | 434 | print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier |
422 | ``` | 435 | ``` |
436 | |||
437 | #### Error Recovery | ||
438 | |||
439 | By using labeled ordered choice or the recovery operator, when a label | ||
440 | is thrown, the parser may record the error and still continue parsing | ||
441 | to find more errors. We can even record the error right away without | ||
442 | actually throwing a label (relying on the regular PEG failure instead). | ||
443 | Below we rewrite the arithmetic expression example and modify | ||
444 | the `expect` function to use the recovery operator for error recovery: | ||
445 | |||
446 | ```lua | ||
447 | local lpeg = require"lpeglabel" | ||
448 | |||
449 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | ||
450 | local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg | ||
451 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec | ||
452 | |||
453 | local labels = { | ||
454 | {"NoExp", "no expression found"}, | ||
455 | {"Extra", "extra characters found after the expression"}, | ||
456 | {"ExpTerm", "expected a term after the operator"}, | ||
457 | {"ExpExp", "expected an expression after the parenthesis"}, | ||
458 | {"MisClose", "missing a closing ')' after the expression"}, | ||
459 | } | ||
460 | |||
461 | local function labelindex(labname) | ||
462 | for i, elem in ipairs(labels) do | ||
463 | if elem[1] == labname then | ||
464 | return i | ||
465 | end | ||
466 | end | ||
467 | error("could not find label: " .. labname) | ||
468 | end | ||
469 | |||
470 | local function expect(patt, labname, recpatt) | ||
471 | local i = labelindex(labname) | ||
472 | local function recorderror(input, pos, errors) | ||
473 | table.insert(errors, {i, pos}) | ||
474 | return true | ||
475 | end | ||
476 | if not recpatt then recpatt = P"" end | ||
477 | return Rec(patt, Cmt(Carg(1), recorderror) * recpatt) | ||
478 | end | ||
479 | |||
480 | local num = R("09")^1 / tonumber | ||
481 | local op = S("+-*/") | ||
482 | |||
483 | local function compute(tokens) | ||
484 | local result = tokens[1] | ||
485 | for i = 2, #tokens, 2 do | ||
486 | if tokens[i] == '+' then | ||
487 | result = result + tokens[i+1] | ||
488 | elseif tokens[i] == '-' then | ||
489 | result = result - tokens[i+1] | ||
490 | elseif tokens[i] == '*' then | ||
491 | result = result * tokens[i+1] | ||
492 | elseif tokens[i] == '/' then | ||
493 | result = result / tokens[i+1] | ||
494 | else | ||
495 | error('unknown operation: ' .. tokens[i]) | ||
496 | end | ||
497 | end | ||
498 | return result | ||
499 | end | ||
500 | |||
501 | |||
502 | local g = P { | ||
503 | "Exp", | ||
504 | Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute; | ||
505 | Operand = expect(V"Term", "ExpTerm", Cc(0)); | ||
506 | Term = num + V"Group"; | ||
507 | Group = "(" * V"InnerExp" * expect(")", "MisClose"); | ||
508 | InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); | ||
509 | } | ||
510 | |||
511 | g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") | ||
512 | |||
513 | local function eval(input) | ||
514 | local errors = {} | ||
515 | local result, label, suffix = g:match(input, 1, errors) | ||
516 | if #errors == 0 then | ||
517 | return result | ||
518 | else | ||
519 | local out = {} | ||
520 | for i, err in ipairs(errors) do | ||
521 | local pos = err[2] | ||
522 | local msg = labels[err[1]][2] | ||
523 | table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") | ||
524 | end | ||
525 | return nil, table.concat(out, "\n") | ||
526 | end | ||
527 | end | ||
528 | |||
529 | print(eval "98-76*(54/32)") | ||
530 | --> 37.125 | ||
531 | |||
532 | print(eval "-1+(1-(1*2))/2") | ||
533 | --> syntax error: no expression found (at index 1) | ||
534 | |||
535 | print(eval "(1+1-1*(2/2+)-():") | ||
536 | --> syntax error: expected a term after the operator (at index 13) | ||
537 | --> syntax error: expected an expression after the parenthesis (at index 16) | ||
538 | --> syntax error: missing a closing ')' after the expression (at index 17) | ||
539 | --> syntax error: extra characters found after the expression (at index 17) | ||
540 | ``` | ||
diff --git a/examples/recoveryOpFail.lua b/examples/recoveryOpFail.lua index d65b9e0..6ddc6a2 100644 --- a/examples/recoveryOpFail.lua +++ b/examples/recoveryOpFail.lua | |||
@@ -1,7 +1,7 @@ | |||
1 | local lpeg = require"lpeglabel" | 1 | local lpeg = require"lpeglabel" |
2 | 2 | ||
3 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | 3 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V |
4 | local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt | 4 | local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg |
5 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec | 5 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec |
6 | 6 | ||
7 | local labels = { | 7 | local labels = { |
@@ -21,16 +21,14 @@ local function labelindex(labname) | |||
21 | error("could not find label: " .. labname) | 21 | error("could not find label: " .. labname) |
22 | end | 22 | end |
23 | 23 | ||
24 | local errors = {} | ||
25 | |||
26 | local function expect(patt, labname, recpatt) | 24 | local function expect(patt, labname, recpatt) |
27 | local i = labelindex(labname) | 25 | local i = labelindex(labname) |
28 | function recorderror(input, pos) | 26 | local function recorderror(input, pos, errors) |
29 | table.insert(errors, {i, pos}) | 27 | table.insert(errors, {i, pos}) |
30 | return true | 28 | return true |
31 | end | 29 | end |
32 | if not recpatt then recpatt = P"" end | 30 | if not recpatt then recpatt = P"" end |
33 | return Rec(patt, Cmt("", recorderror) * recpatt) | 31 | return Rec(patt, Cmt(Carg(1), recorderror) * recpatt) |
34 | end | 32 | end |
35 | 33 | ||
36 | local num = R("09")^1 / tonumber | 34 | local num = R("09")^1 / tonumber |
@@ -57,18 +55,18 @@ end | |||
57 | 55 | ||
58 | local g = P { | 56 | local g = P { |
59 | "Exp", | 57 | "Exp", |
60 | Exp = Ct(V"Term" * (C(op) * V"OpRecov")^0) / compute; | 58 | Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute; |
61 | OpRecov = V"Operand"; | ||
62 | Operand = expect(V"Term", "ExpTerm", Cc(0)); | 59 | Operand = expect(V"Term", "ExpTerm", Cc(0)); |
63 | Term = num + V"Group"; | 60 | Term = num + V"Group"; |
64 | Group = "(" * V"InnerExp" * expect(")", "MisClose", ""); | 61 | Group = "(" * V"InnerExp" * expect(")", "MisClose"); |
65 | InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); | 62 | InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); |
66 | } | 63 | } |
67 | 64 | ||
68 | g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") | 65 | g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") |
69 | 66 | ||
70 | local function eval(input) | 67 | local function eval(input) |
71 | local result, label, suffix = g:match(input) | 68 | local errors = {} |
69 | local result, label, suffix = g:match(input, 1, errors) | ||
72 | if #errors == 0 then | 70 | if #errors == 0 then |
73 | return result | 71 | return result |
74 | else | 72 | else |
@@ -78,7 +76,6 @@ local function eval(input) | |||
78 | local msg = labels[err[1]][2] | 76 | local msg = labels[err[1]][2] |
79 | table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") | 77 | table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") |
80 | end | 78 | end |
81 | errors = {} | ||
82 | return nil, table.concat(out, "\n") | 79 | return nil, table.concat(out, "\n") |
83 | end | 80 | end |
84 | end | 81 | end |