diff options
-rw-r--r-- | README.md | 542 | ||||
-rw-r--r-- | examples/expRec.lua | 50 | ||||
-rw-r--r-- | examples/expRecAut.lua | 52 | ||||
-rw-r--r-- | examples/listId2Rec2.lua | 2 | ||||
-rw-r--r-- | examples/listIdRe2.lua | 31 |
5 files changed, 376 insertions, 301 deletions
@@ -37,13 +37,13 @@ Below there is a brief summary of the new functions provided by LpegLabel: | |||
37 | <tbody><tr><td><b>Function</b></td><td><b>Description</b></td></tr> | 37 | <tbody><tr><td><b>Function</b></td><td><b>Description</b></td></tr> |
38 | <tr><td><a href="#f-t"><code>lpeglabelrec.T (l)</code></a></td> | 38 | <tr><td><a href="#f-t"><code>lpeglabelrec.T (l)</code></a></td> |
39 | <td>Throws a label <code>l</code> to signal an error</td></tr> | 39 | <td>Throws a label <code>l</code> to signal an error</td></tr> |
40 | <tr><td><a href="#f-rec"><code>lpeglabelrec.Rec (p1, p2, l1, [l2, ..., ln])</code></a></td> | 40 | <tr><td><a href="#f-rec"><code>lpeglabelrec.Rec (p1, p2, l1 [, l2, ..., ln])</code></a></td> |
41 | <td>Specifies a recovery pattern <code>p2</code> for <code>p1</code>, | 41 | <td>Specifies a recovery pattern <code>p2</code> for <code>p1</code>, |
42 | when the matching of <code>p1</code> gives one of the labels l1, ..., ln.</td></tr> | 42 | when the matching of <code>p1</code> gives one of the labels l1, ..., ln.</td></tr> |
43 | <tr><td><a href="#re-t"><code>%{l}</code></a></td> | 43 | <tr><td><a href="#re-t"><code>%{l}</code></a></td> |
44 | <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.T(l)</code> | 44 | <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.T(l)</code> |
45 | </td></tr> | 45 | </td></tr> |
46 | <tr><td><a href="#re-rec"><code>p1 //{l1, ..., ln} p2</code></a></td> | 46 | <tr><td><a href="#re-rec"><code>p1 //{l1 [, l2, ..., ln} p2</code></a></td> |
47 | <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.Rec(p1, p2, l1, ..., ln)</code> | 47 | <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.Rec(p1, p2, l1, ..., ln)</code> |
48 | </td></tr> | 48 | </td></tr> |
49 | <tr><td><a href="#re-line"><code>relabelrec.calcline(subject, i)</code></a></td> | 49 | <tr><td><a href="#re-line"><code>relabelrec.calcline(subject, i)</code></a></td> |
@@ -173,41 +173,38 @@ By using the `Rec` function we can specify a recovery pattern that | |||
173 | should be matched when a label is thrown. After matching the recovery | 173 | should be matched when a label is thrown. After matching the recovery |
174 | pattern, and possibly recording the error, the parser will resume | 174 | pattern, and possibly recording the error, the parser will resume |
175 | the <em>regular</em> matching. For example, in the example below | 175 | the <em>regular</em> matching. For example, in the example below |
176 | we expect to match rule `A`, but in case label 42 is thrown | 176 | we expect to match rule `A`, but when a failure occur the label 42 |
177 | then we will try to match `recp`: | 177 | is thrown and then we will try to match the recovery pattern `recp`: |
178 | ```lua | 178 | ```lua |
179 | local m = require'lpeglabelrec' | 179 | local m = require'lpeglabelrec' |
180 | 180 | ||
181 | local recp = m.P"oast" | 181 | local recp = m.P"oast" |
182 | 182 | ||
183 | local g = m.P{ | 183 | local g = m.P{ |
184 | "S", | 184 | "S", |
185 | S = m.Rec(m.V"A", recp, 42) * ".", | 185 | S = m.Rec(m.V"A", recp, 42) * ".", |
186 | A = m.P"t" * (m.P("est") + m.T(42)) | 186 | A = m.P"t" * (m.P"est" + m.T(42)) |
187 | } | 187 | } |
188 | 188 | ||
189 | print(g:match("test.")) --> 6 | 189 | print(g:match("test.")) --> 6 |
190 | |||
191 | print(g:match("toast.")) --> 7 | 190 | print(g:match("toast.")) --> 7 |
192 | 191 | print(g:match("oast.")) --> nil 0 oast. | |
193 | print(g:match("oast.")) --> nil 0 oast. | 192 | print(g:match("toward.")) --> nil 0 ward. |
194 | |||
195 | print(g:match("toward.")) --> nil 0 ward. | ||
196 | ``` | 193 | ``` |
197 | When trying to match 'toast.', in rule `A` the first | 194 | When trying to match subject 'toast.', in rule `A` the first |
198 | 't' is matched, and then label 42 is thrown, with the associated | 195 | 't' is matched, then the matching of `m.P"est"` fails and label 42 |
199 | inpux suffix 'oast.'. In rule `S` this label is caught | 196 | is thrown, with the associated inpux suffix 'oast.'. In rule |
200 | and the recovery pattern matches 'oast', so pattern `'.'` | 197 | `S` label 42 is caught and the recovery pattern matches 'oast', |
201 | matches the rest of the input. | 198 | so pattern `'.'` matches the rest of the input. |
202 | 199 | ||
203 | When matching 'oast.', pattern `m.P"t"` fails, and | 200 | When matching subject 'oast.', pattern `m.P"t"` fails, and |
204 | the result of the matching is <b>nil, 0, oast.</b>. | 201 | the result of the matching is <b>nil, 0, oast.</b>. |
205 | 202 | ||
206 | When matching 'toward.', label 42 is throw, with the associated | 203 | When matching 'toward.', label 42 is thrown after matching 't', |
207 | input suffix 'oward.'. The matching of the recovery pattern fails to, | 204 | with the associated input suffix 'oward.'. As the matching of the |
208 | so the result of the matching is <b>nil, 0, ward.</b>. | 205 | recovery pattern fails, the result is <b>nil, 0, ward.</b>. |
209 | 206 | ||
210 | Usually, the recovery pattern is an expression that never fails. | 207 | Usually, the recovery pattern is an expression that does not fail. |
211 | In the previous example, we could have used `(m.P(1) - m.P".")^0` | 208 | In the previous example, we could have used `(m.P(1) - m.P".")^0` |
212 | as the recovery pattern. | 209 | as the recovery pattern. |
213 | 210 | ||
@@ -216,11 +213,14 @@ to use a recovery strategy. Grammar `g` remains the same, but we add a | |||
216 | recovery grammar `grec` that handles the labels thrown by `g`. | 213 | recovery grammar `grec` that handles the labels thrown by `g`. |
217 | 214 | ||
218 | In grammar `grec` we use functions `record` and `sync`. | 215 | In grammar `grec` we use functions `record` and `sync`. |
219 | Function `record` gives us a pattern that captures two | 216 | Function `record`, plus function `recorderror`, will help |
220 | values: the current subject position (where a label was thrown) | 217 | us to save the input position where a label was thrown, |
221 | and the label itself. These values will be used to record | 218 | while function `sync` will give us a synchronization pattern, |
222 | all the errors found. Function `sync` give us synchronization | 219 | that consumes the input while is not possible to match a given |
223 | pattern, that macthes the input | 220 | pattern `p`. |
221 | |||
222 | When the matching of an identifier fails, a defaul value ('NONE') | ||
223 | is provided. | ||
224 | 224 | ||
225 | ```lua | 225 | ```lua |
226 | local m = require'lpeglabelrec' | 226 | local m = require'lpeglabelrec' |
@@ -266,8 +266,8 @@ end | |||
266 | local grec = m.P{ | 266 | local grec = m.P{ |
267 | "S", | 267 | "S", |
268 | S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), | 268 | S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), |
269 | ErrComma = record(errComma) * sync(-m.P(1) + id), | 269 | ErrComma = record(errComma) * sync(id), |
270 | ErrId = record(errId) * sync(-m.P(1) + ",") | 270 | ErrId = record(errId) * sync(m.P",") |
271 | } | 271 | } |
272 | 272 | ||
273 | 273 | ||
@@ -281,58 +281,41 @@ function mymatch (g, s) | |||
281 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg | 281 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg |
282 | table.insert(out, msg) | 282 | table.insert(out, msg) |
283 | end | 283 | end |
284 | return nil, table.concat(out, "\n") | 284 | return nil, table.concat(out, "\n") .. "\n" |
285 | end | 285 | end |
286 | return r | 286 | return r |
287 | end | 287 | end |
288 | 288 | ||
289 | print(mymatch(grec, "one,two")) | 289 | print(mymatch(grec, "one,two")) |
290 | -- Captures (separated by ';'): one; two; | ||
291 | -- Syntactic errors found: 0 | ||
292 | |||
290 | print(mymatch(grec, "one two three")) | 293 | print(mymatch(grec, "one two three")) |
294 | -- Captures (separated by ';'): one; two; three; | ||
295 | -- Syntactic errors found: 2 | ||
296 | -- Error at line 1 (col 4): expecting ',' | ||
297 | -- Error at line 1 (col 8): expecting ',' | ||
298 | |||
291 | print(mymatch(grec, "1,\n two, \n3,")) | 299 | print(mymatch(grec, "1,\n two, \n3,")) |
300 | -- Captures (separated by ';'): NONE; two; NONE; NONE; | ||
301 | -- Syntactic errors found: 3 | ||
302 | -- Error at line 1 (col 1): expecting an identifier | ||
303 | -- Error at line 2 (col 6): expecting an identifier | ||
304 | -- Error at line 3 (col 2): expecting an identifier | ||
305 | |||
292 | print(mymatch(grec, "one\n two123, \nthree,")) | 306 | print(mymatch(grec, "one\n two123, \nthree,")) |
307 | -- Captures (separated by ';'): one; two; three; NONE; | ||
308 | -- Syntactic errors found: 3 | ||
309 | -- Error at line 2 (col 1): expecting ',' | ||
310 | -- Error at line 2 (col 5): expecting ',' | ||
311 | -- Error at line 3 (col 6): expecting an identifier | ||
293 | ``` | 312 | ``` |
294 | 313 | ||
295 | |||
296 | |||
297 | ##### *relabelrec* syntax | 314 | ##### *relabelrec* syntax |
298 | 315 | ||
299 | Now we rewrite the previous example using the syntax | 316 | Below we describe again a grammar that matches a list of identifiers, |
300 | supported by *relabelrec*: | 317 | now using the syntax supported by *relabelrec*, where `//{}` is the |
301 | 318 | recovery operator, and `%{}` is the throw operator: | |
302 | ```lua | ||
303 | local re = require 'relabelrec' | ||
304 | |||
305 | local g = re.compile[[ | ||
306 | S <- Id List | ||
307 | List <- !. / (',' / %{2}) (Id / %{1}) List | ||
308 | Id <- Sp [a-z]+ | ||
309 | Comma <- Sp ',' | ||
310 | Sp <- %s* | ||
311 | ]] | ||
312 | |||
313 | function mymatch (g, s) | ||
314 | local r, e, sfail = g:match(s) | ||
315 | if not r then | ||
316 | local line, col = re.calcline(s, #s - #sfail) | ||
317 | local msg = "Error at line " .. line .. " (col " .. col .. ")" | ||
318 | if e == 1 then | ||
319 | return r, msg .. ": expecting an identifier before '" .. sfail .. "'" | ||
320 | elseif e == 2 then | ||
321 | return r, msg .. ": expecting ',' before '" .. sfail .. "'" | ||
322 | else | ||
323 | return r, msg | ||
324 | end | ||
325 | end | ||
326 | return r | ||
327 | end | ||
328 | |||
329 | print(mymatch(g, "one,two")) --> 8 | ||
330 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | ||
331 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | ||
332 | ``` | ||
333 | |||
334 | With the help of function *setlabels* we can also rewrite the previous example to use | ||
335 | mnemonic labels instead of plain numbers: | ||
336 | 319 | ||
337 | ```lua | 320 | ```lua |
338 | local re = require 'relabelrec' | 321 | local re = require 'relabelrec' |
@@ -355,64 +338,124 @@ re.setlabels(labels) | |||
355 | 338 | ||
356 | local g = re.compile[[ | 339 | local g = re.compile[[ |
357 | S <- Id List | 340 | S <- Id List |
358 | List <- !. / (',' / %{errComma}) (Id / %{errId}) List | 341 | List <- !. / Comma Id List |
359 | Id <- Sp [a-z]+ | 342 | Id <- Sp {[a-z]+} / %{errId} |
360 | Comma <- Sp ',' | 343 | Comma <- Sp ',' / %{errComma} |
361 | Sp <- %s* | 344 | Sp <- %s* |
362 | ]] | 345 | ]] |
363 | 346 | ||
347 | local errors | ||
348 | |||
349 | function recorderror (subject, pos, label) | ||
350 | local line, col = re.calcline(subject, pos) | ||
351 | table.insert(errors, { line = line, col = col, msg = errmsgs[labels[label]] }) | ||
352 | return true | ||
353 | end | ||
354 | |||
355 | function sync (p) | ||
356 | return '( !(' .. p .. ') .)*' | ||
357 | end | ||
358 | |||
359 | local grec = re.compile( | ||
360 | "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" .. | ||
361 | "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('[a-z]+') .. "\n" .. | ||
362 | "ErrId <- ('' -> 'errId' => recorderror) " .. sync('","') .. "-> default" | ||
363 | , {g = g, recorderror = recorderror, default = "NONE"} | ||
364 | ) | ||
365 | |||
364 | function mymatch (g, s) | 366 | function mymatch (g, s) |
365 | local r, e, sfail = g:match(s) | 367 | errors = {} |
366 | if not r then | 368 | subject = s |
367 | local line, col = re.calcline(s, #s - #sfail) | 369 | io.write("Input: ", s, "\n") |
368 | local msg = "Error at line " .. line .. " (col " .. col .. "): " | 370 | local r = { g:match(s) } |
369 | return r, msg .. errmsgs[e] .. " before '" .. sfail .. "'" | 371 | io.write("Captures (separated by ';'): ") |
372 | for k, v in pairs(r) do | ||
373 | io.write(v .. "; ") | ||
374 | end | ||
375 | io.write("\nSyntactic errors found: " .. #errors) | ||
376 | if #errors > 0 then | ||
377 | io.write("\n") | ||
378 | local out = {} | ||
379 | for i, err in ipairs(errors) do | ||
380 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg | ||
381 | table.insert(out, msg) | ||
382 | end | ||
383 | io.write(table.concat(out, "\n")) | ||
370 | end | 384 | end |
385 | print("\n") | ||
371 | return r | 386 | return r |
372 | end | 387 | end |
373 | 388 | ||
374 | print(mymatch(g, "one,two")) --> 8 | 389 | print(mymatch(grec, "one,two")) |
375 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | 390 | -- Captures (separated by ';'): one; two; |
376 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | 391 | -- Syntactic errors found: 0 |
377 | ``` | ||
378 | |||
379 | 392 | ||
393 | print(mymatch(grec, "one two three")) | ||
394 | -- Captures (separated by ';'): one; two; three; | ||
395 | -- Syntactic errors found: 2 | ||
396 | -- Error at line 1 (col 4): expecting ',' | ||
397 | -- Error at line 1 (col 8): expecting ',' | ||
380 | 398 | ||
399 | print(mymatch(grec, "1,\n two, \n3,")) | ||
400 | -- Captures (separated by ';'): NONE; two; NONE; NONE; | ||
401 | -- Syntactic errors found: 3 | ||
402 | -- Error at line 1 (col 1): expecting an identifier | ||
403 | -- Error at line 2 (col 6): expecting an identifier | ||
404 | -- Error at line 3 (col 2): expecting an identifier | ||
381 | 405 | ||
406 | print(mymatch(grec, "one\n two123, \nthree,")) | ||
407 | -- Captures (separated by ';'): one; two; three; NONE; | ||
408 | -- Syntactic errors found: 3 | ||
409 | -- Error at line 2 (col 1): expecting ',' | ||
410 | -- Error at line 2 (col 5): expecting ',' | ||
411 | -- Error at line 3 (col 6): expecting an identifier | ||
412 | ``` | ||
382 | 413 | ||
383 | 414 | ||
384 | #### Arithmetic Expressions | 415 | #### Arithmetic Expressions |
385 | 416 | ||
386 | Here's an example of an LPegLabel grammar that make its own function called | 417 | Here's an example of an LPegLabel grammar that matches an expression. |
387 | 'expect', which takes a pattern and a label as parameters and throws the label | 418 | We have used a function `expect`, that takes a pattern `patt` and a label as |
388 | if the pattern fails to be matched. This function can be extended later on to | 419 | parameters and builds a new pattern that throws this label when `patt` |
389 | record all errors encountered once error recovery is implemented. | 420 | fails. |
390 | 421 | ||
391 | ```lua | 422 | When a subexpression is syntactically invalid, a default value of 1000 |
392 | local lpeg = require"lpeglabel" | 423 | is provided by the recovery pattern, so the evaluation of an expression |
424 | should always produce a numeric value. | ||
393 | 425 | ||
394 | local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T | 426 | In this example, we can see that it may be a tedious and error prone |
427 | task to build manually the recovery grammar `grec`. In the next example | ||
428 | we will show how to build the recovery grammar in a more automatic way. | ||
429 | |||
430 | ```lua | ||
431 | local m = require"lpeglabelrec" | ||
432 | local re = require"relabelrec" | ||
395 | 433 | ||
396 | local labels = { | 434 | local labels = { |
397 | {"NoExp", "no expression found"}, | 435 | {"ExpTermFirst", "expected an expression"}, |
398 | {"Extra", "extra characters found after the expression"}, | 436 | {"ExpTermOp", "expected a term after the operator"}, |
399 | {"ExpTerm", "expected a term after the operator"}, | ||
400 | {"ExpExp", "expected an expression after the parenthesis"}, | ||
401 | {"MisClose", "missing a closing ')' after the expression"}, | 437 | {"MisClose", "missing a closing ')' after the expression"}, |
402 | } | 438 | } |
403 | 439 | ||
404 | local function expect(patt, labname) | 440 | local function labelindex(labname) |
405 | for i, elem in ipairs(labels) do | 441 | for i, elem in ipairs(labels) do |
406 | if elem[1] == labname then | 442 | if elem[1] == labname then |
407 | return patt + T(i) | 443 | return i |
408 | end | 444 | end |
409 | end | 445 | end |
410 | |||
411 | error("could not find label: " .. labname) | 446 | error("could not find label: " .. labname) |
412 | end | 447 | end |
413 | 448 | ||
414 | local num = R("09")^1 / tonumber | 449 | local errors, subject |
415 | local op = S("+-*/") | 450 | |
451 | local function expect(patt, labname) | ||
452 | local i = labelindex(labname) | ||
453 | return patt + m.T(i) | ||
454 | end | ||
455 | |||
456 | |||
457 | local num = m.R("09")^1 / tonumber | ||
458 | local op = m.S("+-") | ||
416 | 459 | ||
417 | local function compute(tokens) | 460 | local function compute(tokens) |
418 | local result = tokens[1] | 461 | local result = tokens[1] |
@@ -421,10 +464,6 @@ local function compute(tokens) | |||
421 | result = result + tokens[i+1] | 464 | result = result + tokens[i+1] |
422 | elseif tokens[i] == '-' then | 465 | elseif tokens[i] == '-' then |
423 | result = result - tokens[i+1] | 466 | result = result - tokens[i+1] |
424 | elseif tokens[i] == '*' then | ||
425 | result = result * tokens[i+1] | ||
426 | elseif tokens[i] == '/' then | ||
427 | result = result / tokens[i+1] | ||
428 | else | 467 | else |
429 | error('unknown operation: ' .. tokens[i]) | 468 | error('unknown operation: ' .. tokens[i]) |
430 | end | 469 | end |
@@ -432,128 +471,128 @@ local function compute(tokens) | |||
432 | return result | 471 | return result |
433 | end | 472 | end |
434 | 473 | ||
435 | local g = P { | 474 | local g = m.P { |
436 | "Exp", | 475 | "Exp", |
437 | Exp = Ct(V"Term" * (C(op) * expect(V"Term", "ExpTerm"))^0) / compute; | 476 | Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, |
438 | Term = num + V"Group"; | 477 | OperandFirst = expect(m.V"Term", "ExpTermFirst"), |
439 | Group = "(" * expect(V"Exp", "ExpExp") * expect(")", "MisClose"); | 478 | Operand = expect(m.V"Term", "ExpTermOp"), |
479 | Term = num + m.V"Group", | ||
480 | Group = "(" * m.V"Exp" * expect(")", "MisClose"), | ||
440 | } | 481 | } |
441 | 482 | ||
442 | g = expect(g, "NoExp") * expect(-P(1), "Extra") | 483 | function recorderror(pos, lab) |
443 | 484 | local line, col = re.calcline(subject, pos) | |
444 | local function eval(input) | 485 | table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) |
445 | local result, label, suffix = g:match(input) | ||
446 | if result ~= nil then | ||
447 | return result | ||
448 | else | ||
449 | local pos = input:len() - suffix:len() + 1 | ||
450 | local msg = labels[label][2] | ||
451 | return nil, "syntax error: " .. msg .. " (at index " .. pos .. ")" | ||
452 | end | ||
453 | end | 486 | end |
454 | 487 | ||
455 | print(eval "98-76*(54/32)") | 488 | function record (labname) |
456 | --> 37.125 | 489 | return (m.Cp() * m.Cc(labelindex(labname))) / recorderror |
457 | 490 | end | |
458 | print(eval "(1+1-1*2/2") | ||
459 | --> syntax error: missing a closing ')' after the expression (at index 11) | ||
460 | |||
461 | print(eval "(1+)-1*(2/2)") | ||
462 | --> syntax error: expected a term after the operator (at index 4) | ||
463 | |||
464 | print(eval "(1+1)-1*(/2)") | ||
465 | --> syntax error: expected an expression after the parenthesis (at index 10) | ||
466 | |||
467 | print(eval "1+(1-(1*2))/2x") | ||
468 | --> syntax error: extra chracters found after the expression (at index 14) | ||
469 | |||
470 | print(eval "-1+(1-(1*2))/2") | ||
471 | --> syntax error: no expression found (at index 1) | ||
472 | ``` | ||
473 | |||
474 | #### Catching labels | ||
475 | |||
476 | When a label is thrown, the grammar itself can handle this label | ||
477 | by using the labeled ordered choice. Below we rewrite the example | ||
478 | of the list of identifiers to show this feature: | ||
479 | |||
480 | |||
481 | ```lua | ||
482 | local m = require'lpeglabel' | ||
483 | |||
484 | local terror = {} | ||
485 | 491 | ||
486 | local function newError(s) | 492 | function sync (p) |
487 | table.insert(terror, s) | 493 | return (-p * m.P(1))^0 |
488 | return #terror | ||
489 | end | 494 | end |
490 | 495 | ||
491 | local errUndef = newError("undefined") | 496 | function defaultValue (p) |
492 | local errId = newError("expecting an identifier") | 497 | return p or m.Cc(1000) |
493 | local errComma = newError("expecting ','") | 498 | end |
494 | 499 | ||
495 | local g = m.P{ | 500 | local grec = m.P { |
496 | "S", | 501 | "S", |
497 | S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), | 502 | S = m.Rec(m.V"A", m.V"ErrExpTermFirst", labelindex("ExpTermFirst")), |
498 | m.V"ErrComma", errComma), | 503 | A = m.Rec(m.V"Sg", m.V"ErrExpTermOp", labelindex("ExpTermOp")), |
499 | List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", | 504 | Sg = m.Rec(g, m.V"ErrMisClose", labelindex("MisClose")), |
500 | Id = m.V"Sp" * m.R'az'^1, | 505 | ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), |
501 | Comma = m.V"Sp" * ",", | 506 | ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), |
502 | Sp = m.S" \n\t"^0, | 507 | ErrMisClose = record("MisClose") * sync(m.P")") * defaultValue(m.P""), |
503 | ErrId = m.Cc(errId) / terror, | ||
504 | ErrComma = m.Cc(errComma) / terror | ||
505 | } | 508 | } |
509 | |||
510 | local function eval(input) | ||
511 | errors = {} | ||
512 | io.write("Input: ", input, "\n") | ||
513 | subject = input | ||
514 | local result, label, suffix = grec:match(input) | ||
515 | io.write("Syntactic errors found: " .. #errors, "\n") | ||
516 | if #errors > 0 then | ||
517 | local out = {} | ||
518 | for i, err in ipairs(errors) do | ||
519 | local pos = err.col | ||
520 | local msg = err.msg | ||
521 | table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") | ||
522 | end | ||
523 | print(table.concat(out, "\n")) | ||
524 | end | ||
525 | io.write("Result = ") | ||
526 | return result | ||
527 | end | ||
506 | 528 | ||
507 | print(m.match(g, "one,two")) --> 8 | 529 | print(eval "90-70-(5)+3") |
508 | print(m.match(g, "one two")) --> expecting ',' | 530 | -- Syntactic errors found: 0 |
509 | print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier | 531 | -- Result = 18 |
532 | |||
533 | print(eval "15+") | ||
534 | -- Syntactic errors found: 1 | ||
535 | -- syntax error: expected a term after the operator (at index 3) | ||
536 | -- Result = 1015 | ||
537 | |||
538 | print(eval "-2") | ||
539 | -- Syntactic errors found: 1 | ||
540 | -- syntax error: expected an expression (at index 1) | ||
541 | -- Result = 998 | ||
542 | |||
543 | print(eval "1+()+") | ||
544 | -- Syntactic errors found: 2 | ||
545 | -- syntax error: expected an expression (at index 4) | ||
546 | -- syntax error: expected a term after the operator (at index 5) | ||
547 | -- Result = 2001 | ||
548 | |||
549 | print(eval "1+(") | ||
550 | -- Syntactic errors found: 2 | ||
551 | -- syntax error: expected an expression (at index 3) | ||
552 | -- syntax error: missing a closing ')' after the expression (at index 3) | ||
553 | -- Result = 1001 | ||
554 | |||
555 | print(eval "3)") | ||
556 | -- Syntactic errors found: 0 | ||
557 | -- Result = 3 | ||
510 | ``` | 558 | ``` |
511 | 559 | ||
512 | #### Error Recovery | 560 | #### Automatically Building the Recovery Grammar |
513 | 561 | ||
514 | By using labeled ordered choice or the recovery operator, when a label | 562 | Below we rewrite the previous example to automatically |
515 | is thrown, the parser may record the error and still continue parsing | 563 | build the recovery grammar based on information provided |
516 | to find more errors. We can even record the error right away without | 564 | by the user for each label (error message, recovery pattern, etc). |
517 | actually throwing a label (relying on the regular PEG failure instead). | 565 | In the example below we also throw an error when the grammar |
518 | Below we rewrite the arithmetic expression example and modify | 566 | does not match the whole subject. |
519 | the `expect` function to use the recovery operator for error recovery: | ||
520 | 567 | ||
521 | ```lua | 568 | ```lua |
522 | local lpeg = require"lpeglabel" | 569 | local m = require"lpeglabelrec" |
570 | local re = require"relabelrec" | ||
523 | 571 | ||
524 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | 572 | local num = m.R("09")^1 / tonumber |
525 | local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg | 573 | local op = m.S("+-") |
526 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec | ||
527 | 574 | ||
528 | local labels = { | 575 | local labels = {} |
529 | {"NoExp", "no expression found"}, | 576 | local nlabels = 0 |
530 | {"Extra", "extra characters found after the expression"}, | ||
531 | {"ExpTerm", "expected a term after the operator"}, | ||
532 | {"ExpExp", "expected an expression after the parenthesis"}, | ||
533 | {"MisClose", "missing a closing ')' after the expression"}, | ||
534 | } | ||
535 | 577 | ||
536 | local function labelindex(labname) | 578 | local function newError(lab, msg, psync, pcap) |
537 | for i, elem in ipairs(labels) do | 579 | nlabels = nlabels + 1 |
538 | if elem[1] == labname then | 580 | psync = psync or m.P(-1) |
539 | return i | 581 | pcap = pcap or m.P"" |
540 | end | 582 | labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap } |
541 | end | ||
542 | error("could not find label: " .. labname) | ||
543 | end | 583 | end |
544 | 584 | ||
545 | local function expect(patt, labname, recpatt) | 585 | newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000)) |
546 | local i = labelindex(labname) | 586 | newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000)) |
547 | local function recorderror(input, pos, errors) | 587 | newError("MisClose", "missing a closing ')' after the expression", m.P")") |
548 | table.insert(errors, {i, pos}) | 588 | newError("Extra", "extra characters found after the expression") |
549 | return true | ||
550 | end | ||
551 | if not recpatt then recpatt = P"" end | ||
552 | return Rec(patt, Cmt(Carg(1), recorderror) * recpatt) | ||
553 | end | ||
554 | 589 | ||
555 | local num = R("09")^1 / tonumber | 590 | local errors, subject |
556 | local op = S("+-*/") | 591 | |
592 | local function expect(patt, labname) | ||
593 | local i = labels[labname].id | ||
594 | return patt + m.T(i) | ||
595 | end | ||
557 | 596 | ||
558 | local function compute(tokens) | 597 | local function compute(tokens) |
559 | local result = tokens[1] | 598 | local result = tokens[1] |
@@ -562,10 +601,6 @@ local function compute(tokens) | |||
562 | result = result + tokens[i+1] | 601 | result = result + tokens[i+1] |
563 | elseif tokens[i] == '-' then | 602 | elseif tokens[i] == '-' then |
564 | result = result - tokens[i+1] | 603 | result = result - tokens[i+1] |
565 | elseif tokens[i] == '*' then | ||
566 | result = result * tokens[i+1] | ||
567 | elseif tokens[i] == '/' then | ||
568 | result = result / tokens[i+1] | ||
569 | else | 604 | else |
570 | error('unknown operation: ' .. tokens[i]) | 605 | error('unknown operation: ' .. tokens[i]) |
571 | end | 606 | end |
@@ -573,43 +608,84 @@ local function compute(tokens) | |||
573 | return result | 608 | return result |
574 | end | 609 | end |
575 | 610 | ||
576 | 611 | local g = m.P { | |
577 | local g = P { | ||
578 | "Exp", | 612 | "Exp", |
579 | Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute; | 613 | Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, |
580 | Operand = expect(V"Term", "ExpTerm", Cc(0)); | 614 | OperandFirst = expect(m.V"Term", "ExpTermFirst"), |
581 | Term = num + V"Group"; | 615 | Operand = expect(m.V"Term", "ExpTermOp"), |
582 | Group = "(" * V"InnerExp" * expect(")", "MisClose"); | 616 | Term = num + m.V"Group", |
583 | InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0)); | 617 | Group = "(" * m.V"Exp" * expect(")", "MisClose"), |
584 | } | 618 | } |
585 | 619 | ||
586 | g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") | 620 | function recorderror(pos, lab) |
621 | local line, col = re.calcline(subject, pos) | ||
622 | table.insert(errors, { line = line, col = col, msg = labels[lab].msg }) | ||
623 | end | ||
624 | |||
625 | function record (labname) | ||
626 | return (m.Cp() * m.Cc(labname)) / recorderror | ||
627 | end | ||
628 | |||
629 | function sync (p) | ||
630 | return (-p * m.P(1))^0 | ||
631 | end | ||
632 | |||
633 | function defaultValue (p) | ||
634 | return p or m.Cc(1000) | ||
635 | end | ||
636 | |||
637 | local grec = g * expect(m.P(-1), "Extra") | ||
638 | for k, v in pairs(labels) do | ||
639 | grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id) | ||
640 | end | ||
587 | 641 | ||
588 | local function eval(input) | 642 | local function eval(input) |
589 | local errors = {} | 643 | errors = {} |
590 | local result, label, suffix = g:match(input, 1, errors) | 644 | io.write("Input: ", input, "\n") |
591 | if #errors == 0 then | 645 | subject = input |
592 | return result | 646 | local result, label, suffix = grec:match(input) |
593 | else | 647 | io.write("Syntactic errors found: " .. #errors, "\n") |
648 | if #errors > 0 then | ||
594 | local out = {} | 649 | local out = {} |
595 | for i, err in ipairs(errors) do | 650 | for i, err in ipairs(errors) do |
596 | local pos = err[2] | 651 | local pos = err.col |
597 | local msg = labels[err[1]][2] | 652 | local msg = err.msg |
598 | table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") | 653 | table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") |
599 | end | 654 | end |
600 | return nil, table.concat(out, "\n") | 655 | print(table.concat(out, "\n")) |
601 | end | 656 | end |
657 | io.write("Result = ") | ||
658 | return result | ||
602 | end | 659 | end |
603 | 660 | ||
604 | print(eval "98-76*(54/32)") | 661 | print(eval "90-70-(5)+3") |
605 | --> 37.125 | 662 | -- Syntactic errors found: 0 |
606 | 663 | -- Result = 18 | |
607 | print(eval "-1+(1-(1*2))/2") | 664 | |
608 | --> syntax error: no expression found (at index 1) | 665 | print(eval "15+") |
609 | 666 | -- Syntactic errors found: 1 | |
610 | print(eval "(1+1-1*(2/2+)-():") | 667 | -- syntax error: expected a term after the operator (at index 3) |
611 | --> syntax error: expected a term after the operator (at index 13) | 668 | -- Result = 1015 |
612 | --> syntax error: expected an expression after the parenthesis (at index 16) | 669 | |
613 | --> syntax error: missing a closing ')' after the expression (at index 17) | 670 | print(eval "-2") |
614 | --> syntax error: extra characters found after the expression (at index 17) | 671 | -- Syntactic errors found: 1 |
672 | -- syntax error: expected an expression (at index 1) | ||
673 | -- Result = 998 | ||
674 | |||
675 | print(eval "1+()+") | ||
676 | -- Syntactic errors found: 2 | ||
677 | -- syntax error: expected an expression (at index 4) | ||
678 | -- syntax error: expected a term after the operator (at index 5) | ||
679 | -- Result = 2001 | ||
680 | |||
681 | print(eval "1+(") | ||
682 | -- Syntactic errors found: 2 | ||
683 | -- syntax error: expected an expression (at index 3) | ||
684 | -- syntax error: missing a closing ')' after the expression (at index 3) | ||
685 | -- Result = 1001 | ||
686 | |||
687 | print(eval "3)") | ||
688 | -- Syntactic errors found: 1 | ||
689 | -- syntax error: extra characters found after the expression (at index 2) | ||
690 | -- Result = 3 | ||
615 | ``` | 691 | ``` |
diff --git a/examples/expRec.lua b/examples/expRec.lua index c5cbcca..5c5fd7d 100644 --- a/examples/expRec.lua +++ b/examples/expRec.lua | |||
@@ -1,10 +1,6 @@ | |||
1 | local m = require"lpeglabelrec" | 1 | local m = require"lpeglabelrec" |
2 | local re = require"relabelrec" | 2 | local re = require"relabelrec" |
3 | 3 | ||
4 | local R, S, P, V = m.R, m.S, m.P, m.V | ||
5 | local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt | ||
6 | local T, Rec = m.T, m.Rec | ||
7 | |||
8 | local labels = { | 4 | local labels = { |
9 | {"ExpTermFirst", "expected an expression"}, | 5 | {"ExpTermFirst", "expected an expression"}, |
10 | {"ExpTermOp", "expected a term after the operator"}, | 6 | {"ExpTermOp", "expected a term after the operator"}, |
@@ -22,14 +18,14 @@ end | |||
22 | 18 | ||
23 | local errors, subject | 19 | local errors, subject |
24 | 20 | ||
25 | local function expect(patt, labname, recpatt) | 21 | local function expect(patt, labname) |
26 | local i = labelindex(labname) | 22 | local i = labelindex(labname) |
27 | return patt + T(i) | 23 | return patt + m.T(i) |
28 | end | 24 | end |
29 | 25 | ||
30 | 26 | ||
31 | local num = R("09")^1 / tonumber | 27 | local num = m.R("09")^1 / tonumber |
32 | local op = S("+-") | 28 | local op = m.S("+-") |
33 | 29 | ||
34 | local function compute(tokens) | 30 | local function compute(tokens) |
35 | local result = tokens[1] | 31 | local result = tokens[1] |
@@ -45,13 +41,13 @@ local function compute(tokens) | |||
45 | return result | 41 | return result |
46 | end | 42 | end |
47 | 43 | ||
48 | local g = P { | 44 | local g = m.P { |
49 | "Exp", | 45 | "Exp", |
50 | Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, | 46 | Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, |
51 | OperandFirst = expect(V"Term", "ExpTermFirst"), | 47 | OperandFirst = expect(m.V"Term", "ExpTermFirst"), |
52 | Operand = expect(V"Term", "ExpTermOp"), | 48 | Operand = expect(m.V"Term", "ExpTermOp"), |
53 | Term = num + V"Group", | 49 | Term = num + m.V"Group", |
54 | Group = "(" * V"Exp" * expect(")", "MisClose"), | 50 | Group = "(" * m.V"Exp" * expect(")", "MisClose"), |
55 | } | 51 | } |
56 | 52 | ||
57 | function recorderror(pos, lab) | 53 | function recorderror(pos, lab) |
@@ -71,22 +67,23 @@ function defaultValue (p) | |||
71 | return p or m.Cc(1000) | 67 | return p or m.Cc(1000) |
72 | end | 68 | end |
73 | 69 | ||
74 | local recg = P { | 70 | local grec = m.P { |
75 | "S", | 71 | "S", |
76 | S = Rec(V"A", V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 | 72 | S = m.Rec(m.V"A", m.V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0 |
77 | A = Rec(V"Sg", V"ErrExpTermOp", labelindex("ExpTermOp")), | 73 | A = m.Rec(m.V"Sg", m.V"ErrExpTermOp", labelindex("ExpTermOp")), |
78 | Sg = Rec(g, V"ErrMisClose", labelindex("MisClose")), | 74 | Sg = m.Rec(g, m.V"ErrMisClose", labelindex("MisClose")), |
79 | ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), | 75 | ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), |
80 | ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), | 76 | ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), |
81 | ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), | 77 | ErrMisClose = record("MisClose") * sync(m.P")") * defaultValue(m.P""), |
82 | } | 78 | } |
83 | 79 | ||
84 | |||
85 | local function eval(input) | 80 | local function eval(input) |
86 | errors = {} | 81 | errors = {} |
82 | io.write("Input: ", input, "\n") | ||
87 | subject = input | 83 | subject = input |
88 | local result, label, suffix = recg:match(input) | 84 | local result, label, suffix = grec:match(input) |
89 | if #errors > 0 then | 85 | io.write("Syntactic errors found: " .. #errors, "\n") |
86 | if #errors > 0 then | ||
90 | local out = {} | 87 | local out = {} |
91 | for i, err in ipairs(errors) do | 88 | for i, err in ipairs(errors) do |
92 | local pos = err.col | 89 | local pos = err.col |
@@ -95,13 +92,14 @@ local function eval(input) | |||
95 | end | 92 | end |
96 | print(table.concat(out, "\n")) | 93 | print(table.concat(out, "\n")) |
97 | end | 94 | end |
95 | io.write("Result = ") | ||
98 | return result | 96 | return result |
99 | end | 97 | end |
100 | 98 | ||
101 | print(eval "90-70*5") | 99 | print(eval "90-70-(5)+3") |
102 | --> 20 | 100 | --> 20 |
103 | 101 | ||
104 | print(eval "2+") | 102 | print(eval "15+") |
105 | --> 2 + 0 | 103 | --> 2 + 0 |
106 | 104 | ||
107 | print(eval "-2") | 105 | print(eval "-2") |
@@ -126,3 +124,5 @@ print(eval "1+(") | |||
126 | 124 | ||
127 | print(eval "3)") | 125 | print(eval "3)") |
128 | 126 | ||
127 | print(eval "11+())3") | ||
128 | |||
diff --git a/examples/expRecAut.lua b/examples/expRecAut.lua index e098078..f870d73 100644 --- a/examples/expRecAut.lua +++ b/examples/expRecAut.lua | |||
@@ -1,12 +1,8 @@ | |||
1 | local m = require"lpeglabelrec" | 1 | local m = require"lpeglabelrec" |
2 | local re = require"relabelrec" | 2 | local re = require"relabelrec" |
3 | 3 | ||
4 | local R, S, P, V = m.R, m.S, m.P, m.V | 4 | local num = m.R("09")^1 / tonumber |
5 | local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt | 5 | local op = m.S("+-") |
6 | local T, Rec = m.T, m.Rec | ||
7 | |||
8 | local num = R("09")^1 / tonumber | ||
9 | local op = S("+-") | ||
10 | 6 | ||
11 | local labels = {} | 7 | local labels = {} |
12 | local nlabels = 0 | 8 | local nlabels = 0 |
@@ -27,7 +23,7 @@ local errors, subject | |||
27 | 23 | ||
28 | local function expect(patt, labname) | 24 | local function expect(patt, labname) |
29 | local i = labels[labname].id | 25 | local i = labels[labname].id |
30 | return patt + T(i) | 26 | return patt + m.T(i) |
31 | end | 27 | end |
32 | 28 | ||
33 | local function compute(tokens) | 29 | local function compute(tokens) |
@@ -44,13 +40,13 @@ local function compute(tokens) | |||
44 | return result | 40 | return result |
45 | end | 41 | end |
46 | 42 | ||
47 | local g = P { | 43 | local g = m.P { |
48 | "Exp", | 44 | "Exp", |
49 | Exp = Ct(V"OperandFirst" * (C(op) * V"Operand")^0) / compute, | 45 | Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute, |
50 | OperandFirst = expect(V"Term", "ExpTermFirst"), | 46 | OperandFirst = expect(m.V"Term", "ExpTermFirst"), |
51 | Operand = expect(V"Term", "ExpTermOp"), | 47 | Operand = expect(m.V"Term", "ExpTermOp"), |
52 | Term = num + V"Group", | 48 | Term = num + m.V"Group", |
53 | Group = "(" * V"Exp" * expect(")", "MisClose"), | 49 | Group = "(" * m.V"Exp" * expect(")", "MisClose"), |
54 | } | 50 | } |
55 | 51 | ||
56 | function recorderror(pos, lab) | 52 | function recorderror(pos, lab) |
@@ -70,27 +66,18 @@ function defaultValue (p) | |||
70 | return p or m.Cc(1000) | 66 | return p or m.Cc(1000) |
71 | end | 67 | end |
72 | 68 | ||
73 | local recg2 = g | 69 | local grec = g * expect(m.P(-1), "Extra") |
74 | for k, v in pairs(labels) do | 70 | for k, v in pairs(labels) do |
75 | recg2 = Rec(recg2, record(k) * sync(v.psync) * v.pcap, v.id) | 71 | grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id) |
76 | end | 72 | end |
77 | 73 | ||
78 | local recg = P { | ||
79 | "S", | ||
80 | S = Rec(V"A", V"ErrExpTermFirst", labels["ExpTermFirst"].id), -- default value is 0 | ||
81 | A = Rec(V"Sg", V"ErrExpTermOp", labels["ExpTermOp"].id), | ||
82 | Sg = Rec(g, V"ErrMisClose", labels["MisClose"].id), | ||
83 | ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(), | ||
84 | ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(), | ||
85 | ErrMisClose = record("MisClose") * sync(P")") * defaultValue(m.P""), | ||
86 | } | ||
87 | |||
88 | |||
89 | local function eval(input) | 74 | local function eval(input) |
90 | errors = {} | 75 | errors = {} |
76 | io.write("Input: ", input, "\n") | ||
91 | subject = input | 77 | subject = input |
92 | local result, label, suffix = recg2:match(input) | 78 | local result, label, suffix = grec:match(input) |
93 | if #errors > 0 then | 79 | io.write("Syntactic errors found: " .. #errors, "\n") |
80 | if #errors > 0 then | ||
94 | local out = {} | 81 | local out = {} |
95 | for i, err in ipairs(errors) do | 82 | for i, err in ipairs(errors) do |
96 | local pos = err.col | 83 | local pos = err.col |
@@ -99,13 +86,14 @@ local function eval(input) | |||
99 | end | 86 | end |
100 | print(table.concat(out, "\n")) | 87 | print(table.concat(out, "\n")) |
101 | end | 88 | end |
89 | io.write("Result = ") | ||
102 | return result | 90 | return result |
103 | end | 91 | end |
104 | 92 | ||
105 | print(eval "90-70*5") | 93 | print(eval "90-70-(5)+3") |
106 | --> 20 | 94 | --> 18 |
107 | 95 | ||
108 | print(eval "2+") | 96 | print(eval "15+") |
109 | --> 2 + 0 | 97 | --> 2 + 0 |
110 | 98 | ||
111 | print(eval "-2") | 99 | print(eval "-2") |
@@ -130,3 +118,5 @@ print(eval "1+(") | |||
130 | 118 | ||
131 | print(eval "3)") | 119 | print(eval "3)") |
132 | 120 | ||
121 | print(eval "11+()3") | ||
122 | --> 1 + ([0]) [+] 3 + [0] | ||
diff --git a/examples/listId2Rec2.lua b/examples/listId2Rec2.lua index ab8f1dd..c6705dd 100644 --- a/examples/listId2Rec2.lua +++ b/examples/listId2Rec2.lua | |||
@@ -56,7 +56,7 @@ function mymatch (g, s) | |||
56 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg | 56 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg |
57 | table.insert(out, msg) | 57 | table.insert(out, msg) |
58 | end | 58 | end |
59 | return nil, table.concat(out, "\n") | 59 | return nil, table.concat(out, "\n") .. "\n" |
60 | end | 60 | end |
61 | return r | 61 | return r |
62 | end | 62 | end |
diff --git a/examples/listIdRe2.lua b/examples/listIdRe2.lua index 070bcdb..6bab6ba 100644 --- a/examples/listIdRe2.lua +++ b/examples/listIdRe2.lua | |||
@@ -19,7 +19,7 @@ re.setlabels(labels) | |||
19 | local g = re.compile[[ | 19 | local g = re.compile[[ |
20 | S <- Id List | 20 | S <- Id List |
21 | List <- !. / Comma Id List | 21 | List <- !. / Comma Id List |
22 | Id <- Sp [a-z]+ / %{errId} | 22 | Id <- Sp {[a-z]+} / %{errId} |
23 | Comma <- Sp ',' / %{errComma} | 23 | Comma <- Sp ',' / %{errComma} |
24 | Sp <- %s* | 24 | Sp <- %s* |
25 | ]] | 25 | ]] |
@@ -38,25 +38,34 @@ end | |||
38 | 38 | ||
39 | local grec = re.compile( | 39 | local grec = re.compile( |
40 | "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" .. | 40 | "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" .. |
41 | "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('!. / [a-z]+') .. "\n" .. | 41 | "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('[a-z]+') .. "\n" .. |
42 | "ErrId <- ('' -> 'errId' => recorderror) (!(!. / ',') .)*" | 42 | "ErrId <- ('' -> 'errId' => recorderror) " .. sync('","') .. "-> default" |
43 | , {g = g, recorderror = recorderror}) | 43 | , {g = g, recorderror = recorderror, default = "NONE"}) |
44 | 44 | ||
45 | function mymatch (g, s) | 45 | function mymatch (g, s) |
46 | errors = {} | 46 | errors = {} |
47 | local r, e, sfail = g:match(s) | 47 | subject = s |
48 | io.write("Input: ", s, "\n") | ||
49 | local r = { g:match(s) } | ||
50 | io.write("Captures (separated by ';'): ") | ||
51 | for k, v in pairs(r) do | ||
52 | io.write(v .. "; ") | ||
53 | end | ||
54 | io.write("\nSyntactic errors found: " .. #errors) | ||
48 | if #errors > 0 then | 55 | if #errors > 0 then |
56 | io.write("\n") | ||
49 | local out = {} | 57 | local out = {} |
50 | for i, err in ipairs(errors) do | 58 | for i, err in ipairs(errors) do |
51 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg | 59 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg |
52 | table.insert(out, msg) | 60 | table.insert(out, msg) |
53 | end | 61 | end |
54 | return nil, table.concat(out, "\n") | 62 | io.write(table.concat(out, "\n")) |
55 | end | 63 | end |
56 | return r | 64 | print("\n") |
65 | return r | ||
57 | end | 66 | end |
58 | 67 | ||
59 | print(mymatch(grec, "one,two")) | 68 | mymatch(grec, "one,two") |
60 | print(mymatch(grec, "one two three")) | 69 | mymatch(grec, "one two three") |
61 | print(mymatch(grec, "1,\n two, \n3,")) | 70 | mymatch(grec, "1,\n two, \n3,") |
62 | print(mymatch(grec, "one\n two123, \nthree,")) | 71 | mymatch(grec, "one\n two123, \nthree,") |