aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md542
-rw-r--r--examples/expRec.lua50
-rw-r--r--examples/expRecAut.lua52
-rw-r--r--examples/listId2Rec2.lua2
-rw-r--r--examples/listIdRe2.lua31
5 files changed, 376 insertions, 301 deletions
diff --git a/README.md b/README.md
index 5e27796..9bc162d 100644
--- a/README.md
+++ b/README.md
@@ -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
173should be matched when a label is thrown. After matching the recovery 173should be matched when a label is thrown. After matching the recovery
174pattern, and possibly recording the error, the parser will resume 174pattern, and possibly recording the error, the parser will resume
175the <em>regular</em> matching. For example, in the example below 175the <em>regular</em> matching. For example, in the example below
176we expect to match rule `A`, but in case label 42 is thrown 176we expect to match rule `A`, but when a failure occur the label 42
177then we will try to match `recp`: 177is thrown and then we will try to match the recovery pattern `recp`:
178```lua 178```lua
179local m = require'lpeglabelrec' 179local m = require'lpeglabelrec'
180 180
181local recp = m.P"oast" 181local recp = m.P"oast"
182 182
183local g = m.P{ 183local 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
189print(g:match("test.")) --> 6 189print(g:match("test.")) --> 6
190
191print(g:match("toast.")) --> 7 190print(g:match("toast.")) --> 7
192 191print(g:match("oast.")) --> nil 0 oast.
193print(g:match("oast.")) --> nil 0 oast. 192print(g:match("toward.")) --> nil 0 ward.
194
195print(g:match("toward.")) --> nil 0 ward.
196``` 193```
197When trying to match 'toast.', in rule `A` the first 194When 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
199inpux suffix 'oast.'. In rule `S` this label is caught 196is thrown, with the associated inpux suffix 'oast.'. In rule
200and the recovery pattern matches 'oast', so pattern `'.'` 197`S` label 42 is caught and the recovery pattern matches 'oast',
201matches the rest of the input. 198so pattern `'.'` matches the rest of the input.
202 199
203When matching 'oast.', pattern `m.P"t"` fails, and 200When matching subject 'oast.', pattern `m.P"t"` fails, and
204the result of the matching is <b>nil, 0, oast.</b>. 201the result of the matching is <b>nil, 0, oast.</b>.
205 202
206When matching 'toward.', label 42 is throw, with the associated 203When matching 'toward.', label 42 is thrown after matching 't',
207input suffix 'oward.'. The matching of the recovery pattern fails to, 204with the associated input suffix 'oward.'. As the matching of the
208so the result of the matching is <b>nil, 0, ward.</b>. 205recovery pattern fails, the result is <b>nil, 0, ward.</b>.
209 206
210Usually, the recovery pattern is an expression that never fails. 207Usually, the recovery pattern is an expression that does not fail.
211In the previous example, we could have used `(m.P(1) - m.P".")^0` 208In the previous example, we could have used `(m.P(1) - m.P".")^0`
212as the recovery pattern. 209as the recovery pattern.
213 210
@@ -216,11 +213,14 @@ to use a recovery strategy. Grammar `g` remains the same, but we add a
216recovery grammar `grec` that handles the labels thrown by `g`. 213recovery grammar `grec` that handles the labels thrown by `g`.
217 214
218In grammar `grec` we use functions `record` and `sync`. 215In grammar `grec` we use functions `record` and `sync`.
219Function `record` gives us a pattern that captures two 216Function `record`, plus function `recorderror`, will help
220values: the current subject position (where a label was thrown) 217us to save the input position where a label was thrown,
221and the label itself. These values will be used to record 218while function `sync` will give us a synchronization pattern,
222all the errors found. Function `sync` give us synchronization 219that consumes the input while is not possible to match a given
223pattern, that macthes the input 220pattern `p`.
221
222When the matching of an identifier fails, a defaul value ('NONE')
223is provided.
224 224
225```lua 225```lua
226local m = require'lpeglabelrec' 226local m = require'lpeglabelrec'
@@ -266,8 +266,8 @@ end
266local grec = m.P{ 266local 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
287end 287end
288 288
289print(mymatch(grec, "one,two")) 289print(mymatch(grec, "one,two"))
290-- Captures (separated by ';'): one; two;
291-- Syntactic errors found: 0
292
290print(mymatch(grec, "one two three")) 293print(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
291print(mymatch(grec, "1,\n two, \n3,")) 299print(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
292print(mymatch(grec, "one\n two123, \nthree,")) 306print(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
299Now we rewrite the previous example using the syntax 316Below we describe again a grammar that matches a list of identifiers,
300supported by *relabelrec*: 317now using the syntax supported by *relabelrec*, where `//{}` is the
301 318recovery operator, and `%{}` is the throw operator:
302```lua
303local re = require 'relabelrec'
304
305local 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
313function 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
327end
328
329print(mymatch(g, "one,two")) --> 8
330print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two'
331print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before ''
332```
333
334With the help of function *setlabels* we can also rewrite the previous example to use
335mnemonic labels instead of plain numbers:
336 319
337```lua 320```lua
338local re = require 'relabelrec' 321local re = require 'relabelrec'
@@ -355,64 +338,124 @@ re.setlabels(labels)
355 338
356local g = re.compile[[ 339local 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
347local errors
348
349function 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
353end
354
355function sync (p)
356 return '( !(' .. p .. ') .)*'
357end
358
359local 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
364function mymatch (g, s) 366function 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
372end 387end
373 388
374print(mymatch(g, "one,two")) --> 8 389print(mymatch(grec, "one,two"))
375print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' 390-- Captures (separated by ';'): one; two;
376print(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
393print(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
399print(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
406print(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
386Here's an example of an LPegLabel grammar that make its own function called 417Here'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 418We have used a function `expect`, that takes a pattern `patt` and a label as
388if the pattern fails to be matched. This function can be extended later on to 419parameters and builds a new pattern that throws this label when `patt`
389record all errors encountered once error recovery is implemented. 420fails.
390 421
391```lua 422When a subexpression is syntactically invalid, a default value of 1000
392local lpeg = require"lpeglabel" 423is provided by the recovery pattern, so the evaluation of an expression
424should always produce a numeric value.
393 425
394local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T 426In this example, we can see that it may be a tedious and error prone
427task to build manually the recovery grammar `grec`. In the next example
428we will show how to build the recovery grammar in a more automatic way.
429
430```lua
431local m = require"lpeglabelrec"
432local re = require"relabelrec"
395 433
396local labels = { 434local 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
404local function expect(patt, labname) 440local 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)
412end 447end
413 448
414local num = R("09")^1 / tonumber 449local errors, subject
415local op = S("+-*/") 450
451local function expect(patt, labname)
452 local i = labelindex(labname)
453 return patt + m.T(i)
454end
455
456
457local num = m.R("09")^1 / tonumber
458local op = m.S("+-")
416 459
417local function compute(tokens) 460local 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
433end 472end
434 473
435local g = P { 474local 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
442g = expect(g, "NoExp") * expect(-P(1), "Extra") 483function recorderror(pos, lab)
443 484 local line, col = re.calcline(subject, pos)
444local 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
453end 486end
454 487
455print(eval "98-76*(54/32)") 488function record (labname)
456--> 37.125 489 return (m.Cp() * m.Cc(labelindex(labname))) / recorderror
457 490end
458print(eval "(1+1-1*2/2")
459--> syntax error: missing a closing ')' after the expression (at index 11)
460
461print(eval "(1+)-1*(2/2)")
462--> syntax error: expected a term after the operator (at index 4)
463
464print(eval "(1+1)-1*(/2)")
465--> syntax error: expected an expression after the parenthesis (at index 10)
466
467print(eval "1+(1-(1*2))/2x")
468--> syntax error: extra chracters found after the expression (at index 14)
469
470print(eval "-1+(1-(1*2))/2")
471--> syntax error: no expression found (at index 1)
472```
473
474#### Catching labels
475
476When a label is thrown, the grammar itself can handle this label
477by using the labeled ordered choice. Below we rewrite the example
478of the list of identifiers to show this feature:
479
480
481```lua
482local m = require'lpeglabel'
483
484local terror = {}
485 491
486local function newError(s) 492function sync (p)
487 table.insert(terror, s) 493 return (-p * m.P(1))^0
488 return #terror
489end 494end
490 495
491local errUndef = newError("undefined") 496function defaultValue (p)
492local errId = newError("expecting an identifier") 497 return p or m.Cc(1000)
493local errComma = newError("expecting ','") 498end
494 499
495local g = m.P{ 500local 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
510local 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
527end
506 528
507print(m.match(g, "one,two")) --> 8 529print(eval "90-70-(5)+3")
508print(m.match(g, "one two")) --> expecting ',' 530-- Syntactic errors found: 0
509print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier 531-- Result = 18
532
533print(eval "15+")
534-- Syntactic errors found: 1
535-- syntax error: expected a term after the operator (at index 3)
536-- Result = 1015
537
538print(eval "-2")
539-- Syntactic errors found: 1
540-- syntax error: expected an expression (at index 1)
541-- Result = 998
542
543print(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
549print(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
555print(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
514By using labeled ordered choice or the recovery operator, when a label 562Below we rewrite the previous example to automatically
515is thrown, the parser may record the error and still continue parsing 563build the recovery grammar based on information provided
516to find more errors. We can even record the error right away without 564by the user for each label (error message, recovery pattern, etc).
517actually throwing a label (relying on the regular PEG failure instead). 565In the example below we also throw an error when the grammar
518Below we rewrite the arithmetic expression example and modify 566does not match the whole subject.
519the `expect` function to use the recovery operator for error recovery:
520 567
521```lua 568```lua
522local lpeg = require"lpeglabel" 569local m = require"lpeglabelrec"
570local re = require"relabelrec"
523 571
524local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V 572local num = m.R("09")^1 / tonumber
525local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg 573local op = m.S("+-")
526local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec
527 574
528local labels = { 575local labels = {}
529 {"NoExp", "no expression found"}, 576local 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
536local function labelindex(labname) 578local 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)
543end 583end
544 584
545local function expect(patt, labname, recpatt) 585newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000))
546 local i = labelindex(labname) 586newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000))
547 local function recorderror(input, pos, errors) 587newError("MisClose", "missing a closing ')' after the expression", m.P")")
548 table.insert(errors, {i, pos}) 588newError("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)
553end
554 589
555local num = R("09")^1 / tonumber 590local errors, subject
556local op = S("+-*/") 591
592local function expect(patt, labname)
593 local i = labels[labname].id
594 return patt + m.T(i)
595end
557 596
558local function compute(tokens) 597local 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
574end 609end
575 610
576 611local g = m.P {
577local 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
586g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") 620function recorderror(pos, lab)
621 local line, col = re.calcline(subject, pos)
622 table.insert(errors, { line = line, col = col, msg = labels[lab].msg })
623end
624
625function record (labname)
626 return (m.Cp() * m.Cc(labname)) / recorderror
627end
628
629function sync (p)
630 return (-p * m.P(1))^0
631end
632
633function defaultValue (p)
634 return p or m.Cc(1000)
635end
636
637local grec = g * expect(m.P(-1), "Extra")
638for k, v in pairs(labels) do
639 grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id)
640end
587 641
588local function eval(input) 642local 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
602end 659end
603 660
604print(eval "98-76*(54/32)") 661print(eval "90-70-(5)+3")
605--> 37.125 662-- Syntactic errors found: 0
606 663-- Result = 18
607print(eval "-1+(1-(1*2))/2") 664
608--> syntax error: no expression found (at index 1) 665print(eval "15+")
609 666-- Syntactic errors found: 1
610print(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) 670print(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
675print(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
681print(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
687print(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 @@
1local m = require"lpeglabelrec" 1local m = require"lpeglabelrec"
2local re = require"relabelrec" 2local re = require"relabelrec"
3 3
4local R, S, P, V = m.R, m.S, m.P, m.V
5local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt
6local T, Rec = m.T, m.Rec
7
8local labels = { 4local 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
23local errors, subject 19local errors, subject
24 20
25local function expect(patt, labname, recpatt) 21local 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)
28end 24end
29 25
30 26
31local num = R("09")^1 / tonumber 27local num = m.R("09")^1 / tonumber
32local op = S("+-") 28local op = m.S("+-")
33 29
34local function compute(tokens) 30local 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
46end 42end
47 43
48local g = P { 44local 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
57function recorderror(pos, lab) 53function recorderror(pos, lab)
@@ -71,22 +67,23 @@ function defaultValue (p)
71 return p or m.Cc(1000) 67 return p or m.Cc(1000)
72end 68end
73 69
74local recg = P { 70local 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
85local function eval(input) 80local 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
99end 97end
100 98
101print(eval "90-70*5") 99print(eval "90-70-(5)+3")
102--> 20 100--> 20
103 101
104print(eval "2+") 102print(eval "15+")
105--> 2 + 0 103--> 2 + 0
106 104
107print(eval "-2") 105print(eval "-2")
@@ -126,3 +124,5 @@ print(eval "1+(")
126 124
127print(eval "3)") 125print(eval "3)")
128 126
127print(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 @@
1local m = require"lpeglabelrec" 1local m = require"lpeglabelrec"
2local re = require"relabelrec" 2local re = require"relabelrec"
3 3
4local R, S, P, V = m.R, m.S, m.P, m.V 4local num = m.R("09")^1 / tonumber
5local C, Cc, Ct, Cmt = m.C, m.Cc, m.Ct, m.Cmt 5local op = m.S("+-")
6local T, Rec = m.T, m.Rec
7
8local num = R("09")^1 / tonumber
9local op = S("+-")
10 6
11local labels = {} 7local labels = {}
12local nlabels = 0 8local nlabels = 0
@@ -27,7 +23,7 @@ local errors, subject
27 23
28local function expect(patt, labname) 24local 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)
31end 27end
32 28
33local function compute(tokens) 29local function compute(tokens)
@@ -44,13 +40,13 @@ local function compute(tokens)
44 return result 40 return result
45end 41end
46 42
47local g = P { 43local 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
56function recorderror(pos, lab) 52function recorderror(pos, lab)
@@ -70,27 +66,18 @@ function defaultValue (p)
70 return p or m.Cc(1000) 66 return p or m.Cc(1000)
71end 67end
72 68
73local recg2 = g 69local grec = g * expect(m.P(-1), "Extra")
74for k, v in pairs(labels) do 70for 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)
76end 72end
77 73
78local 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
89local function eval(input) 74local 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
103end 91end
104 92
105print(eval "90-70*5") 93print(eval "90-70-(5)+3")
106--> 20 94--> 18
107 95
108print(eval "2+") 96print(eval "15+")
109--> 2 + 0 97--> 2 + 0
110 98
111print(eval "-2") 99print(eval "-2")
@@ -130,3 +118,5 @@ print(eval "1+(")
130 118
131print(eval "3)") 119print(eval "3)")
132 120
121print(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
62end 62end
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)
19local g = re.compile[[ 19local 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
39local grec = re.compile( 39local 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
45function mymatch (g, s) 45function 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
57end 66end
58 67
59print(mymatch(grec, "one,two")) 68mymatch(grec, "one,two")
60print(mymatch(grec, "one two three")) 69mymatch(grec, "one two three")
61print(mymatch(grec, "1,\n two, \n3,")) 70mymatch(grec, "1,\n two, \n3,")
62print(mymatch(grec, "one\n two123, \nthree,")) 71mymatch(grec, "one\n two123, \nthree,")