aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md122
-rw-r--r--examples/recoveryOpFail.lua19
2 files changed, 128 insertions, 13 deletions
diff --git a/README.md b/README.md
index b882968..1f1bdff 100644
--- a/README.md
+++ b/README.md
@@ -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.
64The label 0 is equivalent to the regular failure of PEGs. 68The 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
69Returns a pattern equivalent to a *labeled ordered choice*. 73Returns a pattern equivalent to a *labeled ordered choice*.
70If the matching of `p1` gives one of the labels `l1, ..., ln`, 74If 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
79labeled ordered choice pattern. 83labeled ordered choice pattern.
80 84
81 85
86#### <a name="f-rec"></a><code>lpeglabel.Rec(p1, p2 [, l1, ..., ln])</code>
87
88The *recovery operator* is similar to labeled order choice except
89that the matching of `p2` is tried from the failure position of `p1`.
90
91If no label is provided, the regular PEG failure is caught
92i.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
84Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. 97Syntax of *relabel* module. Equivalent to `lpeg.T(l)`.
@@ -420,3 +433,108 @@ print(m.match(g, "one,two")) --> 8
420print(m.match(g, "one two")) --> expecting ',' 433print(m.match(g, "one two")) --> expecting ','
421print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier 434print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier
422``` 435```
436
437#### Error Recovery
438
439By using labeled ordered choice or the recovery operator, when a label
440is thrown, the parser may record the error and still continue parsing
441to find more errors. We can even record the error right away without
442actually throwing a label (relying on the regular PEG failure instead).
443Below we rewrite the arithmetic expression example and modify
444the `expect` function to use the recovery operator for error recovery:
445
446```lua
447local lpeg = require"lpeglabel"
448
449local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V
450local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg
451local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec
452
453local 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
461local 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)
468end
469
470local 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)
478end
479
480local num = R("09")^1 / tonumber
481local op = S("+-*/")
482
483local 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
499end
500
501
502local 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
511g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra")
512
513local 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
527end
528
529print(eval "98-76*(54/32)")
530--> 37.125
531
532print(eval "-1+(1-(1*2))/2")
533--> syntax error: no expression found (at index 1)
534
535print(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 @@
1local lpeg = require"lpeglabel" 1local lpeg = require"lpeglabel"
2 2
3local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V 3local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V
4local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt 4local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg
5local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec 5local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec
6 6
7local labels = { 7local labels = {
@@ -21,16 +21,14 @@ local function labelindex(labname)
21 error("could not find label: " .. labname) 21 error("could not find label: " .. labname)
22end 22end
23 23
24local errors = {}
25
26local function expect(patt, labname, recpatt) 24local 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)
34end 32end
35 33
36local num = R("09")^1 / tonumber 34local num = R("09")^1 / tonumber
@@ -57,18 +55,18 @@ end
57 55
58local g = P { 56local 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
68g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") 65g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra")
69 66
70local function eval(input) 67local 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
84end 81end