aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergio Queiroz <sqmedeiros@gmail.com>2016-12-13 13:53:49 -0300
committerSergio Queiroz <sqmedeiros@gmail.com>2016-12-13 13:53:49 -0300
commit09fab0decb7df93528ab40fcfd99587e9074c64f (patch)
treeecd7a763c7a08712f122945bb5ce1ed7d7e5f077
parentd80821d79376671371c15ded562fbe1a9bebc635 (diff)
parent1322d612d72ac658f2aa443dca94954b819c0993 (diff)
downloadlpeglabel-09fab0decb7df93528ab40fcfd99587e9074c64f.tar.gz
lpeglabel-09fab0decb7df93528ab40fcfd99587e9074c64f.tar.bz2
lpeglabel-09fab0decb7df93528ab40fcfd99587e9074c64f.zip
Merge branch 'recoveryresume'
-rw-r--r--README.md665
-rw-r--r--examples/expRec.lua128
-rw-r--r--examples/expRecAut.lua122
-rw-r--r--examples/expect.lua98
-rw-r--r--examples/listId1.lua12
-rw-r--r--examples/listId2.lua13
-rw-r--r--examples/listId2Rec2.lua67
-rw-r--r--examples/listId2Rec2Cap.lua79
-rw-r--r--examples/listIdCatch.lua28
-rw-r--r--examples/listIdCatchRe.lua34
-rw-r--r--examples/listIdRe1.lua8
-rw-r--r--examples/listIdRe2.lua61
-rw-r--r--examples/recovery.lua134
-rw-r--r--examples/recoveryOpFail.lua105
-rw-r--r--examples/recoveryOpLab.lua107
-rw-r--r--examples/tiny.lua84
-rwxr-xr-xexamples/typedlua/test.lua9
-rw-r--r--examples/typedlua/tllexer.lua11
-rw-r--r--examples/typedlua/tlparser.lua2
-rw-r--r--lpcode.c40
-rw-r--r--lpprint.c6
-rw-r--r--lptree.c31
-rw-r--r--lptree.h2
-rw-r--r--lpvm.c52
-rw-r--r--lpvm.h1
-rw-r--r--makefile14
-rw-r--r--relabelrec.lua (renamed from relabel.lua)10
-rwxr-xr-xtest.lua4
-rw-r--r--testlabel.lua475
29 files changed, 1407 insertions, 995 deletions
diff --git a/README.md b/README.md
index 1f1bdff..9484b3d 100644
--- a/README.md
+++ b/README.md
@@ -10,47 +10,46 @@ 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)
11library that provides an implementation of Parsing 11library that provides an implementation of Parsing
12Expression Grammars (PEGs) with labeled failures. 12Expression Grammars (PEGs) with labeled failures.
13Labels can be used to signal different kinds of erros 13Labels can be used to signal different kinds of errors
14and to specify which alternative in a labeled ordered 14and to specify which recovery pattern should handle a
15choice should handle a given label. Labels can also be 15given label. Labels can also be combined with the standard
16combined with the standard patterns of LPeg. 16patterns of LPeg.
17 17
18This document describes the new functions available 18This document describes the new functions available
19in LpegLabel and presents some examples of usage. 19in LpegLabel and presents some examples of usage.
20For a more detailed discussion about PEGs with labeled failures
21please see [A Parsing Machine for Parsing Expression
22Grammars with Labeled Failures](https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxzcW1lZGVpcm9zfGd4OjMzZmE3YzM0Y2E2MGM5Y2M).
23
24 20
25In LPegLabel, the result of an unsuccessful matching 21In LPegLabel, the result of an unsuccessful matching
26is a triple **nil, lab, sfail**, where **lab** 22is a triple **nil, lab, sfail**, where **lab**
27is the label associated with the failure, and 23is 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.
30of the new functions provided by LpegLabel: 26
27With labeled failures it is possible to distinguish
28between a regular failure and an error. Usually, a
29regular failure is produced when the matching of a
30character fails, and it is caught by an ordered choice.
31An error, by its turn, is produced by the throw operator
32and may be caught by the recovery operator.
33
34Below there is a brief summary of the new functions provided by LpegLabel:
31 35
32<table border="1"> 36<table border="1">
33<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>
34<tr><td><a href="#f-t"><code>lpeglabel.T (l)</code></a></td> 38<tr><td><a href="#f-t"><code>lpeglabelrec.T (l)</code></a></td>
35 <td>Throws label <code>l</code></td></tr> 39 <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> 40<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> 41 <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> 42 when the matching of <code>p1</code> gives one of the labels l1, ..., ln.</td></tr>
39 </td></tr>
40<tr><td><a href="#f-rec"><code>lpeglabel.Rec (p1, p2 [, l1, ..., ln])</code></a></td>
41 <td>Like <code>Lc</code> but does not reset the position of the parser
42 when trying <code>p2</code>. By default, it catches regular PEG failures
43 </td></tr>
44<tr><td><a href="#re-t"><code>%{l}</code></a></td> 43<tr><td><a href="#re-t"><code>%{l}</code></a></td>
45 <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.T(l)</code> 44 <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.T(l)</code>
46 </td></tr> 45 </td></tr>
47<tr><td><a href="#re-lc"><code>p1 /{l1, ..., ln} p2</code></a></td> 46<tr><td><a href="#re-rec"><code>p1 //{l1 [, l2, ..., ln} p2</code></a></td>
48 <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.Lc(p1, p2, l1, ..., ln)</code> 47 <td>Syntax of <em>relabelrec</em> module. Equivalent to <code>lpeglabelrec.Rec(p1, p2, l1, ..., ln)</code>
49 </td></tr> 48 </td></tr>
50<tr><td><a href="#re-line"><code>relabel.calcline(subject, i)</code></a></td> 49<tr><td><a href="#re-line"><code>relabelrec.calcline(subject, i)</code></a></td>
51 <td>Calculates line and column information regarding position <i>i</i> of the subject</code> 50 <td>Calculates line and column information regarding position <i>i</i> of the subject</code>
52 </td></tr> 51 </td></tr>
53<tr><td><a href="#re-setl"><code>relabel.setlabels (tlabel)</code></a></td> 52<tr><td><a href="#re-setl"><code>relabelrec.setlabels (tlabel)</code></a></td>
54 <td>Allows to specicify a table with mnemonic labels. 53 <td>Allows to specicify a table with mnemonic labels.
55 </td></tr> 54 </td></tr>
56</tbody></table> 55</tbody></table>
@@ -59,28 +58,20 @@ of the new functions provided by LpegLabel:
59### Functions 58### Functions
60 59
61 60
62#### <a name="f-t"></a><code>lpeglabel.T(l)</code> 61#### <a name="f-t"></a><code>lpeglabelrec.T(l)</code>
63 62
64 63
65Returns a pattern that throws the label `l`. 64Returns a pattern that throws the label `l`.
66A label must be an integer between 0 and 255. 65A label must be an integer between 1 and 255.
67 66
68The label 0 is equivalent to the regular failure of PEGs.
69 67
68#### <a name="f-rec"></a><code>lpeglabelrec.Rec(p1, p2, l1, ..., ln)</code>
70 69
71#### <a name="f-lc"></a><code>lpeglabel.Lc(p1, p2, l1, ..., ln)</code> 70Returns a *recovery pattern*.
72
73Returns a pattern equivalent to a *labeled ordered choice*.
74If the matching of `p1` gives one of the labels `l1, ..., ln`, 71If the matching of `p1` gives one of the labels `l1, ..., ln`,
75then the matching of `p2` is tried from the same position. Otherwise, 72then the matching of `p2` is tried from the failure position of `p1`.
76the result of the matching of `p1` is the pattern's result. 73Otherwise, the result of the matching of `p1` is the pattern's result.
77
78The labeled ordered choice `lpeg.Lc(p1, p2, 0)` is equivalent to the
79regular ordered choice `p1 / p2`.
80 74
81Although PEG's ordered choice is associative, the labeled ordered choice is not.
82When using this function, the user should take care to build a left-associative
83labeled ordered choice pattern.
84 75
85 76
86#### <a name="f-rec"></a><code>lpeglabel.Rec(p1, p2 [, l1, ..., ln])</code> 77#### <a name="f-rec"></a><code>lpeglabel.Rec(p1, p2 [, l1, ..., ln])</code>
@@ -94,29 +85,26 @@ i.e. `lpeg.Rec(p1, p2)` is equivalent to `lpeg.Rec(p1, p2, 0)`.
94 85
95#### <a name="re-t"></a><code>%{l}</code> 86#### <a name="re-t"></a><code>%{l}</code>
96 87
97Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. 88Syntax of *relabelrec* module. Equivalent to `lpeg.T(l)`.
98 89
99 90
100#### <a name="re-lc"></a><code>p1 /{l1, ..., ln} p2</code> 91#### <a name="re-lc"></a><code>p1 //{l1, ..., ln} p2</code>
101 92
102Syntax of *relabel* module. Equivalent to `lpeg.Lc(p1, p2, l1, ..., ln)`. 93Syntax of *relabelrec* module. Equivalent to `lpeglabelrec.Rec(p1, p2, l1, ..., ln)`.
103 94
104The `/{}` operator is left-associative. 95The `//{}` operator is left-associative.
105 96
106A grammar can use both choice operators (`/` and `/{}`),
107but a single choice can not mix them. That is, the parser of `relabel`
108module will not recognize a pattern as `p1 / p2 /{l1} p3`.
109 97
110 98
111#### <a name="re-line"></a><code>relabel.calcline (subject, i)</code> 99#### <a name="re-line"></a><code>relabelrec.calcline (subject, i)</code>
112 100
113Returns line and column information regarding position <i>i</i> of the subject. 101Returns line and column information regarding position <i>i</i> of the subject.
114 102
115 103
116#### <a name="re-setl"></a><code>relabel.setlabels (tlabel)</code> 104#### <a name="re-setl"></a><code>relabelrec.setlabels (tlabel)</code>
117 105
118Allows to specicify a table with labels. They keys of 106Allows to specicify a table with labels. They keys of
119`tlabel` must be integers between 0 and 255, 107`tlabel` must be integers between 1 and 255,
120and the associated values should be strings. 108and the associated values should be strings.
121 109
122 110
@@ -132,16 +120,31 @@ in the *examples* directory.
132The following example defines a grammar that matches 120The following example defines a grammar that matches
133a list of identifiers separated by commas. A label 121a list of identifiers separated by commas. A label
134is thrown when there is an error matching an identifier 122is thrown when there is an error matching an identifier
135or a comma: 123or a comma.
124
125We use function `newError` to store error messages in a
126table and to return the index associated with each error message.
127
136 128
137```lua 129```lua
138local m = require'lpeglabel' 130local m = require'lpeglabelrec'
139local re = require'relabel' 131local re = require'relabelrec'
132
133local terror = {}
134
135local function newError(s)
136 table.insert(terror, s)
137 return #terror
138end
139
140local errUndef = newError("undefined")
141local errId = newError("expecting an identifier")
142local errComma = newError("expecting ','")
140 143
141local g = m.P{ 144local g = m.P{
142 "S", 145 "S",
143 S = m.V"Id" * m.V"List", 146 S = m.V"Id" * m.V"List",
144 List = -m.P(1) + (m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)) * m.V"List", 147 List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List",
145 Id = m.V"Sp" * m.R'az'^1, 148 Id = m.V"Sp" * m.R'az'^1,
146 Comma = m.V"Sp" * ",", 149 Comma = m.V"Sp" * ",",
147 Sp = m.S" \n\t"^0, 150 Sp = m.S" \n\t"^0,
@@ -151,18 +154,12 @@ function mymatch (g, s)
151 local r, e, sfail = g:match(s) 154 local r, e, sfail = g:match(s)
152 if not r then 155 if not r then
153 local line, col = re.calcline(s, #s - #sfail) 156 local line, col = re.calcline(s, #s - #sfail)
154 local msg = "Error at line " .. line .. " (col " .. col .. ")" 157 local msg = "Error at line " .. line .. " (col " .. col .. "): "
155 if e == 1 then 158 return r, msg .. terror[e] .. " before '" .. sfail .. "'"
156 return r, msg .. ": expecting an identifier before '" .. sfail .. "'"
157 elseif e == 2 then
158 return r, msg .. ": expecting ',' before '" .. sfail .. "'"
159 else
160 return r, msg
161 end
162 end 159 end
163 return r 160 return r
164end 161end
165 162
166print(mymatch(g, "one,two")) --> 8 163print(mymatch(g, "one,two")) --> 8
167print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' 164print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two'
168print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' 165print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before ''
@@ -170,23 +167,73 @@ print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expec
170 167
171In this example we could think about writing rule <em>List</em> as follows: 168In this example we could think about writing rule <em>List</em> as follows:
172```lua 169```lua
173List = ((m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)))^0, 170List = ((m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)))^0,
174``` 171```
175 172
176but when matching this expression agains the end of input 173but when matching this expression against the end of input
177we would get a failure whose associated label would be **2**, 174we would get a failure whose associated label would be **errComma**,
178and this would cause the failure of the *whole* repetition. 175and this would cause the failure of the *whole* repetition.
179
180 176
181##### Mnemonics instead of numbers
182 177
183In the previous example we could have created a table
184with the error messages to improve the readbility of the PEG.
185Below we rewrite the previous grammar following this approach:
186 178
179#### Error Recovery
180
181By using the `Rec` function we can specify a recovery pattern that
182should be matched when a label is thrown. After matching the recovery
183pattern, and possibly recording the error, the parser will resume
184the <em>regular</em> matching. For example, in the example below
185we expect to match rule `A`, but when a failure occur the label 42
186is thrown and then we will try to match the recovery pattern `recp`:
187```lua 187```lua
188local m = require'lpeglabel' 188local m = require'lpeglabelrec'
189local re = require'relabel' 189
190local recp = m.P"oast"
191
192local g = m.P{
193 "S",
194 S = m.Rec(m.V"A", recp, 42) * ".",
195 A = m.P"t" * (m.P"est" + m.T(42))
196}
197
198print(g:match("test.")) --> 6
199print(g:match("toast.")) --> 7
200print(g:match("oast.")) --> nil 0 oast.
201print(g:match("toward.")) --> nil 0 ward.
202```
203When trying to match subject 'toast.', in rule `A` the first
204't' is matched, then the matching of `m.P"est"` fails and label 42
205is thrown, with the associated inpux suffix 'oast.'. In rule
206`S` label 42 is caught and the recovery pattern matches 'oast',
207so pattern `'.'` matches the rest of the input.
208
209When matching subject 'oast.', pattern `m.P"t"` fails, and
210the result of the matching is <b>nil, 0, oast.</b>.
211
212When matching 'toward.', label 42 is thrown after matching 't',
213with the associated input suffix 'oward.'. As the matching of the
214recovery pattern fails, the result is <b>nil, 0, ward.</b>.
215
216Usually, the recovery pattern is an expression that does not fail.
217In the previous example, we could have used `(m.P(1) - m.P".")^0`
218as the recovery pattern.
219
220Below we rewrite the grammar that describes a list of identifiers
221to use a recovery strategy. Grammar `g` remains the same, but we add a
222recovery grammar `grec` that handles the labels thrown by `g`.
223
224In grammar `grec` we use functions `record` and `sync`.
225Function `record`, plus function `recorderror`, will help
226us to save the input position where a label was thrown,
227while function `sync` will give us a synchronization pattern,
228that consumes the input while is not possible to match a given
229pattern `p`.
230
231When the matching of an identifier fails, a defaul value ('NONE')
232is provided.
233
234```lua
235local m = require'lpeglabelrec'
236local re = require'relabelrec'
190 237
191local terror = {} 238local terror = {}
192 239
@@ -199,73 +246,88 @@ local errUndef = newError("undefined")
199local errId = newError("expecting an identifier") 246local errId = newError("expecting an identifier")
200local errComma = newError("expecting ','") 247local errComma = newError("expecting ','")
201 248
249local id = m.R'az'^1
250
202local g = m.P{ 251local g = m.P{
203 "S", 252 "S",
204 S = m.V"Id" * m.V"List", 253 S = m.V"Id" * m.V"List",
205 List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", 254 List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List",
206 Id = m.V"Sp" * m.R'az'^1, 255 Id = m.V"Sp" * id + m.T(errId),
207 Comma = m.V"Sp" * ",", 256 Comma = m.V"Sp" * "," + m.T(errComma),
208 Sp = m.S" \n\t"^0, 257 Sp = m.S" \n\t"^0,
209} 258}
210 259
211function mymatch (g, s) 260local subject, errors
212 local r, e, sfail = g:match(s)
213 if not r then
214 local line, col = re.calcline(s, #s - #sfail)
215 local msg = "Error at line " .. line .. " (col " .. col .. "): "
216 return r, msg .. terror[e] .. " before '" .. sfail .. "'"
217 end
218 return r
219end
220
221print(mymatch(g, "one,two")) --> 8
222print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two'
223print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before ''
224```
225 261
262function recorderror(pos, lab)
263 local line, col = re.calcline(subject, pos)
264 table.insert(errors, { line = line, col = col, msg = terror[lab] })
265end
226 266
227##### *relabel* syntax 267function record (lab)
268 return (m.Cp() * m.Cc(lab)) / recorderror
269end
228 270
229Now we rewrite the previous example using the syntax 271function sync (p)
230supported by *relabel*: 272 return (-p * m.P(1))^0
273end
231 274
232```lua 275local grec = m.P{
233local re = require 'relabel' 276 "S",
277 S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId),
278 ErrComma = record(errComma) * sync(id),
279 ErrId = record(errId) * sync(m.P",")
280}
234 281
235local g = re.compile[[
236 S <- Id List
237 List <- !. / (',' / %{2}) (Id / %{1}) List
238 Id <- Sp [a-z]+
239 Comma <- Sp ','
240 Sp <- %s*
241]]
242 282
243function mymatch (g, s) 283function mymatch (g, s)
284 errors = {}
285 subject = s
244 local r, e, sfail = g:match(s) 286 local r, e, sfail = g:match(s)
245 if not r then 287 if #errors > 0 then
246 local line, col = re.calcline(s, #s - #sfail) 288 local out = {}
247 local msg = "Error at line " .. line .. " (col " .. col .. ")" 289 for i, err in ipairs(errors) do
248 if e == 1 then 290 local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
249 return r, msg .. ": expecting an identifier before '" .. sfail .. "'" 291 table.insert(out, msg)
250 elseif e == 2 then
251 return r, msg .. ": expecting ',' before '" .. sfail .. "'"
252 else
253 return r, msg
254 end 292 end
293 return nil, table.concat(out, "\n") .. "\n"
255 end 294 end
256 return r 295 return r
257end 296end
258 297
259print(mymatch(g, "one,two")) --> 8 298print(mymatch(grec, "one,two"))
260print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' 299-- Captures (separated by ';'): one; two;
261print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' 300-- Syntactic errors found: 0
301
302print(mymatch(grec, "one two three"))
303-- Captures (separated by ';'): one; two; three;
304-- Syntactic errors found: 2
305-- Error at line 1 (col 4): expecting ','
306-- Error at line 1 (col 8): expecting ','
307
308print(mymatch(grec, "1,\n two, \n3,"))
309-- Captures (separated by ';'): NONE; two; NONE; NONE;
310-- Syntactic errors found: 3
311-- Error at line 1 (col 1): expecting an identifier
312-- Error at line 2 (col 6): expecting an identifier
313-- Error at line 3 (col 2): expecting an identifier
314
315print(mymatch(grec, "one\n two123, \nthree,"))
316-- Captures (separated by ';'): one; two; three; NONE;
317-- Syntactic errors found: 3
318-- Error at line 2 (col 1): expecting ','
319-- Error at line 2 (col 5): expecting ','
320-- Error at line 3 (col 6): expecting an identifier
262``` 321```
263 322
264With the help of function *setlabels* we can also rewrite the previous example to use 323##### *relabelrec* syntax
265mnemonic labels instead of plain numbers: 324
325Below we describe again a grammar that matches a list of identifiers,
326now using the syntax supported by *relabelrec*, where `//{}` is the
327recovery operator, and `%{}` is the throw operator:
266 328
267```lua 329```lua
268local re = require 'relabel' 330local re = require 'relabelrec'
269 331
270local errinfo = { 332local errinfo = {
271 {"errUndef", "undefined"}, 333 {"errUndef", "undefined"},
@@ -285,59 +347,124 @@ re.setlabels(labels)
285 347
286local g = re.compile[[ 348local g = re.compile[[
287 S <- Id List 349 S <- Id List
288 List <- !. / (',' / %{errComma}) (Id / %{errId}) List 350 List <- !. / Comma Id List
289 Id <- Sp [a-z]+ 351 Id <- Sp {[a-z]+} / %{errId}
290 Comma <- Sp ',' 352 Comma <- Sp ',' / %{errComma}
291 Sp <- %s* 353 Sp <- %s*
292]] 354]]
293 355
356local errors
357
358function recorderror (subject, pos, label)
359 local line, col = re.calcline(subject, pos)
360 table.insert(errors, { line = line, col = col, msg = errmsgs[labels[label]] })
361 return true
362end
363
364function sync (p)
365 return '( !(' .. p .. ') .)*'
366end
367
368local grec = re.compile(
369 "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" ..
370 "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('[a-z]+') .. "\n" ..
371 "ErrId <- ('' -> 'errId' => recorderror) " .. sync('","') .. "-> default"
372 , {g = g, recorderror = recorderror, default = "NONE"}
373)
374
294function mymatch (g, s) 375function mymatch (g, s)
295 local r, e, sfail = g:match(s) 376 errors = {}
296 if not r then 377 subject = s
297 local line, col = re.calcline(s, #s - #sfail) 378 io.write("Input: ", s, "\n")
298 local msg = "Error at line " .. line .. " (col " .. col .. "): " 379 local r = { g:match(s) }
299 return r, msg .. errmsgs[e] .. " before '" .. sfail .. "'" 380 io.write("Captures (separated by ';'): ")
381 for k, v in pairs(r) do
382 io.write(v .. "; ")
300 end 383 end
384 io.write("\nSyntactic errors found: " .. #errors)
385 if #errors > 0 then
386 io.write("\n")
387 local out = {}
388 for i, err in ipairs(errors) do
389 local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
390 table.insert(out, msg)
391 end
392 io.write(table.concat(out, "\n"))
393 end
394 print("\n")
301 return r 395 return r
302end 396end
303 397
304print(mymatch(g, "one,two")) --> 8 398print(mymatch(grec, "one,two"))
305print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' 399-- Captures (separated by ';'): one; two;
306print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' 400-- Syntactic errors found: 0
401
402print(mymatch(grec, "one two three"))
403-- Captures (separated by ';'): one; two; three;
404-- Syntactic errors found: 2
405-- Error at line 1 (col 4): expecting ','
406-- Error at line 1 (col 8): expecting ','
407
408print(mymatch(grec, "1,\n two, \n3,"))
409-- Captures (separated by ';'): NONE; two; NONE; NONE;
410-- Syntactic errors found: 3
411-- Error at line 1 (col 1): expecting an identifier
412-- Error at line 2 (col 6): expecting an identifier
413-- Error at line 3 (col 2): expecting an identifier
414
415print(mymatch(grec, "one\n two123, \nthree,"))
416-- Captures (separated by ';'): one; two; three; NONE;
417-- Syntactic errors found: 3
418-- Error at line 2 (col 1): expecting ','
419-- Error at line 2 (col 5): expecting ','
420-- Error at line 3 (col 6): expecting an identifier
307``` 421```
308 422
423
309#### Arithmetic Expressions 424#### Arithmetic Expressions
310 425
311Here's an example of an LPegLabel grammar that make its own function called 426Here's an example of an LPegLabel grammar that matches an expression.
312'expect', which takes a pattern and a label as parameters and throws the label 427We have used a function `expect`, that takes a pattern `patt` and a label as
313if the pattern fails to be matched. This function can be extended later on to 428parameters and builds a new pattern that throws this label when `patt`
314record all errors encountered once error recovery is implemented. 429fails.
315 430
316```lua 431When a subexpression is syntactically invalid, a default value of 1000
317local lpeg = require"lpeglabel" 432is provided by the recovery pattern, so the evaluation of an expression
433should always produce a numeric value.
318 434
319local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T 435In this example, we can see that it may be a tedious and error prone
436task to build manually the recovery grammar `grec`. In the next example
437we will show how to build the recovery grammar in a more automatic way.
438
439```lua
440local m = require"lpeglabelrec"
441local re = require"relabelrec"
320 442
321local labels = { 443local labels = {
322 {"NoExp", "no expression found"}, 444 {"ExpTermFirst", "expected an expression"},
323 {"Extra", "extra characters found after the expression"}, 445 {"ExpTermOp", "expected a term after the operator"},
324 {"ExpTerm", "expected a term after the operator"},
325 {"ExpExp", "expected an expression after the parenthesis"},
326 {"MisClose", "missing a closing ')' after the expression"}, 446 {"MisClose", "missing a closing ')' after the expression"},
327} 447}
328 448
329local function expect(patt, labname) 449local function labelindex(labname)
330 for i, elem in ipairs(labels) do 450 for i, elem in ipairs(labels) do
331 if elem[1] == labname then 451 if elem[1] == labname then
332 return patt + T(i) 452 return i
333 end 453 end
334 end 454 end
335
336 error("could not find label: " .. labname) 455 error("could not find label: " .. labname)
337end 456end
338 457
339local num = R("09")^1 / tonumber 458local errors, subject
340local op = S("+-*/") 459
460local function expect(patt, labname)
461 local i = labelindex(labname)
462 return patt + m.T(i)
463end
464
465
466local num = m.R("09")^1 / tonumber
467local op = m.S("+-")
341 468
342local function compute(tokens) 469local function compute(tokens)
343 local result = tokens[1] 470 local result = tokens[1]
@@ -346,10 +473,6 @@ local function compute(tokens)
346 result = result + tokens[i+1] 473 result = result + tokens[i+1]
347 elseif tokens[i] == '-' then 474 elseif tokens[i] == '-' then
348 result = result - tokens[i+1] 475 result = result - tokens[i+1]
349 elseif tokens[i] == '*' then
350 result = result * tokens[i+1]
351 elseif tokens[i] == '/' then
352 result = result / tokens[i+1]
353 else 476 else
354 error('unknown operation: ' .. tokens[i]) 477 error('unknown operation: ' .. tokens[i])
355 end 478 end
@@ -357,81 +480,223 @@ local function compute(tokens)
357 return result 480 return result
358end 481end
359 482
360local g = P { 483local g = m.P {
361 "Exp", 484 "Exp",
362 Exp = Ct(V"Term" * (C(op) * expect(V"Term", "ExpTerm"))^0) / compute; 485 Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute,
363 Term = num + V"Group"; 486 OperandFirst = expect(m.V"Term", "ExpTermFirst"),
364 Group = "(" * expect(V"Exp", "ExpExp") * expect(")", "MisClose"); 487 Operand = expect(m.V"Term", "ExpTermOp"),
488 Term = num + m.V"Group",
489 Group = "(" * m.V"Exp" * expect(")", "MisClose"),
365} 490}
366 491
367g = expect(g, "NoExp") * expect(-P(1), "Extra") 492function recorderror(pos, lab)
493 local line, col = re.calcline(subject, pos)
494 table.insert(errors, { line = line, col = col, msg = labels[lab][2] })
495end
368 496
497function record (labname)
498 return (m.Cp() * m.Cc(labelindex(labname))) / recorderror
499end
500
501function sync (p)
502 return (-p * m.P(1))^0
503end
504
505function defaultValue (p)
506 return p or m.Cc(1000)
507end
508
509local grec = m.P {
510 "S",
511 S = m.Rec(m.V"A", m.V"ErrExpTermFirst", labelindex("ExpTermFirst")),
512 A = m.Rec(m.V"Sg", m.V"ErrExpTermOp", labelindex("ExpTermOp")),
513 Sg = m.Rec(g, m.V"ErrMisClose", labelindex("MisClose")),
514 ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(),
515 ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(),
516 ErrMisClose = record("MisClose") * sync(m.P")") * defaultValue(m.P""),
517}
518
369local function eval(input) 519local function eval(input)
370 local result, label, suffix = g:match(input) 520 errors = {}
371 if result ~= nil then 521 io.write("Input: ", input, "\n")
372 return result 522 subject = input
373 else 523 local result, label, suffix = grec:match(input)
374 local pos = input:len() - suffix:len() + 1 524 io.write("Syntactic errors found: " .. #errors, "\n")
375 local msg = labels[label][2] 525 if #errors > 0 then
376 return nil, "syntax error: " .. msg .. " (at index " .. pos .. ")" 526 local out = {}
527 for i, err in ipairs(errors) do
528 local pos = err.col
529 local msg = err.msg
530 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
531 end
532 print(table.concat(out, "\n"))
377 end 533 end
534 io.write("Result = ")
535 return result
378end 536end
379 537
380print(eval "98-76*(54/32)") 538print(eval "90-70-(5)+3")
381--> 37.125 539-- Syntactic errors found: 0
540-- Result = 18
541
542print(eval "15+")
543-- Syntactic errors found: 1
544-- syntax error: expected a term after the operator (at index 3)
545-- Result = 1015
546
547print(eval "-2")
548-- Syntactic errors found: 1
549-- syntax error: expected an expression (at index 1)
550-- Result = 998
551
552print(eval "1+()+")
553-- Syntactic errors found: 2
554-- syntax error: expected an expression (at index 4)
555-- syntax error: expected a term after the operator (at index 5)
556-- Result = 2001
557
558print(eval "1+(")
559-- Syntactic errors found: 2
560-- syntax error: expected an expression (at index 3)
561-- syntax error: missing a closing ')' after the expression (at index 3)
562-- Result = 1001
563
564print(eval "3)")
565-- Syntactic errors found: 0
566-- Result = 3
567```
382 568
383print(eval "(1+1-1*2/2") 569#### Automatically Building the Recovery Grammar
384--> syntax error: missing a closing ')' after the expression (at index 11)
385 570
386print(eval "(1+)-1*(2/2)") 571Below we rewrite the previous example to automatically
387--> syntax error: expected a term after the operator (at index 4) 572build the recovery grammar based on information provided
573by the user for each label (error message, recovery pattern, etc).
574In the example below we also throw an error when the grammar
575does not match the whole subject.
388 576
389print(eval "(1+1)-1*(/2)") 577```lua
390--> syntax error: expected an expression after the parenthesis (at index 10) 578local m = require"lpeglabelrec"
579local re = require"relabelrec"
391 580
392print(eval "1+(1-(1*2))/2x") 581local num = m.R("09")^1 / tonumber
393--> syntax error: extra chracters found after the expression (at index 14) 582local op = m.S("+-")
394 583
395print(eval "-1+(1-(1*2))/2") 584local labels = {}
396--> syntax error: no expression found (at index 1) 585local nlabels = 0
397```
398 586
399#### Catching labels 587local function newError(lab, msg, psync, pcap)
588 nlabels = nlabels + 1
589 psync = psync or m.P(-1)
590 pcap = pcap or m.P""
591 labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap }
592end
400 593
401When a label is thrown, the grammar itself can handle this label 594newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000))
402by using the labeled ordered choice. Below we rewrite the example 595newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000))
403of the list of identifiers to show this feature: 596newError("MisClose", "missing a closing ')' after the expression", m.P")")
597newError("Extra", "extra characters found after the expression")
404 598
599local errors, subject
405 600
406```lua 601local function expect(patt, labname)
407local m = require'lpeglabel' 602 local i = labels[labname].id
603 return patt + m.T(i)
604end
408 605
409local terror = {} 606local function compute(tokens)
607 local result = tokens[1]
608 for i = 2, #tokens, 2 do
609 if tokens[i] == '+' then
610 result = result + tokens[i+1]
611 elseif tokens[i] == '-' then
612 result = result - tokens[i+1]
613 else
614 error('unknown operation: ' .. tokens[i])
615 end
616 end
617 return result
618end
410 619
411local function newError(s) 620local g = m.P {
412 table.insert(terror, s) 621 "Exp",
413 return #terror 622 Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute,
623 OperandFirst = expect(m.V"Term", "ExpTermFirst"),
624 Operand = expect(m.V"Term", "ExpTermOp"),
625 Term = num + m.V"Group",
626 Group = "(" * m.V"Exp" * expect(")", "MisClose"),
627}
628
629function recorderror(pos, lab)
630 local line, col = re.calcline(subject, pos)
631 table.insert(errors, { line = line, col = col, msg = labels[lab].msg })
414end 632end
415 633
416local errUndef = newError("undefined") 634function record (labname)
417local errId = newError("expecting an identifier") 635 return (m.Cp() * m.Cc(labname)) / recorderror
418local errComma = newError("expecting ','") 636end
419 637
420local g = m.P{ 638function sync (p)
421 "S", 639 return (-p * m.P(1))^0
422 S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), 640end
423 m.V"ErrComma", errComma), 641
424 List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", 642function defaultValue (p)
425 Id = m.V"Sp" * m.R'az'^1, 643 return p or m.Cc(1000)
426 Comma = m.V"Sp" * ",", 644end
427 Sp = m.S" \n\t"^0, 645
428 ErrId = m.Cc(errId) / terror, 646local grec = g * expect(m.P(-1), "Extra")
429 ErrComma = m.Cc(errComma) / terror 647for k, v in pairs(labels) do
430} 648 grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id)
649end
650
651local function eval(input)
652 errors = {}
653 io.write("Input: ", input, "\n")
654 subject = input
655 local result, label, suffix = grec:match(input)
656 io.write("Syntactic errors found: " .. #errors, "\n")
657 if #errors > 0 then
658 local out = {}
659 for i, err in ipairs(errors) do
660 local pos = err.col
661 local msg = err.msg
662 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
663 end
664 print(table.concat(out, "\n"))
665 end
666 io.write("Result = ")
667 return result
668end
431 669
432print(m.match(g, "one,two")) --> 8 670print(eval "90-70-(5)+3")
433print(m.match(g, "one two")) --> expecting ',' 671-- Syntactic errors found: 0
434print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier 672-- Result = 18
673
674print(eval "15+")
675-- Syntactic errors found: 1
676-- syntax error: expected a term after the operator (at index 3)
677-- Result = 1015
678
679print(eval "-2")
680-- Syntactic errors found: 1
681-- syntax error: expected an expression (at index 1)
682-- Result = 998
683
684print(eval "1+()+")
685-- Syntactic errors found: 2
686-- syntax error: expected an expression (at index 4)
687-- syntax error: expected a term after the operator (at index 5)
688-- Result = 2001
689
690print(eval "1+(")
691-- Syntactic errors found: 2
692-- syntax error: expected an expression (at index 3)
693-- syntax error: missing a closing ')' after the expression (at index 3)
694-- Result = 1001
695
696print(eval "3)")
697-- Syntactic errors found: 1
698-- syntax error: extra characters found after the expression (at index 2)
699-- Result = 3
435``` 700```
436 701
437#### Error Recovery 702#### Error Recovery
diff --git a/examples/expRec.lua b/examples/expRec.lua
new file mode 100644
index 0000000..5c5fd7d
--- /dev/null
+++ b/examples/expRec.lua
@@ -0,0 +1,128 @@
1local m = require"lpeglabelrec"
2local re = require"relabelrec"
3
4local labels = {
5 {"ExpTermFirst", "expected an expression"},
6 {"ExpTermOp", "expected a term after the operator"},
7 {"MisClose", "missing a closing ')' after the expression"},
8}
9
10local function labelindex(labname)
11 for i, elem in ipairs(labels) do
12 if elem[1] == labname then
13 return i
14 end
15 end
16 error("could not find label: " .. labname)
17end
18
19local errors, subject
20
21local function expect(patt, labname)
22 local i = labelindex(labname)
23 return patt + m.T(i)
24end
25
26
27local num = m.R("09")^1 / tonumber
28local op = m.S("+-")
29
30local function compute(tokens)
31 local result = tokens[1]
32 for i = 2, #tokens, 2 do
33 if tokens[i] == '+' then
34 result = result + tokens[i+1]
35 elseif tokens[i] == '-' then
36 result = result - tokens[i+1]
37 else
38 error('unknown operation: ' .. tokens[i])
39 end
40 end
41 return result
42end
43
44local g = m.P {
45 "Exp",
46 Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute,
47 OperandFirst = expect(m.V"Term", "ExpTermFirst"),
48 Operand = expect(m.V"Term", "ExpTermOp"),
49 Term = num + m.V"Group",
50 Group = "(" * m.V"Exp" * expect(")", "MisClose"),
51}
52
53function recorderror(pos, lab)
54 local line, col = re.calcline(subject, pos)
55 table.insert(errors, { line = line, col = col, msg = labels[lab][2] })
56end
57
58function record (labname)
59 return (m.Cp() * m.Cc(labelindex(labname))) / recorderror
60end
61
62function sync (p)
63 return (-p * m.P(1))^0
64end
65
66function defaultValue (p)
67 return p or m.Cc(1000)
68end
69
70local grec = m.P {
71 "S",
72 S = m.Rec(m.V"A", m.V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0
73 A = m.Rec(m.V"Sg", m.V"ErrExpTermOp", labelindex("ExpTermOp")),
74 Sg = m.Rec(g, m.V"ErrMisClose", labelindex("MisClose")),
75 ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(),
76 ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(),
77 ErrMisClose = record("MisClose") * sync(m.P")") * defaultValue(m.P""),
78}
79
80local function eval(input)
81 errors = {}
82 io.write("Input: ", input, "\n")
83 subject = input
84 local result, label, suffix = grec:match(input)
85 io.write("Syntactic errors found: " .. #errors, "\n")
86 if #errors > 0 then
87 local out = {}
88 for i, err in ipairs(errors) do
89 local pos = err.col
90 local msg = err.msg
91 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
92 end
93 print(table.concat(out, "\n"))
94 end
95 io.write("Result = ")
96 return result
97end
98
99print(eval "90-70-(5)+3")
100--> 20
101
102print(eval "15+")
103--> 2 + 0
104
105print(eval "-2")
106--> 0 - 2
107
108print(eval "1+3+-9")
109--> 1 + 3 + [0] - 9
110
111print(eval "1+()3+")
112--> 1 + ([0]) [3 +] [0]
113
114print(eval "8-(2+)-5")
115--> 8 - (2 + [0]) - 5
116
117print(eval "()")
118
119print(eval "")
120
121print(eval "1+()+")
122
123print(eval "1+(")
124
125print(eval "3)")
126
127print(eval "11+())3")
128
diff --git a/examples/expRecAut.lua b/examples/expRecAut.lua
new file mode 100644
index 0000000..f870d73
--- /dev/null
+++ b/examples/expRecAut.lua
@@ -0,0 +1,122 @@
1local m = require"lpeglabelrec"
2local re = require"relabelrec"
3
4local num = m.R("09")^1 / tonumber
5local op = m.S("+-")
6
7local labels = {}
8local nlabels = 0
9
10local function newError(lab, msg, psync, pcap)
11 nlabels = nlabels + 1
12 psync = psync or m.P(-1)
13 pcap = pcap or m.P""
14 labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap }
15end
16
17newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000))
18newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000))
19newError("MisClose", "missing a closing ')' after the expression", m.P")")
20newError("Extra", "extra characters found after the expression")
21
22local errors, subject
23
24local function expect(patt, labname)
25 local i = labels[labname].id
26 return patt + m.T(i)
27end
28
29local function compute(tokens)
30 local result = tokens[1]
31 for i = 2, #tokens, 2 do
32 if tokens[i] == '+' then
33 result = result + tokens[i+1]
34 elseif tokens[i] == '-' then
35 result = result - tokens[i+1]
36 else
37 error('unknown operation: ' .. tokens[i])
38 end
39 end
40 return result
41end
42
43local g = m.P {
44 "Exp",
45 Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute,
46 OperandFirst = expect(m.V"Term", "ExpTermFirst"),
47 Operand = expect(m.V"Term", "ExpTermOp"),
48 Term = num + m.V"Group",
49 Group = "(" * m.V"Exp" * expect(")", "MisClose"),
50}
51
52function recorderror(pos, lab)
53 local line, col = re.calcline(subject, pos)
54 table.insert(errors, { line = line, col = col, msg = labels[lab].msg })
55end
56
57function record (labname)
58 return (m.Cp() * m.Cc(labname)) / recorderror
59end
60
61function sync (p)
62 return (-p * m.P(1))^0
63end
64
65function defaultValue (p)
66 return p or m.Cc(1000)
67end
68
69local grec = g * expect(m.P(-1), "Extra")
70for k, v in pairs(labels) do
71 grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id)
72end
73
74local function eval(input)
75 errors = {}
76 io.write("Input: ", input, "\n")
77 subject = input
78 local result, label, suffix = grec:match(input)
79 io.write("Syntactic errors found: " .. #errors, "\n")
80 if #errors > 0 then
81 local out = {}
82 for i, err in ipairs(errors) do
83 local pos = err.col
84 local msg = err.msg
85 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
86 end
87 print(table.concat(out, "\n"))
88 end
89 io.write("Result = ")
90 return result
91end
92
93print(eval "90-70-(5)+3")
94--> 18
95
96print(eval "15+")
97--> 2 + 0
98
99print(eval "-2")
100--> 0 - 2
101
102print(eval "1+3+-9")
103--> 1 + 3 + [0] - 9
104
105print(eval "1+()3+")
106--> 1 + ([0]) [+] 3 + [0]
107
108print(eval "8-(2+)-5")
109--> 8 - (2 + [0]) - 5
110
111print(eval "()")
112
113print(eval "")
114
115print(eval "1+()+")
116
117print(eval "1+(")
118
119print(eval "3)")
120
121print(eval "11+()3")
122--> 1 + ([0]) [+] 3 + [0]
diff --git a/examples/expect.lua b/examples/expect.lua
deleted file mode 100644
index cb68d38..0000000
--- a/examples/expect.lua
+++ /dev/null
@@ -1,98 +0,0 @@
1local lpeg = require"lpeglabel"
2
3local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T
4
5-- The `labels` table contains the list of labels that we will be using
6-- as well as the corresponding error message for each label, which will
7-- be used in our error reporting later on.
8local labels = {
9 {"NoExp", "no expression found"},
10 {"Extra", "extra characters found after the expression"},
11 {"ExpTerm", "expected a term after the operator"},
12 {"ExpExp", "expected an expression after the parenthesis"},
13 {"MisClose", "missing a closing ')' after the expression"},
14}
15
16-- The `expect` function takes a pattern and a label defined in
17-- the `labels` table and returns a pattern that throws the specified
18-- label if the original pattern fails to match.
19-- Note: LPegLabel requires us to use integers for the labels, so we
20-- use the index of the label in the `labels` table to represent it.
21local function expect(patt, labname)
22 for i, elem in ipairs(labels) do
23 if elem[1] == labname then
24 return patt + T(i)
25 end
26 end
27
28 error("could not find label: " .. labname)
29end
30
31local num = R("09")^1 / tonumber
32local op = S("+-*/")
33
34-- The `compute` function takes an alternating list of numbers and
35-- operators and computes the result of applying the operations
36-- to the numbers in a left to right order (no operator precedence).
37local function compute(tokens)
38 local result = tokens[1]
39 for i = 2, #tokens, 2 do
40 if tokens[i] == '+' then
41 result = result + tokens[i+1]
42 elseif tokens[i] == '-' then
43 result = result - tokens[i+1]
44 elseif tokens[i] == '*' then
45 result = result * tokens[i+1]
46 elseif tokens[i] == '/' then
47 result = result / tokens[i+1]
48 else
49 error('unknown operation: ' .. tokens[i])
50 end
51 end
52 return result
53end
54
55-- Our grammar is a simple arithmetic expression of integers that
56-- does not take operator precedence into account but allows grouping
57-- via parenthesis.
58local g = P {
59 "Exp",
60 Exp = Ct(V"Term" * (C(op) * expect(V"Term", "ExpTerm"))^0) / compute;
61 Term = num + V"Group";
62 Group = "(" * expect(V"Exp", "ExpExp") * expect(")", "MisClose");
63}
64
65g = expect(g, "NoExp") * expect(-P(1), "Extra")
66
67-- The `eval` function takes an input string to match against the grammar
68-- we've just defined. If the input string matches, then the result of the
69-- computation is returned, otherwise we return the error message and
70-- position of the first failure encountered.
71local function eval(input)
72 local result, label, suffix = g:match(input)
73 if result ~= nil then
74 return result
75 else
76 local pos = input:len() - suffix:len() + 1
77 local msg = labels[label][2]
78 return nil, "syntax error: " .. msg .. " (at index " .. pos .. ")"
79 end
80end
81
82print(eval "98-76*(54/32)")
83--> 37.125
84
85print(eval "(1+1-1*2/2")
86--> syntax error: missing a closing ')' after the expression (at index 11)
87
88print(eval "(1+)-1*(2/2)")
89--> syntax error: expected a term after the operator (at index 4)
90
91print(eval "(1+1)-1*(/2)")
92--> syntax error: expected an expression after the parenthesis (at index 10)
93
94print(eval "1+(1-(1*2))/2x")
95--> syntax error: extra chracters found after the expression (at index 14)
96
97print(eval "-1+(1-(1*2))/2")
98--> syntax error: no expression found (at index 1)
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 @@
1local m = require'lpeglabel' 1local m = require'lpeglabelrec'
2local re = require'relabel' 2local re = require'relabelrec'
3
4local id = m.R'az'^1
3 5
4local g = m.P{ 6local 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 @@
1local m = require'lpeglabel' 1local m = require'lpeglabelrec'
2local re = require'relabel' 2local re = require'relabelrec'
3 3
4local terror = {} 4local terror = {}
5 5
@@ -12,15 +12,18 @@ local errUndef = newError("undefined")
12local errId = newError("expecting an identifier") 12local errId = newError("expecting an identifier")
13local errComma = newError("expecting ','") 13local errComma = newError("expecting ','")
14 14
15local id = m.R'az'^1
16
15local g = m.P{ 17local 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
24function mymatch (g, s) 27function 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..c6705dd
--- /dev/null
+++ b/examples/listId2Rec2.lua
@@ -0,0 +1,67 @@
1local m = require'lpeglabelrec'
2local re = require'relabelrec'
3
4local terror = {}
5
6local function newError(s)
7 table.insert(terror, s)
8 return #terror
9end
10
11local errUndef = newError("undefined")
12local errId = newError("expecting an identifier")
13local errComma = newError("expecting ','")
14
15local id = m.R'az'^1
16
17local 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
26local subject, errors
27
28function recorderror(pos, lab)
29 local line, col = re.calcline(subject, pos)
30 table.insert(errors, { line = line, col = col, msg = terror[lab] })
31end
32
33function record (lab)
34 return (m.Cp() * m.Cc(lab)) / recorderror
35end
36
37function sync (p)
38 return (-p * m.P(1))^0
39end
40
41local 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(id),
45 ErrId = record(errId) * sync(m.P",")
46}
47
48
49function 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") .. "\n"
60 end
61 return r
62end
63
64print(mymatch(grec, "one,two"))
65print(mymatch(grec, "one two three"))
66print(mymatch(grec, "1,\n two, \n3,"))
67print(mymatch(grec, "one\n two123, \nthree,"))
diff --git a/examples/listId2Rec2Cap.lua b/examples/listId2Rec2Cap.lua
new file mode 100644
index 0000000..1c22c88
--- /dev/null
+++ b/examples/listId2Rec2Cap.lua
@@ -0,0 +1,79 @@
1local m = require'lpeglabelrec'
2local re = require'relabelrec'
3
4local terror = {}
5
6local function newError(s)
7 table.insert(terror, s)
8 return #terror
9end
10
11local errUndef = newError("undefined")
12local errId = newError("expecting an identifier")
13local errComma = newError("expecting ','")
14
15local id = m.R'az'^1
16
17local 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" * m.C(id) + m.T(errId),
22 Comma = m.V"Sp" * "," + m.T(errComma),
23 Sp = m.S" \n\t"^0,
24}
25
26local subject, errors
27
28function recorderror(pos, lab)
29 local line, col = re.calcline(subject, pos)
30 table.insert(errors, { line = line, col = col, msg = terror[lab] })
31end
32
33function record (lab)
34 return (m.Cp() * m.Cc(lab)) / recorderror
35end
36
37function sync (p)
38 return (-p * m.P(1))^0
39end
40
41function defaultValue ()
42 return m.Cc"NONE"
43end
44
45local grec = m.P{
46 "S",
47 S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId),
48 ErrComma = record(errComma) * sync(id),
49 ErrId = record(errId) * sync(m.P",") * defaultValue(),
50}
51
52
53function mymatch (g, s)
54 errors = {}
55 subject = s
56 io.write("Input: ", s, "\n")
57 local r = { g:match(s) }
58 io.write("Captures (separated by ';'): ")
59 for k, v in pairs(r) do
60 io.write(v .. "; ")
61 end
62 io.write("\nSyntactic errors found: " .. #errors)
63 if #errors > 0 then
64 io.write("\n")
65 local out = {}
66 for i, err in ipairs(errors) do
67 local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
68 table.insert(out, msg)
69 end
70 io.write(table.concat(out, "\n"))
71 end
72 print("\n")
73 return r
74end
75
76mymatch(grec, "one,two")
77mymatch(grec, "one two three")
78mymatch(grec, "1,\n two, \n3,")
79mymatch(grec, "one\n two123, \nthree,")
diff --git a/examples/listIdCatch.lua b/examples/listIdCatch.lua
deleted file mode 100644
index 5ad6f2d..0000000
--- a/examples/listIdCatch.lua
+++ /dev/null
@@ -1,28 +0,0 @@
1local m = require'lpeglabel'
2
3local terror = {}
4
5local function newError(s)
6 table.insert(terror, s)
7 return #terror
8end
9
10local errUndef = newError("undefined")
11local errId = newError("expecting an identifier")
12local errComma = newError("expecting ','")
13
14local g = m.P{
15 "S",
16 S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId),
17 m.V"ErrComma", errComma),
18 List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List",
19 Id = m.V"Sp" * m.R'az'^1,
20 Comma = m.V"Sp" * ",",
21 Sp = m.S" \n\t"^0,
22 ErrId = m.Cc(errId) / terror,
23 ErrComma = m.Cc(errComma) / terror
24}
25
26print(m.match(g, "one,two"))
27print(m.match(g, "one two"))
28print(m.match(g, "one,\n two,\nthree,"))
diff --git a/examples/listIdCatchRe.lua b/examples/listIdCatchRe.lua
deleted file mode 100644
index 8971191..0000000
--- a/examples/listIdCatchRe.lua
+++ /dev/null
@@ -1,34 +0,0 @@
1local re = require'relabel'
2
3local terror = {}
4
5local function newError(l, msg)
6 table.insert(terror, { l = l, msg = msg } )
7end
8
9newError("errId", "Error: expecting an identifier")
10newError("errComma", "Error: expecting ','")
11
12local labelCode = {}
13local labelMsg = {}
14for k, v in ipairs(terror) do
15 labelCode[v.l] = k
16 labelMsg[v.l] = v.msg
17end
18
19re.setlabels(labelCode)
20
21local p = re.compile([[
22 S <- Id List /{errId} ErrId /{errComma} ErrComma
23 List <- !. / Comma Id List
24 Id <- [a-z]+ / %{errId}
25 Comma <- ',' / %{errComma}
26 ErrId <- '' -> errId
27 ErrComma <- '' -> errComma
28]], labelMsg)
29
30print(p:match("a,b"))
31print(p:match("a b"))
32print(p:match(",b"))
33
34
diff --git a/examples/listIdRe1.lua b/examples/listIdRe1.lua
index d092566..3988a3b 100644
--- a/examples/listIdRe1.lua
+++ b/examples/listIdRe1.lua
@@ -1,10 +1,10 @@
1local re = require 'relabel' 1local re = require 'relabelrec'
2 2
3local g = re.compile[[ 3local g = re.compile[[
4 S <- Id List 4 S <- Id List
5 List <- !. / (',' / %{2}) (Id / %{1}) List 5 List <- !. / Comma Id List
6 Id <- Sp [a-z]+ 6 Id <- Sp [a-z]+ / %{2}
7 Comma <- Sp ',' 7 Comma <- Sp ',' / %{3}
8 Sp <- %s* 8 Sp <- %s*
9]] 9]]
10 10
diff --git a/examples/listIdRe2.lua b/examples/listIdRe2.lua
index fe30535..6bab6ba 100644
--- a/examples/listIdRe2.lua
+++ b/examples/listIdRe2.lua
@@ -1,4 +1,4 @@
1local re = require 'relabel' 1local re = require 'relabelrec'
2 2
3local errinfo = { 3local errinfo = {
4 {"errUndef", "undefined"}, 4 {"errUndef", "undefined"},
@@ -18,23 +18,54 @@ re.setlabels(labels)
18 18
19local g = re.compile[[ 19local g = re.compile[[
20 S <- Id List 20 S <- Id List
21 List <- !. / (',' / %{errComma}) (Id / %{errId}) List 21 List <- !. / Comma Id List
22 Id <- Sp [a-z]+ 22 Id <- Sp {[a-z]+} / %{errId}
23 Comma <- Sp ',' 23 Comma <- Sp ',' / %{errComma}
24 Sp <- %s* 24 Sp <- %s*
25]] 25]]
26 26
27function mymatch (g, s) 27local errors
28 local r, e, sfail = g:match(s) 28
29 if not r then 29function recorderror (subject, pos, label)
30 local line, col = re.calcline(s, #s - #sfail) 30 local line, col = re.calcline(subject, pos)
31 local msg = "Error at line " .. line .. " (col " .. col .. "): " 31 table.insert(errors, { line = line, col = col, msg = errmsgs[labels[label]] })
32 return r, msg .. errmsgs[e] .. " before '" .. sfail .. "'" 32 return true
33 end 33end
34 return r 34
35function sync (p)
36 return '( !(' .. p .. ') .)*'
35end 37end
36 38
37print(mymatch(g, "one,two")) 39local grec = re.compile(
38print(mymatch(g, "one two")) 40 "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" ..
39print(mymatch(g, "one,\n two,\nthree,")) 41 "ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('[a-z]+') .. "\n" ..
42 "ErrId <- ('' -> 'errId' => recorderror) " .. sync('","') .. "-> default"
43 , {g = g, recorderror = recorderror, default = "NONE"})
40 44
45function mymatch (g, s)
46 errors = {}
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)
55 if #errors > 0 then
56 io.write("\n")
57 local out = {}
58 for i, err in ipairs(errors) do
59 local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
60 table.insert(out, msg)
61 end
62 io.write(table.concat(out, "\n"))
63 end
64 print("\n")
65 return r
66end
67
68mymatch(grec, "one,two")
69mymatch(grec, "one two three")
70mymatch(grec, "1,\n two, \n3,")
71mymatch(grec, "one\n two123, \nthree,")
diff --git a/examples/recovery.lua b/examples/recovery.lua
deleted file mode 100644
index 3272ae7..0000000
--- a/examples/recovery.lua
+++ /dev/null
@@ -1,134 +0,0 @@
1local lpeg = require"lpeglabel"
2
3local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V
4local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt
5local T, Lc = lpeg.T, lpeg.Lc
6
7-- The `labels` table contains the list of labels that we will be using
8-- as well as the corresponding error message for each label, which will
9-- be used in our error reporting later on.
10local labels = {
11 {"NoExp", "no expression found"},
12 {"Extra", "extra characters found after the expression"},
13 {"ExpTerm", "expected a term after the operator"},
14 {"ExpExp", "expected an expression after the parenthesis"},
15 {"MisClose", "missing a closing ')' after the expression"},
16}
17
18-- The `labelindex` function gives us the index of a label in the
19-- `labels` table, which serves as the integer representation of the label.
20-- We need this because LPegLabel requires us to use integers for the labels.
21local function labelindex(labname)
22 for i, elem in ipairs(labels) do
23 if elem[1] == labname then
24 return i
25 end
26 end
27 error("could not find label: " .. labname)
28end
29
30-- The `errors` table will hold the list of errors recorded during parsing
31local errors = {}
32
33-- The `expect` function takes a pattern and a label and returns a pattern
34-- that throws the specified label if the original pattern fails to match.
35-- Before throwing the label, it records the label to be thrown along with
36-- the position of the failure (index in input string) into the `errors` table.
37local function expect(patt, labname)
38 local i = labelindex(labname)
39 function recorderror(input, pos)
40 table.insert(errors, {i, pos})
41 return true
42 end
43 return patt + Cmt("", recorderror) * T(i)
44end
45
46local num = R("09")^1 / tonumber
47local op = S("+-*/")
48
49-- The `compute` function takes an alternating list of numbers and
50-- operators and computes the result of applying the operations
51-- to the numbers in a left to right order (no operator precedence).
52local function compute(tokens)
53 local result = tokens[1]
54 for i = 2, #tokens, 2 do
55 if tokens[i] == '+' then
56 result = result + tokens[i+1]
57 elseif tokens[i] == '-' then
58 result = result - tokens[i+1]
59 elseif tokens[i] == '*' then
60 result = result * tokens[i+1]
61 elseif tokens[i] == '/' then
62 result = result / tokens[i+1]
63 else
64 error('unknown operation: ' .. tokens[i])
65 end
66 end
67 return result
68end
69
70-- Our grammar is a simple arithmetic expression of integers that
71-- does not take operator precedence into account but allows grouping
72-- via parenthesis. We have incorporated some error recovery startegies
73-- to our grammar so that it may resume parsing even after encountering
74-- an error, which allows us to report more errors.
75local g = P {
76 "Exp",
77 Exp = Ct(V"Term" * (C(op) * V"OpRecov")^0) / compute;
78 -- `OpRecov` handles missing terms/operands by returning a dummy (zero).
79 OpRecov = Lc(V"Operand", Cc(0), labelindex("ExpTerm"));
80 Operand = expect(V"Term", "ExpTerm");
81 Term = num + V"Group";
82 -- `Group` handles missing closing parenthesis by simply ignoring it.
83 -- Like all the others, the error is still recorded of course.
84 Group = "(" * V"InnerExp" * Lc(expect(")", "MisClose"), P"", labelindex("MisClose"));
85 -- `InnerExp` handles missing expressions by skipping to the next closing
86 -- parenthesis. A dummy (zero) is returned in place of the expression.
87 InnerExp = Lc(expect(V"Exp", "ExpExp"), (P(1) - ")")^0 * Cc(0), labelindex("ExpExp"));
88}
89
90g = expect(g, "NoExp") * expect(-P(1), "Extra")
91
92-- The `eval` function takes an input string to match against the grammar
93-- we've just defined. If the input string matches, then the result of the
94-- computation is returned, otherwise we return the error messages and
95-- positions of all the failures encountered.
96local function eval(input)
97 local result, label, suffix = g:match(input)
98 if #errors == 0 then
99 return result
100 else
101 local out = {}
102 for i, err in ipairs(errors) do
103 local pos = err[2]
104 local msg = labels[err[1]][2]
105 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
106 end
107 errors = {}
108 return nil, table.concat(out, "\n")
109 end
110end
111
112print(eval "98-76*(54/32)")
113--> 37.125
114
115print(eval "(1+1-1*2/2")
116--> syntax error: missing a closing ')' after the expression (at index 11)
117
118print(eval "(1+)-1*(2/2)")
119--> syntax error: expected a term after the operator (at index 4)
120
121print(eval "(1+1)-1*(/2)")
122--> syntax error: expected an expression after the parenthesis (at index 10)
123
124print(eval "1+(1-(1*2))/2x")
125--> syntax error: extra chracters found after the expression (at index 14)
126
127print(eval "-1+(1-(1*2))/2")
128--> syntax error: no expression found (at index 1)
129
130print(eval "(1+1-1*(2/2+)-():")
131--> syntax error: expected a term after the operator (at index 13)
132--> syntax error: expected an expression after the parenthesis (at index 16)
133--> syntax error: missing a closing ')' after the expression (at index 17)
134--> syntax error: extra characters found after the expression (at index 17)
diff --git a/examples/recoveryOpFail.lua b/examples/recoveryOpFail.lua
deleted file mode 100644
index 6ddc6a2..0000000
--- a/examples/recoveryOpFail.lua
+++ /dev/null
@@ -1,105 +0,0 @@
1local lpeg = require"lpeglabel"
2
3local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V
4local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg
5local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec
6
7local labels = {
8 {"NoExp", "no expression found"},
9 {"Extra", "extra characters found after the expression"},
10 {"ExpTerm", "expected a term after the operator"},
11 {"ExpExp", "expected an expression after the parenthesis"},
12 {"MisClose", "missing a closing ')' after the expression"},
13}
14
15local function labelindex(labname)
16 for i, elem in ipairs(labels) do
17 if elem[1] == labname then
18 return i
19 end
20 end
21 error("could not find label: " .. labname)
22end
23
24local function expect(patt, labname, recpatt)
25 local i = labelindex(labname)
26 local function recorderror(input, pos, errors)
27 table.insert(errors, {i, pos})
28 return true
29 end
30 if not recpatt then recpatt = P"" end
31 return Rec(patt, Cmt(Carg(1), recorderror) * recpatt)
32end
33
34local num = R("09")^1 / tonumber
35local op = S("+-*/")
36
37local function compute(tokens)
38 local result = tokens[1]
39 for i = 2, #tokens, 2 do
40 if tokens[i] == '+' then
41 result = result + tokens[i+1]
42 elseif tokens[i] == '-' then
43 result = result - tokens[i+1]
44 elseif tokens[i] == '*' then
45 result = result * tokens[i+1]
46 elseif tokens[i] == '/' then
47 result = result / tokens[i+1]
48 else
49 error('unknown operation: ' .. tokens[i])
50 end
51 end
52 return result
53end
54
55
56local g = P {
57 "Exp",
58 Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute;
59 Operand = expect(V"Term", "ExpTerm", Cc(0));
60 Term = num + V"Group";
61 Group = "(" * V"InnerExp" * expect(")", "MisClose");
62 InnerExp = expect(V"Exp", "ExpExp", (P(1) - ")")^0 * Cc(0));
63}
64
65g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra")
66
67local function eval(input)
68 local errors = {}
69 local result, label, suffix = g:match(input, 1, errors)
70 if #errors == 0 then
71 return result
72 else
73 local out = {}
74 for i, err in ipairs(errors) do
75 local pos = err[2]
76 local msg = labels[err[1]][2]
77 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
78 end
79 return nil, table.concat(out, "\n")
80 end
81end
82
83print(eval "98-76*(54/32)")
84--> 37.125
85
86print(eval "(1+1-1*2/2")
87--> syntax error: missing a closing ')' after the expression (at index 11)
88
89print(eval "(1+)-1*(2/2)")
90--> syntax error: expected a term after the operator (at index 4)
91
92print(eval "(1+1)-1*(/2)")
93--> syntax error: expected an expression after the parenthesis (at index 10)
94
95print(eval "1+(1-(1*2))/2x")
96--> syntax error: extra chracters found after the expression (at index 14)
97
98print(eval "-1+(1-(1*2))/2")
99--> syntax error: no expression found (at index 1)
100
101print(eval "(1+1-1*(2/2+)-():")
102--> syntax error: expected a term after the operator (at index 13)
103--> syntax error: expected an expression after the parenthesis (at index 16)
104--> syntax error: missing a closing ')' after the expression (at index 17)
105--> syntax error: extra characters found after the expression (at index 17)
diff --git a/examples/recoveryOpLab.lua b/examples/recoveryOpLab.lua
deleted file mode 100644
index 6697f8b..0000000
--- a/examples/recoveryOpLab.lua
+++ /dev/null
@@ -1,107 +0,0 @@
1local lpeg = require"lpeglabel"
2
3local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V
4local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt
5local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec
6
7local labels = {
8 {"NoExp", "no expression found"},
9 {"Extra", "extra characters found after the expression"},
10 {"ExpTerm", "expected a term after the operator"},
11 {"ExpExp", "expected an expression after the parenthesis"},
12 {"MisClose", "missing a closing ')' after the expression"},
13}
14
15local function labelindex(labname)
16 for i, elem in ipairs(labels) do
17 if elem[1] == labname then
18 return i
19 end
20 end
21 error("could not find label: " .. labname)
22end
23
24local errors = {}
25
26local function expect(patt, labname)
27 local i = labelindex(labname)
28 function recorderror(input, pos)
29 table.insert(errors, {i, pos})
30 return true
31 end
32 return patt + Cmt("", recorderror) * T(i)
33end
34
35local num = R("09")^1 / tonumber
36local op = S("+-*/")
37
38local function compute(tokens)
39 local result = tokens[1]
40 for i = 2, #tokens, 2 do
41 if tokens[i] == '+' then
42 result = result + tokens[i+1]
43 elseif tokens[i] == '-' then
44 result = result - tokens[i+1]
45 elseif tokens[i] == '*' then
46 result = result * tokens[i+1]
47 elseif tokens[i] == '/' then
48 result = result / tokens[i+1]
49 else
50 error('unknown operation: ' .. tokens[i])
51 end
52 end
53 return result
54end
55
56
57local g = P {
58 "Exp",
59 Exp = Ct(V"Term" * (C(op) * V"OpRecov")^0) / compute;
60 OpRecov = Rec(V"Operand", Cc(0), labelindex("ExpTerm"));
61 Operand = expect(V"Term", "ExpTerm");
62 Term = num + Rec(V"Group", P"", labelindex("MisClose"));
63 Group = "(" * Rec(V"InnerExp", (P(1) - ")")^0 * Cc(0), labelindex("ExpExp")) * expect(")", "MisClose");
64 InnerExp = expect(V"Exp", "ExpExp");
65}
66
67g = expect(g, "NoExp") * expect(-P(1), "Extra")
68
69local function eval(input)
70 local result, label, suffix = g:match(input)
71 if #errors == 0 then
72 return result
73 else
74 local out = {}
75 for i, err in ipairs(errors) do
76 local pos = err[2]
77 local msg = labels[err[1]][2]
78 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
79 end
80 errors = {}
81 return nil, table.concat(out, "\n")
82 end
83end
84
85print(eval "98-76*(54/32)")
86--> 37.125
87
88print(eval "(1+1-1*2/2")
89--> syntax error: missing a closing ')' after the expression (at index 11)
90
91print(eval "(1+)-1*(2/2)")
92--> syntax error: expected a term after the operator (at index 4)
93
94print(eval "(1+1)-1*(/2)")
95--> syntax error: expected an expression after the parenthesis (at index 10)
96
97print(eval "1+(1-(1*2))/2x")
98--> syntax error: extra chracters found after the expression (at index 14)
99
100print(eval "-1+(1-(1*2))/2")
101--> syntax error: no expression found (at index 1)
102
103print(eval "(1+1-1*(2/2+)-():")
104--> syntax error: expected a term after the operator (at index 13)
105--> syntax error: expected an expression after the parenthesis (at index 16)
106--> syntax error: missing a closing ')' after the expression (at index 17)
107--> syntax error: extra characters found after the expression (at index 17)
diff --git a/examples/tiny.lua b/examples/tiny.lua
index 99c3144..784e031 100644
--- a/examples/tiny.lua
+++ b/examples/tiny.lua
@@ -1,4 +1,4 @@
1local re = require 'relabel' 1local re = require 'relabelrec'
2 2
3local terror = {} 3local terror = {}
4 4
@@ -25,21 +25,6 @@ newError("errFactor", "Error: expected '(', ID, or number after '*' or '/'")
25newError("errExpFac", "Error: expected expression after '('") 25newError("errExpFac", "Error: expected expression after '('")
26newError("errClosePar", "Error: expected ')' after expression") 26newError("errClosePar", "Error: expected ')' after expression")
27 27
28local line
29
30local function incLine()
31 line = line + 1
32 return true
33end
34
35local function countLine(s, i)
36 line = 1
37 local p = re.compile([[
38 S <- (%nl -> incLine / .)*
39 ]], { incLine = incLine})
40 p:match(s:sub(1, i))
41 return true
42end
43 28
44local labelCode = {} 29local labelCode = {}
45for k, v in ipairs(terror) do 30for k, v in ipairs(terror) do
@@ -48,7 +33,7 @@ end
48 33
49re.setlabels(labelCode) 34re.setlabels(labelCode)
50 35
51local g = re.compile([[ 36local g = re.compile[[
52 Tiny <- CmdSeq 37 Tiny <- CmdSeq
53 CmdSeq <- (Cmd (SEMICOLON / ErrSemi)) (Cmd (SEMICOLON / ErrSemi))* 38 CmdSeq <- (Cmd (SEMICOLON / ErrSemi)) (Cmd (SEMICOLON / ErrSemi))*
54 Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd 39 Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd
@@ -61,25 +46,24 @@ local g = re.compile([[
61 SimpleExp <- Term ((ADD / SUB) (Term / ErrTerm))* 46 SimpleExp <- Term ((ADD / SUB) (Term / ErrTerm))*
62 Term <- Factor ((MUL / DIV) (Factor / ErrFactor))* 47 Term <- Factor ((MUL / DIV) (Factor / ErrFactor))*
63 Factor <- OPENPAR (Exp / ErrExpFac) (CLOSEPAR / ErrClosePar) / NUMBER / NAME 48 Factor <- OPENPAR (Exp / ErrExpFac) (CLOSEPAR / ErrClosePar) / NUMBER / NAME
64 ErrSemi <- ErrCount %{errSemi} 49 ErrSemi <- %{errSemi}
65 ErrExpIf <- ErrCount %{errExpIf} 50 ErrExpIf <- %{errExpIf}
66 ErrThen <- ErrCount %{errThen} 51 ErrThen <- %{errThen}
67 ErrCmdSeq1 <- ErrCount %{errCmdSeq1} 52 ErrCmdSeq1 <- %{errCmdSeq1}
68 ErrCmdSeq2 <- ErrCount %{errCmdSeq2} 53 ErrCmdSeq2 <- %{errCmdSeq2}
69 ErrEnd <- ErrCount %{errEnd} 54 ErrEnd <- %{errEnd}
70 ErrCmdSeqRep <- ErrCount %{errCmdSeqRep} 55 ErrCmdSeqRep <- %{errCmdSeqRep}
71 ErrUntil <- ErrCount %{errUntil} 56 ErrUntil <- %{errUntil}
72 ErrExpRep <- ErrCount %{errExpRep} 57 ErrExpRep <- %{errExpRep}
73 ErrAssignOp <- ErrCount %{errAssignOp} 58 ErrAssignOp <- %{errAssignOp}
74 ErrExpAssign <- ErrCount %{errExpAssign} 59 ErrExpAssign <- %{errExpAssign}
75 ErrReadName <- ErrCount %{errReadName} 60 ErrReadName <- %{errReadName}
76 ErrWriteExp <- ErrCount %{errWriteExp} 61 ErrWriteExp <- %{errWriteExp}
77 ErrSimpExp <- ErrCount %{errSimpExp} 62 ErrSimpExp <- %{errSimpExp}
78 ErrTerm <- ErrCount %{errTerm} 63 ErrTerm <- %{errTerm}
79 ErrFactor <- ErrCount %{errFactor} 64 ErrFactor <- %{errFactor}
80 ErrExpFac <- ErrCount %{errExpFac} 65 ErrExpFac <- %{errExpFac}
81 ErrClosePar <- ErrCount %{errClosePar} 66 ErrClosePar <- %{errClosePar}
82 ErrCount <- '' => countLine
83 ADD <- Sp '+' 67 ADD <- Sp '+'
84 ASSIGNMENT <- Sp ':=' 68 ASSIGNMENT <- Sp ':='
85 CLOSEPAR <- Sp ')' 69 CLOSEPAR <- Sp ')'
@@ -102,12 +86,17 @@ local g = re.compile([[
102 WRITE <- Sp 'write' 86 WRITE <- Sp 'write'
103 RESERVED <- (IF / ELSE / END / READ / REPEAT / THEN / UNTIL / WRITE) ![a-z]+ 87 RESERVED <- (IF / ELSE / END / READ / REPEAT / THEN / UNTIL / WRITE) ![a-z]+
104 Sp <- %s* 88 Sp <- %s*
105]], { countLine = countLine }) 89]]
106 90
107 91
108local function printError(n, e) 92local function mymatch(g, s)
109 assert(n == nil) 93 local r, e, sfail = g:match(s)
110 print("Line " .. line .. ": " .. terror[e].msg) 94 if not r then
95 local line, col = re.calcline(s, #s - #sfail)
96 local msg = "Error at line " .. line .. " (col " .. col .. "): "
97 return r, msg .. terror[e].msg
98 end
99 return r
111end 100end
112 101
113local s = [[ 102local s = [[
@@ -118,7 +107,7 @@ repeat
118 n := n - 1 107 n := n - 1
119until (n < 1); 108until (n < 1);
120write f;]] 109write f;]]
121printError(g:match(s)) 110print(mymatch(g, s))
122 111
123s = [[ 112s = [[
124n := 5; 113n := 5;
@@ -128,14 +117,14 @@ repeat
128 n := n - 1; 117 n := n - 1;
129until (n < 1); 118until (n < 1);
130read ;]] 119read ;]]
131printError(g:match(s)) 120print(mymatch(g, s))
132 121
133s = [[ 122s = [[
134if a < 1 then 123if a < 1 then
135 b := 2; 124 b := 2;
136else 125else
137 b := 3;]] 126 b := 3;]]
138printError(g:match(s)) 127print(mymatch(g, s))
139 128
140s = [[ 129s = [[
141n := 5; 130n := 5;
@@ -145,7 +134,7 @@ repeat
145 n := n - 1; 134 n := n - 1;
146untill (n < 1); 135untill (n < 1);
147]] 136]]
148printError(g:match(s)) 137print(mymatch(g, s))
149 138
150s = [[ 139s = [[
151n := 5; 140n := 5;
@@ -155,9 +144,8 @@ repeat
155 n := n - 1; 144 n := n - 1;
1563 (n < 1); 1453 (n < 1);
157]] 146]]
158printError(g:match(s)) 147print(mymatch(g, s))
159
160printError(g:match("a : 2"))
161printError(g:match("a := (2"))
162 148
149print(mymatch(g, "a : 2"))
150print(mymatch(g, "a := (2"))
163 151
diff --git a/examples/typedlua/test.lua b/examples/typedlua/test.lua
index ed4e7a1..95474ba 100755
--- a/examples/typedlua/test.lua
+++ b/examples/typedlua/test.lua
@@ -401,15 +401,18 @@ assert(m == e)
401-- unfinished comments 401-- unfinished comments
402 402
403s = [=[ 403s = [=[
404--[[ testing 404--[[
405
406testing
405unfinished 407unfinished
408
406comment 409comment
407]=] 410 ]=]
408--[=[ 411--[=[
409test.lua:3:1: syntax error, unexpected 'comment', expecting '=', ',', 'String', '{', '(', ':', '[', '.' 412test.lua:3:1: syntax error, unexpected 'comment', expecting '=', ',', 'String', '{', '(', ':', '[', '.'
410]=] 413]=]
411e = [=[ 414e = [=[
412test.lua:1:1: unfinished long comment 415test.lua:1:2: unfinished long comment
413]=] 416]=]
414 417
415r, m = parse(s) 418r, m = parse(s)
diff --git a/examples/typedlua/tllexer.lua b/examples/typedlua/tllexer.lua
index 6517ba5..d6033ec 100644
--- a/examples/typedlua/tllexer.lua
+++ b/examples/typedlua/tllexer.lua
@@ -1,6 +1,6 @@
1local tllexer = {} 1local tllexer = {}
2 2
3local lpeg = require "lpeglabel" 3local lpeg = require "lpeglabelrec"
4lpeg.locale(lpeg) 4lpeg.locale(lpeg)
5 5
6local tlerror = require "tlerror" 6local tlerror = require "tlerror"
@@ -9,10 +9,6 @@ function tllexer.try (pat, label)
9 return pat + lpeg.T(tlerror.labels[label]) 9 return pat + lpeg.T(tlerror.labels[label])
10end 10end
11 11
12function tllexer.catch (pat, label)
13 return lpeg.Lc(pat, lpeg.P(false), tlerror.labels[label])
14end
15
16local function setffp (s, i, t, n) 12local function setffp (s, i, t, n)
17 if not t.ffp or i > t.ffp then 13 if not t.ffp or i > t.ffp then
18 t.ffp = i 14 t.ffp = i
@@ -45,7 +41,10 @@ local CloseEQ = lpeg.Cmt(Close * lpeg.Cb("init"),
45local LongString = Open * (lpeg.P(1) - CloseEQ)^0 * tllexer.try(Close, "LongString") / 41local LongString = Open * (lpeg.P(1) - CloseEQ)^0 * tllexer.try(Close, "LongString") /
46 function (s, o) return s end 42 function (s, o) return s end
47 43
48local Comment = lpeg.Lc(lpeg.P("--") * LongString / function () return end, 44local LongStringCm1 = Open * (lpeg.P(1) - CloseEQ)^0 * Close /
45 function (s, o) return s end
46
47local Comment = lpeg.Rec(lpeg.P"--" * #Open * (LongStringCm1 / function() return end + lpeg.T(tlerror.labels["LongString"])),
49 lpeg.T(tlerror.labels["LongComment"]), tlerror.labels["LongString"]) + 48 lpeg.T(tlerror.labels["LongComment"]), tlerror.labels["LongString"]) +
50 lpeg.P("--") * (lpeg.P(1) - lpeg.P("\n"))^0 49 lpeg.P("--") * (lpeg.P(1) - lpeg.P("\n"))^0
51 50
diff --git a/examples/typedlua/tlparser.lua b/examples/typedlua/tlparser.lua
index a301fa6..fe4fd5e 100644
--- a/examples/typedlua/tlparser.lua
+++ b/examples/typedlua/tlparser.lua
@@ -1,6 +1,6 @@
1local tlparser = {} 1local tlparser = {}
2 2
3local lpeg = require "lpeglabel" 3local lpeg = require "lpeglabelrec"
4lpeg.locale(lpeg) 4lpeg.locale(lpeg)
5 5
6local tllexer = require "tllexer" 6local tllexer = require "tllexer"
diff --git a/lpcode.c b/lpcode.c
index 2987cfc..b2dbba2 100644
--- a/lpcode.c
+++ b/lpcode.c
@@ -196,7 +196,7 @@ int checkaux (TTree *tree, int pred) {
196 if (checkaux(sib2(tree), pred)) return 1; 196 if (checkaux(sib2(tree), pred)) return 1;
197 /* else return checkaux(sib1(tree), pred); */ 197 /* else return checkaux(sib1(tree), pred); */
198 tree = sib1(tree); goto tailcall; 198 tree = sib1(tree); goto tailcall;
199 case TLabChoice: case TRecov: /* labeled failure */ 199 case TRecov: /* labeled failure */
200 /* we do not know whether sib2 will be evaluated */ 200 /* we do not know whether sib2 will be evaluated */
201 tree = sib1(tree); goto tailcall; 201 tree = sib1(tree); goto tailcall;
202 case TCapture: case TGrammar: case TRule: 202 case TCapture: case TGrammar: case TRule:
@@ -218,10 +218,10 @@ int fixedlenx (TTree *tree, int count, int len) {
218 switch (tree->tag) { 218 switch (tree->tag) {
219 case TChar: case TSet: case TAny: 219 case TChar: case TSet: case TAny:
220 return len + 1; 220 return len + 1;
221 case TFalse: case TTrue: case TNot: case TAnd: case TBehind: 221 case TFalse: case TTrue: case TNot: case TAnd: case TBehind:
222 case TThrow: /* labeled failure */
223 return len; 222 return len;
224 case TRep: case TRunTime: case TOpenCall: 223 case TRep: case TRunTime: case TOpenCall:
224 case TThrow: /* labeled failure */
225 return -1; 225 return -1;
226 case TCapture: case TRule: case TGrammar: 226 case TCapture: case TRule: case TGrammar:
227 /* return fixedlenx(sib1(tree), count); */ 227 /* return fixedlenx(sib1(tree), count); */
@@ -237,7 +237,7 @@ int fixedlenx (TTree *tree, int count, int len) {
237 /* else return fixedlenx(sib2(tree), count, len); */ 237 /* else return fixedlenx(sib2(tree), count, len); */
238 tree = sib2(tree); goto tailcall; 238 tree = sib2(tree); goto tailcall;
239 } 239 }
240 case TChoice: case TLabChoice: { /* labeled failure */ 240 case TChoice: {
241 int n1, n2; 241 int n1, n2;
242 n1 = fixedlenx(sib1(tree), count, len); 242 n1 = fixedlenx(sib1(tree), count, len);
243 if (n1 < 0) return -1; 243 if (n1 < 0) return -1;
@@ -287,7 +287,7 @@ static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) {
287 loopset(i, firstset->cs[i] = follow->cs[i]); /* follow = fullset(?) */ 287 loopset(i, firstset->cs[i] = follow->cs[i]); /* follow = fullset(?) */
288 return 1; 288 return 1;
289 } 289 }
290 case TChoice: case TLabChoice: { /*(?) labeled failure */ 290 case TChoice: {
291 Charset csaux; 291 Charset csaux;
292 int e1 = getfirst(sib1(tree), follow, firstset); 292 int e1 = getfirst(sib1(tree), follow, firstset);
293 int e2 = getfirst(sib2(tree), follow, &csaux); 293 int e2 = getfirst(sib2(tree), follow, &csaux);
@@ -378,7 +378,7 @@ static int headfail (TTree *tree) {
378 if (!nofail(sib2(tree))) return 0; 378 if (!nofail(sib2(tree))) return 0;
379 /* else return headfail(sib1(tree)); */ 379 /* else return headfail(sib1(tree)); */
380 tree = sib1(tree); goto tailcall; 380 tree = sib1(tree); goto tailcall;
381 case TChoice: case TLabChoice: case TRecov: /* labeled failure */ 381 case TChoice: case TRecov: /* labeled failure */
382 if (!headfail(sib1(tree))) return 0; 382 if (!headfail(sib1(tree))) return 0;
383 /* else return headfail(sib2(tree)); */ 383 /* else return headfail(sib2(tree)); */
384 tree = sib2(tree); goto tailcall; 384 tree = sib2(tree); goto tailcall;
@@ -398,7 +398,7 @@ static int needfollow (TTree *tree) {
398 case TChar: case TSet: case TAny: 398 case TChar: case TSet: case TAny:
399 case TFalse: case TTrue: case TAnd: case TNot: 399 case TFalse: case TTrue: case TAnd: case TNot:
400 case TRunTime: case TGrammar: case TCall: case TBehind: 400 case TRunTime: case TGrammar: case TCall: case TBehind:
401 case TThrow: case TLabChoice: case TRecov: /* (?)labeled failure */ 401 case TThrow: case TRecov: /* (?)labeled failure */
402 return 0; 402 return 0;
403 case TChoice: case TRep: 403 case TChoice: case TRep:
404 return 1; 404 return 1;
@@ -433,7 +433,7 @@ int sizei (const Instruction *i) {
433 return 2; 433 return 2;
434 case IThrow: /* labeled failure */ 434 case IThrow: /* labeled failure */
435 return 1; 435 return 1;
436 case ILabChoice: case IRecov: 436 case IRecov:
437 return (CHARSETINSTSIZE - 1) + 2; /* labeled failure */ 437 return (CHARSETINSTSIZE - 1) + 2; /* labeled failure */
438 default: return 1; 438 default: return 1;
439 } 439 }
@@ -499,7 +499,7 @@ static int addoffsetinst (CompileState *compst, Opcode op) {
499 int i = addinstruction(compst, op, 0); /* instruction */ 499 int i = addinstruction(compst, op, 0); /* instruction */
500 addinstruction(compst, (Opcode)0, 0); /* open space for offset */ 500 addinstruction(compst, (Opcode)0, 0); /* open space for offset */
501 assert(op == ITestSet || sizei(&getinstr(compst, i)) == 2 || 501 assert(op == ITestSet || sizei(&getinstr(compst, i)) == 2 ||
502 op == IRecov || op == ILabChoice); /* labeled failure */ 502 op == IRecov); /* labeled failure */
503 return i; 503 return i;
504} 504}
505 505
@@ -707,21 +707,6 @@ static void codechoice (CompileState *compst, TTree *p1, TTree *p2, int opt,
707 707
708 708
709/* labeled failure begin */ 709/* labeled failure begin */
710static void codelabchoice (CompileState *compst, TTree *p1, TTree *p2, int opt,
711 const Charset *fl, const byte *cs) {
712 int emptyp2 = (p2->tag == TTrue);
713 int pcommit;
714 int test = NOINST;
715 int pchoice = addoffsetinst(compst, ILabChoice);
716 addcharset(compst, cs);
717 codegen(compst, p1, emptyp2, test, fullset);
718 pcommit = addoffsetinst(compst, ICommit);
719 jumptohere(compst, pchoice);
720 jumptohere(compst, test);
721 codegen(compst, p2, opt, NOINST, fl);
722 jumptohere(compst, pcommit);
723}
724
725static void coderecovery (CompileState *compst, TTree *p1, TTree *p2, int opt, 710static void coderecovery (CompileState *compst, TTree *p1, TTree *p2, int opt,
726 const Charset *fl, const byte *cs) { 711 const Charset *fl, const byte *cs) {
727 int emptyp2 = (p2->tag == TTrue); 712 int emptyp2 = (p2->tag == TTrue);
@@ -734,6 +719,7 @@ static void coderecovery (CompileState *compst, TTree *p1, TTree *p2, int opt,
734 jumptohere(compst, precovery); 719 jumptohere(compst, precovery);
735 jumptohere(compst, test); 720 jumptohere(compst, test);
736 codegen(compst, p2, opt, NOINST, fl); 721 codegen(compst, p2, opt, NOINST, fl);
722 addinstruction(compst, IRet, 0);
737 jumptohere(compst, pcommit); 723 jumptohere(compst, pcommit);
738} 724}
739/* labeled failure end */ 725/* labeled failure end */
@@ -969,10 +955,6 @@ static void codegen (CompileState *compst, TTree *tree, int opt, int tt,
969 addinstruction(compst, IThrow, (byte) tree->u.label); 955 addinstruction(compst, IThrow, (byte) tree->u.label);
970 break; 956 break;
971 } 957 }
972 case TLabChoice: { /* labeled failure */
973 codelabchoice(compst, sib1(tree), sib2(tree), opt, fl, treelabelset(tree));
974 break;
975 }
976 case TRecov: { /* labeled failure */ 958 case TRecov: { /* labeled failure */
977 coderecovery(compst, sib1(tree), sib2(tree), opt, fl, treelabelset(tree)); 959 coderecovery(compst, sib1(tree), sib2(tree), opt, fl, treelabelset(tree));
978 break; 960 break;
@@ -999,7 +981,7 @@ static void peephole (CompileState *compst) {
999 switch (code[i].i.code) { 981 switch (code[i].i.code) {
1000 case IChoice: case ICall: case ICommit: case IPartialCommit: 982 case IChoice: case ICall: case ICommit: case IPartialCommit:
1001 case IBackCommit: case ITestChar: case ITestSet: 983 case IBackCommit: case ITestChar: case ITestSet:
1002 case ILabChoice: case IRecov: /* labeled failure */ 984 case IRecov: /* labeled failure */
1003 case ITestAny: { /* instructions with labels */ 985 case ITestAny: { /* instructions with labels */
1004 jumptothere(compst, i, finallabel(code, i)); /* optimize label */ 986 jumptothere(compst, i, finallabel(code, i)); /* optimize label */
1005 break; 987 break;
diff --git a/lpprint.c b/lpprint.c
index 122d2e5..8c488ea 100644
--- a/lpprint.c
+++ b/lpprint.c
@@ -61,7 +61,7 @@ void printinst (const Instruction *op, const Instruction *p) {
61 "choice", "jmp", "call", "open_call", 61 "choice", "jmp", "call", "open_call",
62 "commit", "partial_commit", "back_commit", "failtwice", "fail", "giveup", 62 "commit", "partial_commit", "back_commit", "failtwice", "fail", "giveup",
63 "fullcapture", "opencapture", "closecapture", "closeruntime", 63 "fullcapture", "opencapture", "closecapture", "closeruntime",
64 "throw", "labeled_choice", "recovery" /* labeled failure */ 64 "throw", "recovery" /* labeled failure */
65 }; 65 };
66 printf("%02ld: %s ", (long)(p - op), names[p->i.code]); 66 printf("%02ld: %s ", (long)(p - op), names[p->i.code]);
67 switch ((Opcode)p->i.code) { 67 switch ((Opcode)p->i.code) {
@@ -112,7 +112,7 @@ void printinst (const Instruction *op, const Instruction *p) {
112 printf("%d", p->i.aux); 112 printf("%d", p->i.aux);
113 break; 113 break;
114 } 114 }
115 case ILabChoice: case IRecov: { /* labeled failure */ 115 case IRecov: { /* labeled failure */
116 printjmp(op, p); 116 printjmp(op, p);
117 printcharset((p+2)->buff); 117 printcharset((p+2)->buff);
118 break; 118 break;
@@ -223,7 +223,7 @@ void printtree (TTree *tree, int ident) {
223 default: { 223 default: {
224 int sibs = numsiblings[tree->tag]; 224 int sibs = numsiblings[tree->tag];
225 printf("\n"); 225 printf("\n");
226 if (tree->tag == TLabChoice || tree->tag == TRecov) { /* labeled failure */ 226 if (tree->tag == TRecov) { /* labeled failure */
227 printcharset(treelabelset(tree)); 227 printcharset(treelabelset(tree));
228 printf("\n"); 228 printf("\n");
229 } 229 }
diff --git a/lptree.c b/lptree.c
index 2044a5c..e15198e 100644
--- a/lptree.c
+++ b/lptree.c
@@ -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
@@ -723,37 +723,25 @@ static int lp_behind (lua_State *L) {
723*/ 723*/
724static int lp_throw (lua_State *L) { 724static int lp_throw (lua_State *L) {
725 int label = luaL_checkinteger(L, -1); 725 int label = luaL_checkinteger(L, -1);
726 luaL_argcheck(L, label >= 0 && label < MAXLABELS, -1, "the number of a label must be between 0 and 255"); 726 luaL_argcheck(L, label >= 1 && label < MAXLABELS, -1, "the number of a label must be between 1 and 255");
727 newthrowleaf(L, label); 727 newthrowleaf(L, label);
728 return 1; 728 return 1;
729} 729}
730 730
731/* 731/*
732** labeled choice function 732** labeled recovery function
733*/ 733*/
734static int lp_labchoice (lua_State *L) {
735 int n = lua_gettop(L);
736 TTree *tree = newrootlab2sib(L, TLabChoice);
737 int i;
738 for (i = 3; i <= n; i++) {
739 int d = luaL_checkinteger(L, i);
740 luaL_argcheck(L, d >= 0 && d < MAXLABELS, i, "the number of a label must be between 0 and 255");
741 setlabel(treelabelset(tree), (byte)d);
742 }
743 return 1;
744}
745
746
747static int lp_recovery (lua_State *L) { 734static int lp_recovery (lua_State *L) {
748 int n = lua_gettop(L); 735 int n = lua_gettop(L);
749 TTree *tree = newrootlab2sib(L, TRecov); 736 TTree *tree = newrootlab2sib(L, TRecov);
737 luaL_argcheck(L, n >= 3, 3, "non-nil value expected");
750 if (n == 2) { /* catches fail as default */ 738 if (n == 2) { /* catches fail as default */
751 setlabel(treelabelset(tree), LFAIL); 739 /*setlabel(treelabelset(tree), LFAIL); recovery does not catch regular fail */
752 } else { 740 } else {
753 int i; 741 int i;
754 for (i = 3; i <= n; i++) { 742 for (i = 3; i <= n; i++) {
755 int d = luaL_checkinteger(L, i); 743 int d = luaL_checkinteger(L, i);
756 luaL_argcheck(L, d >= 0 && d < MAXLABELS, i, "the number of a label must be between 0 and 255"); 744 luaL_argcheck(L, d >= 1 && d < MAXLABELS, i, "the number of a label must be between 1 and 255");
757 setlabel(treelabelset(tree), (byte)d); 745 setlabel(treelabelset(tree), (byte)d);
758 } 746 }
759 } 747 }
@@ -1089,7 +1077,7 @@ static int verifyrule (lua_State *L, TTree *tree, int *passed, int npassed,
1089 return nb; 1077 return nb;
1090 /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */ 1078 /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */
1091 tree = sib2(tree); goto tailcall; 1079 tree = sib2(tree); goto tailcall;
1092 case TChoice: case TLabChoice: case TRecov: /* must check both children */ /* labeled failure */ 1080 case TChoice: case TRecov: /* must check both children */ /* labeled failure */
1093 nb = verifyrule(L, sib1(tree), passed, npassed, nb); 1081 nb = verifyrule(L, sib1(tree), passed, npassed, nb);
1094 /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ 1082 /* return verifyrule(L, sib2(tree), passed, npassed, nb); */
1095 tree = sib2(tree); goto tailcall; 1083 tree = sib2(tree); goto tailcall;
@@ -1342,7 +1330,6 @@ static struct luaL_Reg pattreg[] = {
1342 {"setmaxstack", lp_setmax}, 1330 {"setmaxstack", lp_setmax},
1343 {"type", lp_type}, 1331 {"type", lp_type},
1344 {"T", lp_throw}, /* labeled failure throw */ 1332 {"T", lp_throw}, /* labeled failure throw */
1345 {"Lc", lp_labchoice}, /* labeled failure choice */
1346 {"Rec", lp_recovery}, /* labeled failure choice */ 1333 {"Rec", lp_recovery}, /* labeled failure choice */
1347 {NULL, NULL} 1334 {NULL, NULL}
1348}; 1335};
@@ -1361,8 +1348,8 @@ static struct luaL_Reg metareg[] = {
1361}; 1348};
1362 1349
1363 1350
1364int luaopen_lpeglabel (lua_State *L); /* labeled failure */ 1351int luaopen_lpeglabelrec (lua_State *L); /* labeled failure */
1365int luaopen_lpeglabel (lua_State *L) { /* labeled failure */ 1352int luaopen_lpeglabelrec (lua_State *L) { /* labeled failure */
1366 luaL_newmetatable(L, PATTERN_T); 1353 luaL_newmetatable(L, PATTERN_T);
1367 lua_pushnumber(L, MAXBACK); /* initialize maximum backtracking */ 1354 lua_pushnumber(L, MAXBACK); /* initialize maximum backtracking */
1368 lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); 1355 lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX);
diff --git a/lptree.h b/lptree.h
index cca346e..b75f323 100644
--- a/lptree.h
+++ b/lptree.h
@@ -25,7 +25,7 @@ typedef enum TTag {
25 TBehind, /* match behind */ 25 TBehind, /* match behind */
26 TCapture, /* regular capture */ 26 TCapture, /* regular capture */
27 TRunTime, /* run-time capture */ 27 TRunTime, /* run-time capture */
28 TThrow, TLabChoice, TRecov /* labeled failure */ 28 TThrow, TRecov /* labeled failure */
29} TTag; 29} TTag;
30 30
31/* number of siblings for each tree */ 31/* number of siblings for each tree */
diff --git a/lpvm.c b/lpvm.c
index 277acf7..a934478 100644
--- a/lpvm.c
+++ b/lpvm.c
@@ -162,15 +162,16 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
162 int captop = 0; /* point to first empty slot in captures */ 162 int captop = 0; /* point to first empty slot in captures */
163 int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ 163 int ndyncap = 0; /* number of dynamic captures (in Lua stack) */
164 const Instruction *p = op; /* current instruction */ 164 const Instruction *p = op; /* current instruction */
165 const Instruction *pk = NULL; /* resume instruction */
165 Labelset lsfail; 166 Labelset lsfail;
166 setlabelfail(&lsfail); 167 setlabelfail(&lsfail);
167 stack->p = &giveup; stack->s = s; stack->ls = &lsfail; stack->caplevel = 0; stack++; /* labeled failure */ 168 stack->p = &giveup; stack->s = s; stack->ls = &lsfail; stack->caplevel = 0; stack++; /* labeled failure */
168 lua_pushlightuserdata(L, stackbase); 169 lua_pushlightuserdata(L, stackbase);
169 for (;;) { 170 for (;;) {
170#if defined(DEBUG) 171#if defined(DEBUG)
172 printinst(op, p);
171 printf("s: |%s| stck:%d, dyncaps:%d, caps:%d ", 173 printf("s: |%s| stck:%d, dyncaps:%d, caps:%d ",
172 s, stack - getstackbase(L, ptop), ndyncap, captop); 174 s, stack - getstackbase(L, ptop), ndyncap, captop);
173 printinst(op, p);
174 printcaplist(capture, capture + captop); 175 printcaplist(capture, capture + captop);
175#endif 176#endif
176 assert(stackidx(ptop) + ndyncap == lua_gettop(L) && ndyncap <= captop); 177 assert(stackidx(ptop) + ndyncap == lua_gettop(L) && ndyncap <= captop);
@@ -194,6 +195,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
194 if (s < e) { p++; s++; } 195 if (s < e) { p++; s++; }
195 else { 196 else {
196 *labelf = LFAIL; /* labeled failure */ 197 *labelf = LFAIL; /* labeled failure */
198 pk = p + 1;
197 *sfail = s; 199 *sfail = s;
198 goto fail; 200 goto fail;
199 } 201 }
@@ -208,6 +210,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
208 if ((byte)*s == p->i.aux && s < e) { p++; s++; } 210 if ((byte)*s == p->i.aux && s < e) { p++; s++; }
209 else { 211 else {
210 *labelf = LFAIL; /* labeled failure */ 212 *labelf = LFAIL; /* labeled failure */
213 pk = p + 1;
211 *sfail = s; 214 *sfail = s;
212 goto fail; 215 goto fail;
213 } 216 }
@@ -224,6 +227,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
224 { p += CHARSETINSTSIZE; s++; } 227 { p += CHARSETINSTSIZE; s++; }
225 else { 228 else {
226 *labelf = LFAIL; /* labeled failure */ 229 *labelf = LFAIL; /* labeled failure */
230 pk = p + CHARSETINSTSIZE;
227 *sfail = s; 231 *sfail = s;
228 goto fail; 232 goto fail;
229 } 233 }
@@ -240,6 +244,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
240 int n = p->i.aux; 244 int n = p->i.aux;
241 if (n > s - o) { 245 if (n > s - o) {
242 *labelf = LFAIL; /* labeled failure */ 246 *labelf = LFAIL; /* labeled failure */
247 pk = p + 1;
243 *sfail = s; 248 *sfail = s;
244 goto fail; 249 goto fail;
245 } 250 }
@@ -269,17 +274,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
269 p += 2; 274 p += 2;
270 continue; 275 continue;
271 } 276 }
272 case ILabChoice: { /* labeled failure */
273 if (stack == stacklimit)
274 stack = doublestack(L, &stacklimit, ptop);
275 stack->p = p + getoffset(p);
276 stack->s = s;
277 stack->ls = (const Labelset *) ((p + 2)->buff);
278 stack->caplevel = captop;
279 stack++;
280 p += (CHARSETINSTSIZE - 1) + 2;
281 continue;
282 }
283 case IRecov: { /* labeled failure */ 277 case IRecov: { /* labeled failure */
284 if (stack == stacklimit) 278 if (stack == stacklimit)
285 stack = doublestack(L, &stacklimit, ptop); 279 stack = doublestack(L, &stacklimit, ptop);
@@ -291,7 +285,6 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
291 p += (CHARSETINSTSIZE - 1) + 2; 285 p += (CHARSETINSTSIZE - 1) + 2;
292 continue; 286 continue;
293 } 287 }
294
295 case ICall: { 288 case ICall: {
296 if (stack == stacklimit) 289 if (stack == stacklimit)
297 stack = doublestack(L, &stacklimit, ptop); 290 stack = doublestack(L, &stacklimit, ptop);
@@ -304,7 +297,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
304 } 297 }
305 case ICommit: { 298 case ICommit: {
306 assert(stack > getstackbase(L, ptop) && (stack - 1)->ls != NULL); /* labeled failure */ 299 assert(stack > getstackbase(L, ptop) && (stack - 1)->ls != NULL); /* labeled failure */
307 /* assert((stack - 1)->s != NULL); labeled failure: IRecov does not push s onto the stack */ 300 /*assert((stack - 1)->s != NULL); labeled failure: IRecov does not push s onto the stack */
308 stack--; 301 stack--;
309 p += getoffset(p); 302 p += getoffset(p);
310 continue; 303 continue;
@@ -325,6 +318,7 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
325 } 318 }
326 case IThrow: { /* labeled failure */ 319 case IThrow: { /* labeled failure */
327 *labelf = p->i.aux; 320 *labelf = p->i.aux;
321 pk = p + 1;
328 *sfail = s; 322 *sfail = s;
329 goto fail; 323 goto fail;
330 } 324 }
@@ -334,20 +328,31 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
334 /* go through */ 328 /* go through */
335 case IFail: 329 case IFail:
336 *labelf = LFAIL; /* labeled failure */ 330 *labelf = LFAIL; /* labeled failure */
331 pk = NULL;
337 *sfail = s; 332 *sfail = s;
338 fail: { /* pattern failed: try to backtrack */ 333 fail: { /* pattern failed: try to backtrack */
339 const Labelset *auxlab = NULL; 334 const Labelset *auxlab = NULL;
335 Stack *pstack = stack;
340 do { /* remove pending calls */ 336 do { /* remove pending calls */
341 assert(stack > getstackbase(L, ptop)); 337 assert(pstack > getstackbase(L, ptop));
342 auxlab = (--stack)->ls; 338 auxlab = (--pstack)->ls;
343 } while (auxlab == NULL || (stack->p != &giveup && !testlabel(stack->ls->cs, *labelf))); 339 } while (auxlab == NULL || !(pstack->p == &giveup || testlabel(pstack->ls->cs, *labelf)));
344 if (stack->p == &giveup || stack->s != NULL) { /* labeled failure */ 340 if (pstack->s != NULL) { /* labeled failure: giveup or backtrack frame */
341 stack = pstack;
345 s = stack->s; 342 s = stack->s;
343 if (ndyncap > 0) /* is there matchtime captures? */
344 ndyncap -= removedyncap(L, capture, stack->caplevel, captop);
345 captop = stack->caplevel;
346 } else { /* labeled failure: recovery frame */
347 if (stack == stacklimit)
348 stack = doublestack(L, &stacklimit, ptop);
349 stack->s = NULL;
350 stack->p = pk; /* save return address */
351 stack->ls = NULL;
352 stack->caplevel = captop; /* TODO: really necessary?? */
353 stack++;
346 } 354 }
347 if (ndyncap > 0) /* is there matchtime captures? */ 355 p = pstack->p;
348 ndyncap -= removedyncap(L, capture, stack->caplevel, captop);
349 captop = stack->caplevel;
350 p = stack->p;
351 continue; 356 continue;
352 } 357 }
353 case ICloseRunTime: { 358 case ICloseRunTime: {
@@ -361,7 +366,8 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
361 res = resdyncaptures(L, fr, s - o, e - o); /* get result */ 366 res = resdyncaptures(L, fr, s - o, e - o); /* get result */
362 if (res == -1) { /* fail? */ 367 if (res == -1) { /* fail? */
363 *labelf = LFAIL; /* labeled failure */ 368 *labelf = LFAIL; /* labeled failure */
364 *sfail = (const char *) s; /* TODO: ??? */ 369 *sfail = (const char *) s;
370 pk = NULL;
365 goto fail; 371 goto fail;
366 } 372 }
367 s = o + res; /* else update current position */ 373 s = o + res; /* else update current position */
diff --git a/lpvm.h b/lpvm.h
index 3c27027..bcfc22f 100644
--- a/lpvm.h
+++ b/lpvm.h
@@ -35,7 +35,6 @@ typedef enum Opcode {
35 ICloseCapture, 35 ICloseCapture,
36 ICloseRunTime, 36 ICloseRunTime,
37 IThrow, /* "fails" with a specific label labeled failure */ 37 IThrow, /* "fails" with a specific label labeled failure */
38 ILabChoice, /* labeled choice */
39 IRecov /* stack a recovery; next fail with label 'f' will jump to 'offset' */ 38 IRecov /* stack a recovery; next fail with label 'f' will jump to 'offset' */
40} Opcode; 39} Opcode;
41 40
diff --git a/makefile b/makefile
index 5728b38..414651e 100644
--- a/makefile
+++ b/makefile
@@ -1,4 +1,4 @@
1LIBNAME = lpeglabel 1LIBNAME = lpeglabelrec
2LUADIR = ../lua/ 2LUADIR = ../lua/
3 3
4COPT = -O2 4COPT = -O2
@@ -29,24 +29,24 @@ FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o
29 29
30# For Linux 30# For Linux
31linux: 31linux:
32 make lpeglabel.so "DLLFLAGS = -shared -fPIC" 32 make lpeglabelrec.so "DLLFLAGS = -shared -fPIC"
33 33
34# For Mac OS 34# For Mac OS
35macosx: 35macosx:
36 make lpeglabel.so "DLLFLAGS = -bundle -undefined dynamic_lookup" 36 make lpeglabelrec.so "DLLFLAGS = -bundle -undefined dynamic_lookup"
37 37
38lpeglabel.so: $(FILES) 38lpeglabelrec.so: $(FILES)
39 env $(CC) $(DLLFLAGS) $(FILES) -o lpeglabel.so 39 env $(CC) $(DLLFLAGS) $(FILES) -o lpeglabelrec.so
40 40
41$(FILES): makefile 41$(FILES): makefile
42 42
43test: test.lua testlabel.lua testerrors.lua relabel.lua lpeglabel.so 43test: test.lua testlabel.lua testerrors.lua relabel.lua lpeglabelrec.so
44 lua test.lua 44 lua test.lua
45 lua testlabel.lua 45 lua testlabel.lua
46 lua testerrors.lua 46 lua testerrors.lua
47 47
48clean: 48clean:
49 rm -f $(FILES) lpeglabel.so 49 rm -f $(FILES) lpeglabelrec.so
50 50
51 51
52lpcap.o: lpcap.c lpcap.h lptypes.h 52lpcap.o: lpcap.c lpcap.h lptypes.h
diff --git a/relabel.lua b/relabelrec.lua
index b541dec..16ca7f0 100644
--- a/relabel.lua
+++ b/relabelrec.lua
@@ -6,7 +6,7 @@ local pcall = pcall
6local setmetatable = setmetatable 6local setmetatable = setmetatable
7local unpack, tinsert, concat = table.unpack or unpack, table.insert, table.concat 7local unpack, tinsert, concat = table.unpack or unpack, table.insert, table.concat
8local rep = string.rep 8local rep = string.rep
9local m = require"lpeglabel" 9local m = require"lpeglabelrec"
10 10
11-- 'm' will be used to parse expressions, and 'mm' will be used to 11-- 'm' will be used to parse expressions, and 'mm' will be used to
12-- create expressions; that is, 're' runs on 'm', creating patterns 12-- create expressions; that is, 're' runs on 'm', creating patterns
@@ -216,14 +216,14 @@ local function NT (n, b)
216 end 216 end
217end 217end
218 218
219local function labchoice (...) 219local function choicerec (...)
220 local t = { ... } 220 local t = { ... }
221 local n = #t 221 local n = #t
222 local p = t[1] 222 local p = t[1]
223 local i = 2 223 local i = 2
224 while i + 1 <= n do 224 while i + 1 <= n do
225 -- t[i] == nil when there are no labels 225 -- t[i] == nil when there are no labels
226 p = t[i] and mm.Lc(p, t[i+1], unpack(t[i])) or mt.__add(p, t[i+1]) 226 p = t[i] and mm.Rec(p, t[i+1], unpack(t[i])) or mt.__add(p, t[i+1])
227 i = i + 2 227 i = i + 2
228 end 228 end
229 229
@@ -232,10 +232,10 @@ end
232 232
233local exp = m.P{ "Exp", 233local exp = m.P{ "Exp",
234 Exp = S * ( m.V"Grammar" 234 Exp = S * ( m.V"Grammar"
235 + (m.V"Seq" * (S * "/" * (m.Ct(m.V"Labels") + m.Cc(nil)) 235 + (m.V"Seq" * (S * (("//" * m.Ct(m.V"Labels")) + ("/" * m.Cc(nil)))
236 * expect(S * m.V"Seq", "ExpPatt1") 236 * expect(S * m.V"Seq", "ExpPatt1")
237 )^0 237 )^0
238 ) / labchoice); 238 ) / choicerec);
239 Labels = m.P"{" * expect(S * m.V"Label", "ExpLab1") 239 Labels = m.P"{" * expect(S * m.V"Label", "ExpLab1")
240 * (S * "," * expect(S * m.V"Label", "ExpLab2"))^0 240 * (S * "," * expect(S * m.V"Label", "ExpLab2"))^0
241 * expect(S * "}", "MisClose7"); 241 * expect(S * "}", "MisClose7");
diff --git a/test.lua b/test.lua
index d5922ac..18ab20f 100755
--- a/test.lua
+++ b/test.lua
@@ -4,7 +4,7 @@
4 4
5-- require"strict" -- just to be pedantic 5-- require"strict" -- just to be pedantic
6 6
7local m = require"lpeglabel" 7local m = require"lpeglabelrec"
8 8
9 9
10-- for general use 10-- for general use
@@ -1110,7 +1110,7 @@ checkeq(t, {'a', 'aa', 20, 'a', 'aaa', 'aaa'})
1110-- Tests for 're' module 1110-- Tests for 're' module
1111------------------------------------------------------------------- 1111-------------------------------------------------------------------
1112 1112
1113local re = require "relabel" 1113local re = require "relabelrec"
1114 1114
1115local match, compile = re.match, re.compile 1115local match, compile = re.match, re.compile
1116 1116
diff --git a/testlabel.lua b/testlabel.lua
index db16354..d9bac64 100644
--- a/testlabel.lua
+++ b/testlabel.lua
@@ -1,4 +1,4 @@
1local m = require 'lpeglabel' 1local m = require 'lpeglabelrec'
2 2
3local p, r, l, s, serror 3local p, r, l, s, serror
4 4
@@ -11,6 +11,15 @@ local function checkeqlab (x, ...)
11 end 11 end
12end 12end
13 13
14local function checkeq (x, y, p)
15if p then print(x,y) end
16 if type(x) ~= "table" then assert(x == y)
17 else
18 for k,v in pairs(x) do checkeq(v, y[k], p) end
19 for k,v in pairs(y) do checkeq(v, x[k], p) end
20 end
21end
22
14-- throws a label 23-- throws a label
15p = m.T(1) 24p = m.T(1)
16s = "abc" 25s = "abc"
@@ -32,47 +41,97 @@ local g = m.P{
32r, l, serror = g:match(s) 41r, l, serror = g:match(s)
33assert(r == nil and l == 1 and serror == "abc") 42assert(r == nil and l == 1 and serror == "abc")
34 43
35-- throws a label that is not caught by labeled choice 44
36p = m.Lc(m.T(2), m.P"a", 1, 3) 45-- throws a label that is not caught by the recovery operator
46p = m.Rec(m.T(2), m.P"a", 1, 3)
37r, l, serror = p:match(s) 47r, l, serror = p:match(s)
38assert(r == nil and l == 2 and serror == "abc") 48assert(r == nil and l == 2 and serror == "abc")
39 49
40-- modifies previous pattern 50-- wraps the previous pattern with a recovery that catches label "2"
41-- adds another labeled choice to catch label "2" 51p = m.Rec(p, m.P"a", 2)
42p = m.Lc(p, m.P"a", 2)
43assert(p:match(s) == 2) 52assert(p:match(s) == 2)
44 53
45-- throws a label that is caught by labeled choice 54-- throws a label that is caught by recovery
46p = m.Lc(m.T(25), m.P"a", 25) 55p = m.Rec(m.T(25), m.P"a", 25)
47assert(p:match(s) == 2) 56assert(p:match(s) == 2)
48 57
49-- "fail" is label "0" 58-- "fail" is label "0"
50-- throws the "fail" label that is not caught by the labeled choice 59-- throws the "fail" label after the recovery
51s = "bola" 60s = "bola"
52r, l, serror = p:match("bola") 61r, l, serror = p:match("bola")
53assert(r == nil and l == 0 and serror == "bola") 62assert(r == nil and l == 0 and serror == "bola")
54 63
55-- labeled choice does not catch "fail" by default 64-- Recovery does not catch "fail" by default
56p = m.Lc(m.P"b", m.P"a", 1) 65p = m.Rec(m.P"b", m.P"a", 1)
57 66
58r, l, serror = p:match("abc") 67r, l, serror = p:match("abc")
59assert(r == nil and l == 0 and serror == "abc") 68assert(r == nil and l == 0 and serror == "abc")
60 69
61assert(p:match("bola") == 2) 70assert(p:match("bola") == 2)
62 71
63-- labeled choice can catch "fail" 72
64p = m.Lc(m.P"b", m.P"a", 0) 73-- recovery operator catches "1" or "3"
74p = m.Rec((m.P"a" + m.T(1)) * m.T(3), (m.P"a" + m.P"b"), 1, 3)
75assert(p:match("aac") == 3)
76assert(p:match("abc") == 3)
77r, l, serror = p:match("acc")
78assert(r == nil and l == 0 and serror == "cc")
79
80--throws 1, recovery pattern matches 'b', throw 3, and rec pat mathces 'a'
81assert(p:match("bac") == 3)
82
83r, l, serror = p:match("cab")
84assert(r == nil and l == 0 and serror == "cab")
85
86
87-- associativity
88-- (p1 / %1) //{1} (p2 / %2) //{2} p3
89-- left-associativity
90-- ("a" //{1} "b") //{2} "c"
91p = m.Rec(m.Rec(m.P"a" + m.T(1), m.P"b" + m.T(2), 1), m.P"c", 2)
65assert(p:match("abc") == 2) 92assert(p:match("abc") == 2)
66assert(p:match("bola") == 2) 93assert(p:match("bac") == 2)
94assert(p:match("cab") == 2)
95r, l, serror = p:match("dab")
96assert(r == nil and l == 0 and serror == "dab")
67 97
68-- "fail" is label "0" 98
69-- labeled choice catches "fail" or "3" 99-- righ-associativity
70p = m.Lc(m.P"a" * m.T(3), (m.P"a" + m.P"b"), 0, 3) 100-- "a" //{1} ("b" //{2} "c")
101p = m.Rec(m.P"a" + m.T(1), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1)
71assert(p:match("abc") == 2) 102assert(p:match("abc") == 2)
72assert(p:match("bac") == 2) 103assert(p:match("bac") == 2)
104assert(p:match("cab") == 2)
105r, l, serror = p:match("dab")
106assert(r == nil and l == 0 and serror == "dab")
107
108
109-- associativity -> in this case the error thrown by p1 is only
110-- recovered when we have a left-associative operator
111-- (p1 / %2) //{1} (p2 / %2) //{2} p3
112-- left-associativity
113-- ("a" //{1} "b") //{2} "c"
114p = m.Rec(m.Rec(m.P"a" + m.T(2), m.P"b" + m.T(2), 1), m.P"c", 2)
115assert(p:match("abc") == 2)
116r, l, serror = p:match("bac")
117assert(r == nil and l == 0 and serror == "bac")
118assert(p:match("cab") == 2)
119r, l, serror = p:match("dab")
120assert(r == nil and l == 0 and serror == "dab")
73 121
122
123-- righ-associativity
124-- "a" //{1} ("b" //{2} "c")
125p = m.Rec(m.P"a" + m.T(2), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1)
126assert(p:match("abc") == 2)
127r, l, serror = p:match("bac")
128assert(r == nil and l == 2 and serror == "bac")
74r, l, serror = p:match("cab") 129r, l, serror = p:match("cab")
75assert(r == nil and l == 0 and serror == "cab") 130assert(r == nil and l == 2 and serror == "cab")
131r, l, serror = p:match("dab")
132assert(r == nil and l == 2 and serror == "dab")
133
134
76 135
77-- tests related to predicates 136-- tests related to predicates
78p = #m.T(1) + m.P"a" 137p = #m.T(1) + m.P"a"
@@ -83,74 +142,75 @@ p = ##m.T(1) + m.P"a"
83r, l, serror = p:match("abc") 142r, l, serror = p:match("abc")
84assert(r == nil and l == 1 and serror == "abc") 143assert(r == nil and l == 1 and serror == "abc")
85 144
86p = #m.T(0) * m.P"a"
87assert(p:match("abc") == fail)
88
89p = #m.T(0) + m.P"a"
90assert(p:match("abc") == 2)
91
92p = -m.T(1) * m.P"a" 145p = -m.T(1) * m.P"a"
93r, l, serror = p:match("abc") 146r, l, serror = p:match("abc")
94assert(r == nil and l == 1 and serror == "abc") 147assert(r == nil and l == 1 and serror == "abc")
95 148
149p = -m.T(1) * m.P"a"
150r, l, serror = p:match("bbc")
151assert(r == nil and l == 1 and serror == "bbc")
152
96p = -(-m.T(1)) * m.P"a" 153p = -(-m.T(1)) * m.P"a"
97r, l, serror = p:match("abc") 154r, l, serror = p:match("abc")
98assert(r == nil and l == 1 and serror == "abc") 155assert(r == nil and l == 1 and serror == "abc")
99 156
100p = -m.T(0) * m.P"a" 157p = m.Rec(-m.T(22), m.P"a", 22)
101assert(p:match("abc") == 2) 158r, l, serror = p:match("abc")
159assert(r == nil and l == 0 and serror == "bc")
102 160
103p = -m.T(0) + m.P"a" 161assert(p:match("bbc") == 1)
104assert(p:match("abc") == 1)
105 162
106p = -(-m.T(0)) + m.P"a" 163p = m.Rec(#m.T(22), m.P"a", 22)
107assert(p:match("abc") == 2) 164assert(p:match("abc") == 1)
108 165
109p = m.Lc(-m.T(22), m.P"a", 22) 166p = #m.Rec(m.T(22), m.P"a", 22)
110assert(p:match("abc") == 2) 167assert(p:match("abc") == 1)
111 168
112p = m.Lc(-m.T(0), m.P"a", 0) 169p = m.Rec(m.T(22), #m.P"a", 22)
113assert(p:match("abc") == 1) 170assert(p:match("abc") == 1)
114 171
115p = m.Lc(#m.T(22), m.P"a", 22) 172p = m.Rec(#m.T(22), m.P"a", 22)
116assert(p:match("abc") == 2) 173r, l, serror = p:match("bbc")
174assert(r == nil and l == 0 and serror == "bbc")
175
176p = m.Rec(#m.P("a") * m.T(22), m.T(15), 22)
177r, l, serror = p:match("abc")
178assert(r == nil and l == 15 and serror == "abc")
179
180p = m.Rec(#(m.P("a") * m.T(22)), m.T(15), 22)
181r, l, serror = p:match("abc")
182assert(r == nil and l == 15 and serror == "bc")
183
117 184
118p = m.Lc(#m.T(0), m.P"a", 0)
119assert(p:match("abc") == 2)
120 185
121-- tests related to repetition 186-- tests related to repetition
122p = m.T(1)^0 187p = m.T(1)^0
123r, l, serror = p:match("ab") 188r, l, serror = p:match("ab")
124assert(r == nil and l == 1 and serror == "ab") 189assert(r == nil and l == 1 and serror == "ab")
125 190
126p = m.T(0)^0
127assert(p:match("ab") == 1)
128
129p = (m.P"a" + m.T(1))^0 191p = (m.P"a" + m.T(1))^0
130r, l, serror = p:match("aa") 192r, l, serror = p:match("aa")
131assert(r == nil and l == 1 and serror == "") 193assert(r == nil and l == 1 and serror == "")
132 194
133p = (m.P"a" + m.T(0))^0
134assert(p:match("aa") == 3)
135 195
136-- Bug reported by Matthew Allen 196-- Bug reported by Matthew Allen
137-- some optmizations performed by LPeg should not be 197-- some optmizations performed by LPeg should not be
138-- applied in case of labeled choices 198-- applied in case of labeled choices
139p = m.Lc(m.P"A", m.P(true), 1) + m.P("B") 199p = m.Rec(m.P"A", m.P(true), 1) + m.P("B")
140assert(p:match("B") == 2) 200assert(p:match("B") == 2)
141 201
142p = m.Lc(m.P"A", m.P(false), 1) + m.P("B") 202p = m.Rec(m.P"A", m.P(false), 1) + m.P("B")
143assert(p:match("B") == 2) 203assert(p:match("B") == 2)
144 204
145 205
146--[[ 206--[[
147S -> A /{1} 'a' 207S -> A //{1} 'a'
148A -> B 208A -> B
149B -> %1 209B -> %1
150]] 210]]
151g = m.P{ 211g = m.P{
152 "S", 212 "S",
153 S = m.Lc(m.V"A", m.P"a", 1), 213 S = m.Rec(m.V"A", m.P"a", 1),
154 A = m.V"B", 214 A = m.V"B",
155 B = m.T(1), 215 B = m.T(1),
156} 216}
@@ -176,64 +236,96 @@ r, l, serror = g:match("a;a")
176assert(r == nil and l == 1 and serror == "") 236assert(r == nil and l == 1 and serror == "")
177 237
178 238
179-- %1 /{1,3} %2 /{2} 'a' 239-- %1 //{1,3} %2 //{2} 'a'
180p = m.Lc(m.Lc(m.T(1), m.T(2), 1, 3), m.P"a", 2) 240p = m.Rec(m.Rec(m.T(1), m.T(2), 1, 3), m.P"a", 2)
181assert(p:match("abc") == 2) 241assert(p:match("abc") == 2)
182 242
183r, l, serror = p:match("") 243r, l, serror = p:match("")
184assert(r == nil and l == 0 and serror == "") 244assert(r == nil and l == 0 and serror == "")
185 245
186p = m.Lc(m.T(1), m.Lc(m.T(2), m.P"a", 2), 1, 3) 246p = m.Rec(m.T(1), m.Rec(m.T(2), m.P"a", 2), 1, 3)
187assert(p:match("abc") == 2) 247assert(p:match("abc") == 2)
188 248
189r, l, serror = p:match("") 249r, l, serror = p:match("")
190assert(r == nil and l == 0 and serror == "") 250assert(r == nil and l == 0 and serror == "")
191 251
252
253-- Infinte Loop TODO: check the semantics
254-- %1 //{1} %1
255p = m.Rec(m.T(1), m.T(1), 1)
256--r, l, serror = p:match("ab")
257--assert(r == nil and l == 1 and serror == "ab")
258
259-- %1 //{1} 'a' (!. / %1)
260p = m.Rec(m.T(1), m.P"a" * (-m.P(1) + m.T(1)), 1)
261r, l, serror = p:match("ab")
262assert(r == nil and l == 0 and serror == "b")
263
264r, l, serror = p:match("cd")
265assert(r == nil and l == 0 and serror == "cd")
266
267-- %1 //{1} . (!. / %1)
268p = m.Rec(m.T(1), m.P(1) * (-m.P(1) + m.T(1)), 1)
269assert(p:match("abc") == 4)
270
271
192-- testing the limit of labels 272-- testing the limit of labels
193p = m.T(0) 273-- can only throw labels between 1 and 255
194s = "abc" 274local r = pcall(m.Rec, m.P"b", m.P"a", 0)
195r, l, serror = p:match(s) 275assert(r == false)
196assert(r == nil and l == 0 and serror == "abc")
197 276
198p = m.T(255) 277local r = pcall(m.Rec, m.P"b", m.P"a", 256)
199s = "abc" 278assert(r == false)
200r, l, serror = p:match(s)
201assert(r == nil and l == 255 and serror == "abc")
202 279
203local r = pcall(m.T, -1) 280local r = pcall(m.Rec, m.P"b", m.P"a", -1)
281assert(r == false)
282
283local r = pcall(m.T, 0)
204assert(r == false) 284assert(r == false)
205 285
206local r = pcall(m.T, 256) 286local r = pcall(m.T, 256)
207assert(r == false) 287assert(r == false)
208 288
289local r = pcall(m.T, -1)
290assert(r == false)
291
292
293local r = m.Rec(m.P"b", m.P"a", 255)
294assert(p:match("a") == 2)
295
296p = m.T(255)
297s = "abc"
298r, l, serror = p:match(s)
299assert(r == nil and l == 255 and serror == "abc")
300
301
209 302
210print("+") 303print("+")
211 304
212--[[ grammar based on Figure 8 of paper submitted to SCP 305--[[ grammar based on Figure 8 of paper submitted to SCP
213S -> S0 /{1} ID /{2} ID '=' Exp /{3} 'unsigned'* 'int' ID /{4} 'unsigned'* ID ID / %error 306S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error
214S0 -> ID S1 / 'unsigned' S2 / 'int' %3 307S0 -> S1 / S2 / &'int' %3
215S1 -> '=' %2 / !. %1 / ID %4 308S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4
216S2 -> 'unsigned' S2 / ID %4 / 'int' %3 309S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3
217]] 310]]
218
219local sp = m.S" \t\n"^0 311local sp = m.S" \t\n"^0
220local eq = sp * m.P"=" 312local eq = sp * m.P"="
221 313
222g = m.P{ 314g = m.P{
223 "S", 315 "S",
224 S = m.Lc( 316 S = m.Rec(
225 m.Lc( 317 m.Rec(
226 m.Lc( 318 m.Rec(
227 m.Lc(m.V"S0", m.V"ID" * (m.P(1) + ""), 1), 319 m.Rec(m.V"S0", m.V"ID", 1),
228 m.V"ID" * eq * m.V"Exp", 2 320 m.V"ID" * eq * m.V"Exp", 2
229 ), 321 ),
230 m.V"U"^0 * m.V"I" * m.V"ID", 3 322 m.V"U"^0 * m.V"I" * m.V"ID", 3
231 ), 323 ),
232 m.V"U"^0 * m.V"ID" * m.V"ID", 4) 324 m.V"U"^0 * m.V"ID" * m.V"ID", 4)
233 + m.T(5), -- error 325 + m.T(5), -- error
234 S0 = m.V"ID" * m.V"S1" + m.V"U" * m.V"S2" + m.V"I" * m.T(3), 326 S0 = m.V"S1" + m.V"S2" + #m.V"I" * m.T(3),
235 S1 = eq * m.T(2) + sp * -m.P(1) * m.T(1) + m.V"ID" * m.T(4), 327 S1 = #(m.V"ID" * eq) * m.T(2) + sp * #(m.V"ID" * -m.P(1)) * m.T(1) + #m.V"ID" * m.T(4),
236 S2 = m.V"U" * m.V"S2" + m.V"ID" * m.T(4) + m.V"I" * m.T(3), 328 S2 = #(m.V"U"^1 * m.V"ID") * m.T(4) + #(m.V"U"^1 * m.V"I") * m.T(3),
237 ID = sp * m.P"a", 329 ID = sp * m.P"a",
238 U = sp * m.P"unsigned", 330 U = sp * m.P"unsigned",
239 I = sp * m.P"int", 331 I = sp * m.P"int",
@@ -270,53 +362,59 @@ assert(r == nil and l == 5 and serror == s)
270 362
271print("+") 363print("+")
272 364
273local re = require 'relabel'
274 365
275g = re.compile[['a' /{4,9} [a-z] 366local re = require 'relabelrec'
367
368g = re.compile[['a' //{4,9} [a-z]
276]] 369]]
277assert(g:match("a") == 2) 370assert(g:match("a") == 2)
278r, l, serror = g:match("b") 371r, l, serror = g:match("b")
279assert(r == nil and l == 0 and serror == "b") 372assert(r == nil and l == 0 and serror == "b")
280 373
281g = re.compile[['a' /{4,9} [a-f] /{5, 7} [a-z] 374g = re.compile[['a' //{4,9} [a-f] //{5, 7} [a-z]
282]] 375]]
283assert(g:match("a") == 2) 376assert(g:match("a") == 2)
284r, l, serror = g:match("b") 377r, l, serror = g:match("b")
285assert(r == nil and l == 0 and serror == "b") 378assert(r == nil and l == 0 and serror == "b")
286 379
287g = re.compile[[%{1} /{4,9} [a-z] 380g = re.compile[[%{1} //{4,9} [a-z]
288]] 381]]
289r, l, serror = g:match("a") 382r, l, serror = g:match("a")
290assert(r == nil and l == 1 and serror == "a") 383assert(r == nil and l == 1 and serror == "a")
291 384
292 385
293g = re.compile[[%{1} /{4,1} [a-f] 386g = re.compile[[%{1} //{4,1} [a-f]
294]] 387]]
295assert(g:match("a") == 2) 388assert(g:match("a") == 2)
296r, l, serror = g:match("h") 389r, l, serror = g:match("h")
297assert(r == nil and l == 0 and serror == "h") 390assert(r == nil and l == 0 and serror == "h")
298 391
299g = re.compile[[[a-f]%{9} /{4,9} [a-c]%{7} /{5, 7} [a-z] ]] 392g = re.compile[[[a-f]%{9} //{4,9} [a-c]%{7} //{5, 7} [a-z] ]]
300assert(g:match("a") == 2) 393r, l, serror = g:match("a")
301assert(g:match("c") == 2) 394assert(r == nil and l == 0 and serror == "")
302r, l, serror = g:match("d") 395r, l, serror = g:match("aa")
396assert(r == nil and l == 0 and serror == "")
397assert(g:match("aaa") == 4)
398
399r, l, serror = g:match("ad")
303assert(r == nil and l == 0 and serror == "d") 400assert(r == nil and l == 0 and serror == "d")
401
304r, l, serror = g:match("g") 402r, l, serror = g:match("g")
305assert(r == nil and l == 0 and serror == "g") 403assert(r == nil and l == 0 and serror == "g")
306 404
405
307--[[ grammar based on Figure 8 of paper submitted to SCP 406--[[ grammar based on Figure 8 of paper submitted to SCP
308S -> S0 /{1} ID /{2} ID '=' Exp /{3} 'unsigned'* 'int' ID /{4} 'unsigned'* ID ID / %error 407S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error
309S0 -> ID S1 / 'unsigned' S2 / 'int' %3 408S0 -> S1 / S2 / &'int' %3
310S1 -> '=' %2 / !. %1 / ID %4 409S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4
311S2 -> 'unsigned' S2 / ID %4 / 'int' %3 410S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3
312]] 411]]
313 412
314
315g = re.compile([[ 413g = re.compile([[
316 S <- S0 /{1} ID /{2} ID %s* '=' Exp /{3} U* Int ID /{4} U ID ID /{0} %{5} 414 S <- S0 //{1} ID //{2} ID %s* '=' Exp //{3} U* Int ID //{4} U ID ID / %{5}
317 S0 <- ID S1 / U S2 / Int %{3} 415 S0 <- S1 / S2 / &Int %{3}
318 S1 <- %s* '=' %{2} / !. %{1} / ID %{4} 416 S1 <- &(ID %s* '=') %{2} / &(ID !.) %{1} / &ID %{4}
319 S2 <- U S2 / ID %{4} / Int %{3} 417 S2 <- &(U+ ID) %{4} / &(U+ Int) %{3}
320 ID <- %s* 'a' 418 ID <- %s* 'a'
321 U <- %s* 'unsigned' 419 U <- %s* 'unsigned'
322 Int <- %s* 'int' 420 Int <- %s* 'int'
@@ -346,6 +444,8 @@ s = "unsigned int"
346r, l, serror = g:match(s) 444r, l, serror = g:match(s)
347assert(r == nil and l == 5 and serror == s) 445assert(r == nil and l == 5 and serror == s)
348 446
447
448
349local terror = { ['cmdSeq'] = "Missing ';' in CmdSeq", 449local terror = { ['cmdSeq'] = "Missing ';' in CmdSeq",
350 ['ifExp'] = "Error in expresion of 'if'", 450 ['ifExp'] = "Error in expresion of 'if'",
351 ['ifThen'] = "Error matching 'then' keyword", 451 ['ifThen'] = "Error matching 'then' keyword",
@@ -367,12 +467,12 @@ local terror = { ['cmdSeq'] = "Missing ';' in CmdSeq",
367 ['undefined'] = "Undefined Error"} 467 ['undefined'] = "Undefined Error"}
368 468
369g = re.compile([[ 469g = re.compile([[
370 Tiny <- CmdSeq /{1} '' -> cmdSeq /{2} '' -> ifExp /{3} '' -> ifThen /{4} '' -> ifThenCmdSeq 470 Tiny <- CmdSeq //{1} '' -> cmdSeq //{2} '' -> ifExp //{3} '' -> ifThen //{4} '' -> ifThenCmdSeq
371 /{5} '' -> ifElseCmdSeq /{6} '' -> ifEnd /{7} '' -> repeatCmdSeq 471 //{5} '' -> ifElseCmdSeq //{6} '' -> ifEnd //{7} '' -> repeatCmdSeq
372 /{8} '' -> repeatUntil /{9} '' -> repeatExp /{10} '' -> assignOp 472 //{8} '' -> repeatUntil //{9} '' -> repeatExp //{10} '' -> assignOp
373 /{11} '' -> assignExp /{12} '' -> readName /{13} '' -> writeExp 473 //{11} '' -> assignExp //{12} '' -> readName //{13} '' -> writeExp
374 /{14} '' -> simpleExp /{15} '' -> term /{16} '' -> factor 474 //{14} '' -> simpleExp //{15} '' -> term //{16} '' -> factor
375 /{17} '' -> openParExp /{18} '' -> closePar /{0} '' -> undefined 475 //{17} '' -> openParExp //{18} '' -> closePar / '' -> undefined
376 CmdSeq <- (Cmd (SEMICOLON / %{1})) (Cmd (SEMICOLON / %{1}))* 476 CmdSeq <- (Cmd (SEMICOLON / %{1})) (Cmd (SEMICOLON / %{1}))*
377 Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd 477 Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd
378 IfCmd <- IF (Exp / %{2}) (THEN / %{3}) (CmdSeq / %{4}) (ELSE (CmdSeq / %{5}) / '') (END / %{6}) 478 IfCmd <- IF (Exp / %{2}) (THEN / %{3}) (CmdSeq / %{4}) (ELSE (CmdSeq / %{5}) / '') (END / %{6})
@@ -550,18 +650,12 @@ assert(g:match(s) == terror['undefined'])
550print("+") 650print("+")
551 651
552 652
553-- test recovery operator
554p = m.Rec("a", "b")
555assert(p:match("a") == 2)
556assert(p:match("b") == 2)
557checkeqlab({nil, 0, "c"}, p:match("c"))
558
559p = m.Rec("a", "b", 3) 653p = m.Rec("a", "b", 3)
560assert(p:match("a") == 2) 654assert(p:match("a") == 2)
561checkeqlab({nil, 0, "b"}, p:match("b")) 655checkeqlab({nil, 0, "b"}, p:match("b"))
562checkeqlab({nil, 0, "c"}, p:match("c")) 656checkeqlab({nil, 0, "c"}, p:match("c"))
563 657
564p = m.Rec(m.T(3), "b") 658p = m.Rec(m.T(3), "b", 1)
565checkeqlab({nil, 3, "a"}, p:match("a")) 659checkeqlab({nil, 3, "a"}, p:match("a"))
566checkeqlab({nil, 3, "b"}, p:match("b")) 660checkeqlab({nil, 3, "b"}, p:match("b"))
567 661
@@ -570,27 +664,28 @@ checkeqlab({nil, 0, "a"}, p:match("a"))
570assert(p:match("b") == 2) 664assert(p:match("b") == 2)
571 665
572--[[ 666--[[
573S -> (A //{fail} (!c .)*) C 667S -> (A //{128} (!c .)*) C
574A -> a*b 668A -> a*b / %128
575C -> c+ 669C -> c+
576]] 670]]
577g = m.P{ 671g = m.P{
578 "S", 672 "S",
579 S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0) * m.V"C", 673 S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0, 128) * m.V"C",
580 A = m.P"a"^0 * "b", 674 A = m.P"a"^0 * "b" + m.T(128),
581 C = m.P"c"^1, 675 C = m.P"c"^1,
582} 676}
583 677
584assert(g:match("abc") == 4) 678assert(g:match("abc") == 4)
585assert(g:match("aabc") == 5) 679assert(g:match("aabc") == 5)
586assert(g:match("aadc") == 5) 680assert(g:match("aadc") == 5)
587assert(g:match("bc") == 3) 681assert(g:match("dc") == 3)
588checkeqlab({nil, 0, "bc"}, g:match("bbc")) 682checkeqlab({nil, 0, "bc"}, g:match("bbc"))
589assert(g:match("xxc") == 4) 683assert(g:match("xxc") == 4)
590assert(g:match("c") == 2) 684assert(g:match("c") == 2)
591checkeqlab({nil, 0, ""}, g:match("fail")) 685checkeqlab({nil, 0, ""}, g:match("fail"))
592checkeqlab({nil, 0, ""}, g:match("aaxx")) 686checkeqlab({nil, 0, ""}, g:match("aaxx"))
593 687
688
594--[[ 689--[[
595S -> (A //{99} (!c .)*) C 690S -> (A //{99} (!c .)*) C
596A -> a+ (b / ^99) 691A -> a+ (b / ^99)
@@ -726,7 +821,169 @@ print(eval "(1+1-1*(2/2+)-():")
726--> syntax error: extra characters found after the expression (at index 821--> syntax error: extra characters found after the expression (at index
727 822
728 823
824print("+")
825
826local g = m.P{
827 "S",
828 S = V"End" + V'A' * V'S',
829 A = P'a' + T(1),
830 End = P"." * (-P(1) + T(2)),
831}
729 832
833assert(g:match("a.") == 3)
834assert(g:match("aa.") == 4)
835assert(g:match(".") == 2)
836checkeqlab({nil, 1, "ba."}, g:match("ba."))
837checkeqlab({nil, 1, "ba."}, g:match("aba."))
838checkeqlab({nil, 1, "cba."}, g:match("cba."))
839checkeqlab({nil, 2, "a"}, g:match("a.a"))
730 840
731print("OK")
732 841
842local g2 = m.P{
843 "S",
844 S = m.Rec(g, V"B", 1),
845 B = P'b'^1 + T(3)
846}
847
848assert(g2:match("a.") == 3)
849assert(g2:match("aa.") == 4)
850assert(g2:match(".") == 2)
851assert(g2:match("ba.") == 4)
852assert(g2:match("aba.") == 5)
853checkeqlab({nil, 3, "cba."}, g2:match("cba."))
854checkeqlab({nil, 2, "a"}, g2:match("a.a"))
855
856local g3 = m.P{
857 "S",
858 S = m.Rec(g2, V"C", 2, 3),
859 C = P'c'^1 + T(4)
860}
861
862assert(g3:match("a.") == 3)
863assert(g3:match("aa.") == 4)
864assert(g3:match(".") == 2)
865assert(g3:match("ba.") == 4)
866assert(g3:match("aba.") == 5)
867assert(g3:match("cba.") == 5)
868checkeqlab({nil, 4, "a"}, g3:match("a.a"))
869checkeqlab({nil, 4, "dc"}, g3:match("dc"))
870checkeqlab({nil, 4, "d"}, g3:match(".d"))
871
872
873-- testing more captures
874local g = re.compile[[
875 S <- ( %s* &. {A} )*
876 A <- [0-9]+ / %{5}
877]]
878
879checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")})
880checkeq({nil, 5, "a 123"}, {g:match("44 a 123")})
881
882local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5)
883
884checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")})
885checkeq({"44", "a ", "58", "123"}, {g2:match("44 a 123")})
886
887
888local g = re.compile[[
889 S <- ( %s* &. A )*
890 A <- {[0-9]+} / %{5}
891]]
892
893checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")})
894checkeq({nil, 5, "a 123"}, {g:match("44 a 123")})
895
896local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5)
897
898checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")})
899checkeq({"44", "58", "123"}, {g2:match("44 a 123")})
900
901
902local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V
903local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt
904local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec
905
906local labels = {
907 {"NoExp", "no expression found"},
908 {"Extra", "extra characters found after the expression"},
909 {"ExpTerm", "expected a term after the operator"},
910 {"ExpExp", "expected an expression after the parenthesis"},
911 {"MisClose", "missing a closing ')' after the expression"},
912}
913
914local function labelindex(labname)
915 for i, elem in ipairs(labels) do
916 if elem[1] == labname then
917 return i
918 end
919 end
920 error("could not find label: " .. labname)
921end
922
923local errors = {}
924
925local function expect(patt, labname, recpatt)
926 local i = labelindex(labname)
927 function recorderror(input, pos)
928 table.insert(errors, {i, pos})
929 return true
930 end
931 if not recpatt then recpatt = P"" end
932 --return Rec(patt, Cmt("", recorderror) * recpatt)
933 return patt + T(i)
934end
935
936local num = R("09")^1 / tonumber
937local op = S("+-*/")
938
939local function compute(tokens)
940 local result = tokens[1]
941 for i = 2, #tokens, 2 do
942 if tokens[i] == '+' then
943 result = result + tokens[i+1]
944 elseif tokens[i] == '-' then
945 result = result - tokens[i+1]
946 elseif tokens[i] == '*' then
947 result = result * tokens[i+1]
948 elseif tokens[i] == '/' then
949 result = result / tokens[i+1]
950 else
951 error('unknown operation: ' .. tokens[i])
952 end
953 end
954 return result
955end
956
957
958local g = P {
959"Exp",
960Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute,
961Operand = expect(V"Term", "ExpTerm"),
962Term = num,
963}
964local rg = Rec(g, Cc(3), labelindex("ExpTerm"))
965
966local function eval(input)
967 local result, label, suffix = rg:match(input)
968 if #errors == 0 then
969 return result
970 else
971 local out = {}
972 for i, err in ipairs(errors) do
973 local pos = err[2]
974 local msg = labels[err[1]][2]
975 table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
976 end
977 errors = {}
978 return nil, table.concat(out, "\n")
979 end
980end
981
982assert(eval("98-76*54/32") == 37.125)
983--> 37.125
984
985assert(eval("1+") == 4)
986--> syntax error: expected a term after the operator (at index 3)
987
988
989print("OK")