diff options
| author | Sergio Queiroz <sqmedeiros@gmail.com> | 2016-11-14 17:15:27 -0300 |
|---|---|---|
| committer | Sergio Queiroz <sqmedeiros@gmail.com> | 2016-11-14 17:15:27 -0300 |
| commit | 448762908fd822fbc101a4fe66fac9cd8aa913b5 (patch) | |
| tree | c7bc865aa66f557be6d3d9d422bcaec42a8be0be | |
| parent | fd28f9d9e54f33bf7ae3a5e12dc71478f9c91aea (diff) | |
| download | lpeglabel-448762908fd822fbc101a4fe66fac9cd8aa913b5.tar.gz lpeglabel-448762908fd822fbc101a4fe66fac9cd8aa913b5.tar.bz2 lpeglabel-448762908fd822fbc101a4fe66fac9cd8aa913b5.zip | |
Changing documentation and examples with recovery
| -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 | } |
