diff options
-rw-r--r-- | README.md | 665 | ||||
-rw-r--r-- | examples/expRec.lua | 128 | ||||
-rw-r--r-- | examples/expRecAut.lua | 122 | ||||
-rw-r--r-- | examples/expect.lua | 98 | ||||
-rw-r--r-- | examples/listId1.lua | 12 | ||||
-rw-r--r-- | examples/listId2.lua | 13 | ||||
-rw-r--r-- | examples/listId2Rec2.lua | 67 | ||||
-rw-r--r-- | examples/listId2Rec2Cap.lua | 79 | ||||
-rw-r--r-- | examples/listIdCatch.lua | 28 | ||||
-rw-r--r-- | examples/listIdCatchRe.lua | 34 | ||||
-rw-r--r-- | examples/listIdRe1.lua | 8 | ||||
-rw-r--r-- | examples/listIdRe2.lua | 61 | ||||
-rw-r--r-- | examples/recovery.lua | 134 | ||||
-rw-r--r-- | examples/recoveryOpFail.lua | 105 | ||||
-rw-r--r-- | examples/recoveryOpLab.lua | 107 | ||||
-rw-r--r-- | examples/tiny.lua | 84 | ||||
-rwxr-xr-x | examples/typedlua/test.lua | 9 | ||||
-rw-r--r-- | examples/typedlua/tllexer.lua | 11 | ||||
-rw-r--r-- | examples/typedlua/tlparser.lua | 2 | ||||
-rw-r--r-- | lpcode.c | 40 | ||||
-rw-r--r-- | lpprint.c | 6 | ||||
-rw-r--r-- | lptree.c | 31 | ||||
-rw-r--r-- | lptree.h | 2 | ||||
-rw-r--r-- | lpvm.c | 52 | ||||
-rw-r--r-- | lpvm.h | 1 | ||||
-rw-r--r-- | makefile | 14 | ||||
-rw-r--r-- | relabelrec.lua (renamed from relabel.lua) | 10 | ||||
-rwxr-xr-x | test.lua | 4 | ||||
-rw-r--r-- | testlabel.lua | 475 |
29 files changed, 1407 insertions, 995 deletions
@@ -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) |
11 | library that provides an implementation of Parsing | 11 | library that provides an implementation of Parsing |
12 | Expression Grammars (PEGs) with labeled failures. | 12 | Expression Grammars (PEGs) with labeled failures. |
13 | Labels can be used to signal different kinds of erros | 13 | Labels can be used to signal different kinds of errors |
14 | and to specify which alternative in a labeled ordered | 14 | and to specify which recovery pattern should handle a |
15 | choice should handle a given label. Labels can also be | 15 | given label. Labels can also be combined with the standard |
16 | combined with the standard patterns of LPeg. | 16 | patterns of LPeg. |
17 | 17 | ||
18 | This document describes the new functions available | 18 | This document describes the new functions available |
19 | in LpegLabel and presents some examples of usage. | 19 | in LpegLabel and presents some examples of usage. |
20 | For a more detailed discussion about PEGs with labeled failures | ||
21 | please see [A Parsing Machine for Parsing Expression | ||
22 | Grammars with Labeled Failures](https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxzcW1lZGVpcm9zfGd4OjMzZmE3YzM0Y2E2MGM5Y2M). | ||
23 | |||
24 | 20 | ||
25 | In LPegLabel, the result of an unsuccessful matching | 21 | In LPegLabel, the result of an unsuccessful matching |
26 | is a triple **nil, lab, sfail**, where **lab** | 22 | is a triple **nil, lab, sfail**, where **lab** |
27 | is the label associated with the failure, and | 23 | is the label associated with the failure, and |
28 | **sfail** is the suffix input being matched when | 24 | **sfail** is the suffix input being matched when |
29 | **lab** was thrown. Below there is a brief summary | 25 | **lab** was thrown. |
30 | of the new functions provided by LpegLabel: | 26 | |
27 | With labeled failures it is possible to distinguish | ||
28 | between a regular failure and an error. Usually, a | ||
29 | regular failure is produced when the matching of a | ||
30 | character fails, and it is caught by an ordered choice. | ||
31 | An error, by its turn, is produced by the throw operator | ||
32 | and may be caught by the recovery operator. | ||
33 | |||
34 | Below there is a brief summary 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 | ||
65 | Returns a pattern that throws the label `l`. | 64 | Returns a pattern that throws the label `l`. |
66 | A label must be an integer between 0 and 255. | 65 | A label must be an integer between 1 and 255. |
67 | 66 | ||
68 | The 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> | 70 | Returns a *recovery pattern*. |
72 | |||
73 | Returns a pattern equivalent to a *labeled ordered choice*. | ||
74 | If the matching of `p1` gives one of the labels `l1, ..., ln`, | 71 | If the matching of `p1` gives one of the labels `l1, ..., ln`, |
75 | then the matching of `p2` is tried from the same position. Otherwise, | 72 | then the matching of `p2` is tried from the failure position of `p1`. |
76 | the result of the matching of `p1` is the pattern's result. | 73 | Otherwise, the result of the matching of `p1` is the pattern's result. |
77 | |||
78 | The labeled ordered choice `lpeg.Lc(p1, p2, 0)` is equivalent to the | ||
79 | regular ordered choice `p1 / p2`. | ||
80 | 74 | ||
81 | Although PEG's ordered choice is associative, the labeled ordered choice is not. | ||
82 | When using this function, the user should take care to build a left-associative | ||
83 | labeled 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 | ||
97 | Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. | 88 | Syntax 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 | ||
102 | Syntax of *relabel* module. Equivalent to `lpeg.Lc(p1, p2, l1, ..., ln)`. | 93 | Syntax of *relabelrec* module. Equivalent to `lpeglabelrec.Rec(p1, p2, l1, ..., ln)`. |
103 | 94 | ||
104 | The `/{}` operator is left-associative. | 95 | The `//{}` operator is left-associative. |
105 | 96 | ||
106 | A grammar can use both choice operators (`/` and `/{}`), | ||
107 | but a single choice can not mix them. That is, the parser of `relabel` | ||
108 | module 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 | ||
113 | Returns line and column information regarding position <i>i</i> of the subject. | 101 | Returns 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 | ||
118 | Allows to specicify a table with labels. They keys of | 106 | Allows 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, |
120 | and the associated values should be strings. | 108 | and the associated values should be strings. |
121 | 109 | ||
122 | 110 | ||
@@ -132,16 +120,31 @@ in the *examples* directory. | |||
132 | The following example defines a grammar that matches | 120 | The following example defines a grammar that matches |
133 | a list of identifiers separated by commas. A label | 121 | a list of identifiers separated by commas. A label |
134 | is thrown when there is an error matching an identifier | 122 | is thrown when there is an error matching an identifier |
135 | or a comma: | 123 | or a comma. |
124 | |||
125 | We use function `newError` to store error messages in a | ||
126 | table and to return the index associated with each error message. | ||
127 | |||
136 | 128 | ||
137 | ```lua | 129 | ```lua |
138 | local m = require'lpeglabel' | 130 | local m = require'lpeglabelrec' |
139 | local re = require'relabel' | 131 | local re = require'relabelrec' |
132 | |||
133 | local terror = {} | ||
134 | |||
135 | local function newError(s) | ||
136 | table.insert(terror, s) | ||
137 | return #terror | ||
138 | end | ||
139 | |||
140 | local errUndef = newError("undefined") | ||
141 | local errId = newError("expecting an identifier") | ||
142 | local errComma = newError("expecting ','") | ||
140 | 143 | ||
141 | local g = m.P{ | 144 | local 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 |
164 | end | 161 | end |
165 | 162 | ||
166 | print(mymatch(g, "one,two")) --> 8 | 163 | print(mymatch(g, "one,two")) --> 8 |
167 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | 164 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' |
168 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | 165 | print(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 | ||
171 | In this example we could think about writing rule <em>List</em> as follows: | 168 | In this example we could think about writing rule <em>List</em> as follows: |
172 | ```lua | 169 | ```lua |
173 | List = ((m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)))^0, | 170 | List = ((m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)))^0, |
174 | ``` | 171 | ``` |
175 | 172 | ||
176 | but when matching this expression agains the end of input | 173 | but when matching this expression against the end of input |
177 | we would get a failure whose associated label would be **2**, | 174 | we would get a failure whose associated label would be **errComma**, |
178 | and this would cause the failure of the *whole* repetition. | 175 | and this would cause the failure of the *whole* repetition. |
179 | |||
180 | 176 | ||
181 | ##### Mnemonics instead of numbers | ||
182 | 177 | ||
183 | In the previous example we could have created a table | ||
184 | with the error messages to improve the readbility of the PEG. | ||
185 | Below we rewrite the previous grammar following this approach: | ||
186 | 178 | ||
179 | #### Error Recovery | ||
180 | |||
181 | By using the `Rec` function we can specify a recovery pattern that | ||
182 | should be matched when a label is thrown. After matching the recovery | ||
183 | pattern, and possibly recording the error, the parser will resume | ||
184 | the <em>regular</em> matching. For example, in the example below | ||
185 | we expect to match rule `A`, but when a failure occur the label 42 | ||
186 | is thrown and then we will try to match the recovery pattern `recp`: | ||
187 | ```lua | 187 | ```lua |
188 | local m = require'lpeglabel' | 188 | local m = require'lpeglabelrec' |
189 | local re = require'relabel' | 189 | |
190 | local recp = m.P"oast" | ||
191 | |||
192 | local 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 | |||
198 | print(g:match("test.")) --> 6 | ||
199 | print(g:match("toast.")) --> 7 | ||
200 | print(g:match("oast.")) --> nil 0 oast. | ||
201 | print(g:match("toward.")) --> nil 0 ward. | ||
202 | ``` | ||
203 | When 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 | ||
205 | is thrown, with the associated inpux suffix 'oast.'. In rule | ||
206 | `S` label 42 is caught and the recovery pattern matches 'oast', | ||
207 | so pattern `'.'` matches the rest of the input. | ||
208 | |||
209 | When matching subject 'oast.', pattern `m.P"t"` fails, and | ||
210 | the result of the matching is <b>nil, 0, oast.</b>. | ||
211 | |||
212 | When matching 'toward.', label 42 is thrown after matching 't', | ||
213 | with the associated input suffix 'oward.'. As the matching of the | ||
214 | recovery pattern fails, the result is <b>nil, 0, ward.</b>. | ||
215 | |||
216 | Usually, the recovery pattern is an expression that does not fail. | ||
217 | In the previous example, we could have used `(m.P(1) - m.P".")^0` | ||
218 | as the recovery pattern. | ||
219 | |||
220 | Below we rewrite the grammar that describes a list of identifiers | ||
221 | to use a recovery strategy. Grammar `g` remains the same, but we add a | ||
222 | recovery grammar `grec` that handles the labels thrown by `g`. | ||
223 | |||
224 | In grammar `grec` we use functions `record` and `sync`. | ||
225 | Function `record`, plus function `recorderror`, will help | ||
226 | us to save the input position where a label was thrown, | ||
227 | while function `sync` will give us a synchronization pattern, | ||
228 | that consumes the input while is not possible to match a given | ||
229 | pattern `p`. | ||
230 | |||
231 | When the matching of an identifier fails, a defaul value ('NONE') | ||
232 | is provided. | ||
233 | |||
234 | ```lua | ||
235 | local m = require'lpeglabelrec' | ||
236 | local re = require'relabelrec' | ||
190 | 237 | ||
191 | local terror = {} | 238 | local terror = {} |
192 | 239 | ||
@@ -199,73 +246,88 @@ local errUndef = newError("undefined") | |||
199 | local errId = newError("expecting an identifier") | 246 | local errId = newError("expecting an identifier") |
200 | local errComma = newError("expecting ','") | 247 | local errComma = newError("expecting ','") |
201 | 248 | ||
249 | local id = m.R'az'^1 | ||
250 | |||
202 | local g = m.P{ | 251 | local 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 | ||
211 | function mymatch (g, s) | 260 | local 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 | ||
219 | end | ||
220 | |||
221 | print(mymatch(g, "one,two")) --> 8 | ||
222 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | ||
223 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | ||
224 | ``` | ||
225 | 261 | ||
262 | function recorderror(pos, lab) | ||
263 | local line, col = re.calcline(subject, pos) | ||
264 | table.insert(errors, { line = line, col = col, msg = terror[lab] }) | ||
265 | end | ||
226 | 266 | ||
227 | ##### *relabel* syntax | 267 | function record (lab) |
268 | return (m.Cp() * m.Cc(lab)) / recorderror | ||
269 | end | ||
228 | 270 | ||
229 | Now we rewrite the previous example using the syntax | 271 | function sync (p) |
230 | supported by *relabel*: | 272 | return (-p * m.P(1))^0 |
273 | end | ||
231 | 274 | ||
232 | ```lua | 275 | local grec = m.P{ |
233 | local 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 | ||
235 | local 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 | ||
243 | function mymatch (g, s) | 283 | function 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 |
257 | end | 296 | end |
258 | 297 | ||
259 | print(mymatch(g, "one,two")) --> 8 | 298 | print(mymatch(grec, "one,two")) |
260 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | 299 | -- Captures (separated by ';'): one; two; |
261 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | 300 | -- Syntactic errors found: 0 |
301 | |||
302 | print(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 | |||
308 | print(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 | |||
315 | print(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 | ||
264 | With the help of function *setlabels* we can also rewrite the previous example to use | 323 | ##### *relabelrec* syntax |
265 | mnemonic labels instead of plain numbers: | 324 | |
325 | Below we describe again a grammar that matches a list of identifiers, | ||
326 | now using the syntax supported by *relabelrec*, where `//{}` is the | ||
327 | recovery operator, and `%{}` is the throw operator: | ||
266 | 328 | ||
267 | ```lua | 329 | ```lua |
268 | local re = require 'relabel' | 330 | local re = require 'relabelrec' |
269 | 331 | ||
270 | local errinfo = { | 332 | local errinfo = { |
271 | {"errUndef", "undefined"}, | 333 | {"errUndef", "undefined"}, |
@@ -285,59 +347,124 @@ re.setlabels(labels) | |||
285 | 347 | ||
286 | local g = re.compile[[ | 348 | local 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 | ||
356 | local errors | ||
357 | |||
358 | function 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 | ||
362 | end | ||
363 | |||
364 | function sync (p) | ||
365 | return '( !(' .. p .. ') .)*' | ||
366 | end | ||
367 | |||
368 | local 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 | |||
294 | function mymatch (g, s) | 375 | function 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 |
302 | end | 396 | end |
303 | 397 | ||
304 | print(mymatch(g, "one,two")) --> 8 | 398 | print(mymatch(grec, "one,two")) |
305 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | 399 | -- Captures (separated by ';'): one; two; |
306 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | 400 | -- Syntactic errors found: 0 |
401 | |||
402 | print(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 | |||
408 | print(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 | |||
415 | print(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 | ||
311 | Here's an example of an LPegLabel grammar that make its own function called | 426 | Here'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 | 427 | We have used a function `expect`, that takes a pattern `patt` and a label as |
313 | if the pattern fails to be matched. This function can be extended later on to | 428 | parameters and builds a new pattern that throws this label when `patt` |
314 | record all errors encountered once error recovery is implemented. | 429 | fails. |
315 | 430 | ||
316 | ```lua | 431 | When a subexpression is syntactically invalid, a default value of 1000 |
317 | local lpeg = require"lpeglabel" | 432 | is provided by the recovery pattern, so the evaluation of an expression |
433 | should always produce a numeric value. | ||
318 | 434 | ||
319 | local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T | 435 | In this example, we can see that it may be a tedious and error prone |
436 | task to build manually the recovery grammar `grec`. In the next example | ||
437 | we will show how to build the recovery grammar in a more automatic way. | ||
438 | |||
439 | ```lua | ||
440 | local m = require"lpeglabelrec" | ||
441 | local re = require"relabelrec" | ||
320 | 442 | ||
321 | local labels = { | 443 | local 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 | ||
329 | local function expect(patt, labname) | 449 | local 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) |
337 | end | 456 | end |
338 | 457 | ||
339 | local num = R("09")^1 / tonumber | 458 | local errors, subject |
340 | local op = S("+-*/") | 459 | |
460 | local function expect(patt, labname) | ||
461 | local i = labelindex(labname) | ||
462 | return patt + m.T(i) | ||
463 | end | ||
464 | |||
465 | |||
466 | local num = m.R("09")^1 / tonumber | ||
467 | local op = m.S("+-") | ||
341 | 468 | ||
342 | local function compute(tokens) | 469 | local 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 |
358 | end | 481 | end |
359 | 482 | ||
360 | local g = P { | 483 | local 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 | ||
367 | g = expect(g, "NoExp") * expect(-P(1), "Extra") | 492 | function recorderror(pos, lab) |
493 | local line, col = re.calcline(subject, pos) | ||
494 | table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) | ||
495 | end | ||
368 | 496 | ||
497 | function record (labname) | ||
498 | return (m.Cp() * m.Cc(labelindex(labname))) / recorderror | ||
499 | end | ||
500 | |||
501 | function sync (p) | ||
502 | return (-p * m.P(1))^0 | ||
503 | end | ||
504 | |||
505 | function defaultValue (p) | ||
506 | return p or m.Cc(1000) | ||
507 | end | ||
508 | |||
509 | local 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 | |||
369 | local function eval(input) | 519 | local 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 | ||
378 | end | 536 | end |
379 | 537 | ||
380 | print(eval "98-76*(54/32)") | 538 | print(eval "90-70-(5)+3") |
381 | --> 37.125 | 539 | -- Syntactic errors found: 0 |
540 | -- Result = 18 | ||
541 | |||
542 | print(eval "15+") | ||
543 | -- Syntactic errors found: 1 | ||
544 | -- syntax error: expected a term after the operator (at index 3) | ||
545 | -- Result = 1015 | ||
546 | |||
547 | print(eval "-2") | ||
548 | -- Syntactic errors found: 1 | ||
549 | -- syntax error: expected an expression (at index 1) | ||
550 | -- Result = 998 | ||
551 | |||
552 | print(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 | |||
558 | print(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 | |||
564 | print(eval "3)") | ||
565 | -- Syntactic errors found: 0 | ||
566 | -- Result = 3 | ||
567 | ``` | ||
382 | 568 | ||
383 | print(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 | ||
386 | print(eval "(1+)-1*(2/2)") | 571 | Below we rewrite the previous example to automatically |
387 | --> syntax error: expected a term after the operator (at index 4) | 572 | build the recovery grammar based on information provided |
573 | by the user for each label (error message, recovery pattern, etc). | ||
574 | In the example below we also throw an error when the grammar | ||
575 | does not match the whole subject. | ||
388 | 576 | ||
389 | print(eval "(1+1)-1*(/2)") | 577 | ```lua |
390 | --> syntax error: expected an expression after the parenthesis (at index 10) | 578 | local m = require"lpeglabelrec" |
579 | local re = require"relabelrec" | ||
391 | 580 | ||
392 | print(eval "1+(1-(1*2))/2x") | 581 | local num = m.R("09")^1 / tonumber |
393 | --> syntax error: extra chracters found after the expression (at index 14) | 582 | local op = m.S("+-") |
394 | 583 | ||
395 | print(eval "-1+(1-(1*2))/2") | 584 | local labels = {} |
396 | --> syntax error: no expression found (at index 1) | 585 | local nlabels = 0 |
397 | ``` | ||
398 | 586 | ||
399 | #### Catching labels | 587 | local 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 } | ||
592 | end | ||
400 | 593 | ||
401 | When a label is thrown, the grammar itself can handle this label | 594 | newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000)) |
402 | by using the labeled ordered choice. Below we rewrite the example | 595 | newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000)) |
403 | of the list of identifiers to show this feature: | 596 | newError("MisClose", "missing a closing ')' after the expression", m.P")") |
597 | newError("Extra", "extra characters found after the expression") | ||
404 | 598 | ||
599 | local errors, subject | ||
405 | 600 | ||
406 | ```lua | 601 | local function expect(patt, labname) |
407 | local m = require'lpeglabel' | 602 | local i = labels[labname].id |
603 | return patt + m.T(i) | ||
604 | end | ||
408 | 605 | ||
409 | local terror = {} | 606 | local 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 | ||
618 | end | ||
410 | 619 | ||
411 | local function newError(s) | 620 | local 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 | |||
629 | function recorderror(pos, lab) | ||
630 | local line, col = re.calcline(subject, pos) | ||
631 | table.insert(errors, { line = line, col = col, msg = labels[lab].msg }) | ||
414 | end | 632 | end |
415 | 633 | ||
416 | local errUndef = newError("undefined") | 634 | function record (labname) |
417 | local errId = newError("expecting an identifier") | 635 | return (m.Cp() * m.Cc(labname)) / recorderror |
418 | local errComma = newError("expecting ','") | 636 | end |
419 | 637 | ||
420 | local g = m.P{ | 638 | function 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), | 640 | end |
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", | 642 | function defaultValue (p) |
425 | Id = m.V"Sp" * m.R'az'^1, | 643 | return p or m.Cc(1000) |
426 | Comma = m.V"Sp" * ",", | 644 | end |
427 | Sp = m.S" \n\t"^0, | 645 | |
428 | ErrId = m.Cc(errId) / terror, | 646 | local grec = g * expect(m.P(-1), "Extra") |
429 | ErrComma = m.Cc(errComma) / terror | 647 | for k, v in pairs(labels) do |
430 | } | 648 | grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id) |
649 | end | ||
650 | |||
651 | local 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 | ||
668 | end | ||
431 | 669 | ||
432 | print(m.match(g, "one,two")) --> 8 | 670 | print(eval "90-70-(5)+3") |
433 | print(m.match(g, "one two")) --> expecting ',' | 671 | -- Syntactic errors found: 0 |
434 | print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier | 672 | -- Result = 18 |
673 | |||
674 | print(eval "15+") | ||
675 | -- Syntactic errors found: 1 | ||
676 | -- syntax error: expected a term after the operator (at index 3) | ||
677 | -- Result = 1015 | ||
678 | |||
679 | print(eval "-2") | ||
680 | -- Syntactic errors found: 1 | ||
681 | -- syntax error: expected an expression (at index 1) | ||
682 | -- Result = 998 | ||
683 | |||
684 | print(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 | |||
690 | print(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 | |||
696 | print(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 @@ | |||
1 | local m = require"lpeglabelrec" | ||
2 | local re = require"relabelrec" | ||
3 | |||
4 | local labels = { | ||
5 | {"ExpTermFirst", "expected an expression"}, | ||
6 | {"ExpTermOp", "expected a term after the operator"}, | ||
7 | {"MisClose", "missing a closing ')' after the expression"}, | ||
8 | } | ||
9 | |||
10 | local 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) | ||
17 | end | ||
18 | |||
19 | local errors, subject | ||
20 | |||
21 | local function expect(patt, labname) | ||
22 | local i = labelindex(labname) | ||
23 | return patt + m.T(i) | ||
24 | end | ||
25 | |||
26 | |||
27 | local num = m.R("09")^1 / tonumber | ||
28 | local op = m.S("+-") | ||
29 | |||
30 | local 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 | ||
42 | end | ||
43 | |||
44 | local 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 | |||
53 | function recorderror(pos, lab) | ||
54 | local line, col = re.calcline(subject, pos) | ||
55 | table.insert(errors, { line = line, col = col, msg = labels[lab][2] }) | ||
56 | end | ||
57 | |||
58 | function record (labname) | ||
59 | return (m.Cp() * m.Cc(labelindex(labname))) / recorderror | ||
60 | end | ||
61 | |||
62 | function sync (p) | ||
63 | return (-p * m.P(1))^0 | ||
64 | end | ||
65 | |||
66 | function defaultValue (p) | ||
67 | return p or m.Cc(1000) | ||
68 | end | ||
69 | |||
70 | local 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 | |||
80 | local 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 | ||
97 | end | ||
98 | |||
99 | print(eval "90-70-(5)+3") | ||
100 | --> 20 | ||
101 | |||
102 | print(eval "15+") | ||
103 | --> 2 + 0 | ||
104 | |||
105 | print(eval "-2") | ||
106 | --> 0 - 2 | ||
107 | |||
108 | print(eval "1+3+-9") | ||
109 | --> 1 + 3 + [0] - 9 | ||
110 | |||
111 | print(eval "1+()3+") | ||
112 | --> 1 + ([0]) [3 +] [0] | ||
113 | |||
114 | print(eval "8-(2+)-5") | ||
115 | --> 8 - (2 + [0]) - 5 | ||
116 | |||
117 | print(eval "()") | ||
118 | |||
119 | print(eval "") | ||
120 | |||
121 | print(eval "1+()+") | ||
122 | |||
123 | print(eval "1+(") | ||
124 | |||
125 | print(eval "3)") | ||
126 | |||
127 | print(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 @@ | |||
1 | local m = require"lpeglabelrec" | ||
2 | local re = require"relabelrec" | ||
3 | |||
4 | local num = m.R("09")^1 / tonumber | ||
5 | local op = m.S("+-") | ||
6 | |||
7 | local labels = {} | ||
8 | local nlabels = 0 | ||
9 | |||
10 | local 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 } | ||
15 | end | ||
16 | |||
17 | newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000)) | ||
18 | newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000)) | ||
19 | newError("MisClose", "missing a closing ')' after the expression", m.P")") | ||
20 | newError("Extra", "extra characters found after the expression") | ||
21 | |||
22 | local errors, subject | ||
23 | |||
24 | local function expect(patt, labname) | ||
25 | local i = labels[labname].id | ||
26 | return patt + m.T(i) | ||
27 | end | ||
28 | |||
29 | local 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 | ||
41 | end | ||
42 | |||
43 | local 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 | |||
52 | function recorderror(pos, lab) | ||
53 | local line, col = re.calcline(subject, pos) | ||
54 | table.insert(errors, { line = line, col = col, msg = labels[lab].msg }) | ||
55 | end | ||
56 | |||
57 | function record (labname) | ||
58 | return (m.Cp() * m.Cc(labname)) / recorderror | ||
59 | end | ||
60 | |||
61 | function sync (p) | ||
62 | return (-p * m.P(1))^0 | ||
63 | end | ||
64 | |||
65 | function defaultValue (p) | ||
66 | return p or m.Cc(1000) | ||
67 | end | ||
68 | |||
69 | local grec = g * expect(m.P(-1), "Extra") | ||
70 | for k, v in pairs(labels) do | ||
71 | grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id) | ||
72 | end | ||
73 | |||
74 | local 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 | ||
91 | end | ||
92 | |||
93 | print(eval "90-70-(5)+3") | ||
94 | --> 18 | ||
95 | |||
96 | print(eval "15+") | ||
97 | --> 2 + 0 | ||
98 | |||
99 | print(eval "-2") | ||
100 | --> 0 - 2 | ||
101 | |||
102 | print(eval "1+3+-9") | ||
103 | --> 1 + 3 + [0] - 9 | ||
104 | |||
105 | print(eval "1+()3+") | ||
106 | --> 1 + ([0]) [+] 3 + [0] | ||
107 | |||
108 | print(eval "8-(2+)-5") | ||
109 | --> 8 - (2 + [0]) - 5 | ||
110 | |||
111 | print(eval "()") | ||
112 | |||
113 | print(eval "") | ||
114 | |||
115 | print(eval "1+()+") | ||
116 | |||
117 | print(eval "1+(") | ||
118 | |||
119 | print(eval "3)") | ||
120 | |||
121 | print(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 @@ | |||
1 | local lpeg = require"lpeglabel" | ||
2 | |||
3 | local 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. | ||
8 | local 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. | ||
21 | local 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) | ||
29 | end | ||
30 | |||
31 | local num = R("09")^1 / tonumber | ||
32 | local 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). | ||
37 | local 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 | ||
53 | end | ||
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. | ||
58 | local 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 | |||
65 | g = 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. | ||
71 | local 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 | ||
80 | end | ||
81 | |||
82 | print(eval "98-76*(54/32)") | ||
83 | --> 37.125 | ||
84 | |||
85 | print(eval "(1+1-1*2/2") | ||
86 | --> syntax error: missing a closing ')' after the expression (at index 11) | ||
87 | |||
88 | print(eval "(1+)-1*(2/2)") | ||
89 | --> syntax error: expected a term after the operator (at index 4) | ||
90 | |||
91 | print(eval "(1+1)-1*(/2)") | ||
92 | --> syntax error: expected an expression after the parenthesis (at index 10) | ||
93 | |||
94 | print(eval "1+(1-(1*2))/2x") | ||
95 | --> syntax error: extra chracters found after the expression (at index 14) | ||
96 | |||
97 | print(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 @@ | |||
1 | local m = require'lpeglabel' | 1 | local m = require'lpeglabelrec' |
2 | local re = require'relabel' | 2 | local re = require'relabelrec' |
3 | |||
4 | local id = m.R'az'^1 | ||
3 | 5 | ||
4 | local g = m.P{ | 6 | local g = m.P{ |
5 | "S", | 7 | "S", |
6 | S = m.V"Id" * m.V"List", | 8 | S = m.V"Id" * m.V"List", |
7 | List = -m.P(1) + (m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)) * m.V"List", | 9 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", |
8 | Id = m.V"Sp" * m.R'az'^1, | 10 | Id = m.V"Sp" * id + m.T(1), |
9 | Comma = m.V"Sp" * ",", | 11 | Comma = m.V"Sp" * "," + m.T(2), |
10 | Sp = m.S" \n\t"^0, | 12 | Sp = m.S" \n\t"^0, |
11 | } | 13 | } |
12 | 14 | ||
diff --git a/examples/listId2.lua b/examples/listId2.lua index 509fda4..46f0063 100644 --- a/examples/listId2.lua +++ b/examples/listId2.lua | |||
@@ -1,5 +1,5 @@ | |||
1 | local m = require'lpeglabel' | 1 | local m = require'lpeglabelrec' |
2 | local re = require'relabel' | 2 | local re = require'relabelrec' |
3 | 3 | ||
4 | local terror = {} | 4 | local terror = {} |
5 | 5 | ||
@@ -12,15 +12,18 @@ local errUndef = newError("undefined") | |||
12 | local errId = newError("expecting an identifier") | 12 | local errId = newError("expecting an identifier") |
13 | local errComma = newError("expecting ','") | 13 | local errComma = newError("expecting ','") |
14 | 14 | ||
15 | local id = m.R'az'^1 | ||
16 | |||
15 | local g = m.P{ | 17 | local g = m.P{ |
16 | "S", | 18 | "S", |
17 | S = m.V"Id" * m.V"List", | 19 | S = m.V"Id" * m.V"List", |
18 | List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", | 20 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", |
19 | Id = m.V"Sp" * m.R'az'^1, | 21 | Id = m.V"Sp" * id + m.T(errId), |
20 | Comma = m.V"Sp" * ",", | 22 | Comma = m.V"Sp" * "," + m.T(errComma), |
21 | Sp = m.S" \n\t"^0, | 23 | Sp = m.S" \n\t"^0, |
22 | } | 24 | } |
23 | 25 | ||
26 | |||
24 | function mymatch (g, s) | 27 | function mymatch (g, s) |
25 | local r, e, sfail = g:match(s) | 28 | local r, e, sfail = g:match(s) |
26 | if not r then | 29 | if not r then |
diff --git a/examples/listId2Rec2.lua b/examples/listId2Rec2.lua new file mode 100644 index 0000000..c6705dd --- /dev/null +++ b/examples/listId2Rec2.lua | |||
@@ -0,0 +1,67 @@ | |||
1 | local m = require'lpeglabelrec' | ||
2 | local re = require'relabelrec' | ||
3 | |||
4 | local terror = {} | ||
5 | |||
6 | local function newError(s) | ||
7 | table.insert(terror, s) | ||
8 | return #terror | ||
9 | end | ||
10 | |||
11 | local errUndef = newError("undefined") | ||
12 | local errId = newError("expecting an identifier") | ||
13 | local errComma = newError("expecting ','") | ||
14 | |||
15 | local id = m.R'az'^1 | ||
16 | |||
17 | local g = m.P{ | ||
18 | "S", | ||
19 | S = m.V"Id" * m.V"List", | ||
20 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", | ||
21 | Id = m.V"Sp" * id + m.T(errId), | ||
22 | Comma = m.V"Sp" * "," + m.T(errComma), | ||
23 | Sp = m.S" \n\t"^0, | ||
24 | } | ||
25 | |||
26 | local subject, errors | ||
27 | |||
28 | function recorderror(pos, lab) | ||
29 | local line, col = re.calcline(subject, pos) | ||
30 | table.insert(errors, { line = line, col = col, msg = terror[lab] }) | ||
31 | end | ||
32 | |||
33 | function record (lab) | ||
34 | return (m.Cp() * m.Cc(lab)) / recorderror | ||
35 | end | ||
36 | |||
37 | function sync (p) | ||
38 | return (-p * m.P(1))^0 | ||
39 | end | ||
40 | |||
41 | local grec = m.P{ | ||
42 | "S", | ||
43 | S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId), | ||
44 | ErrComma = record(errComma) * sync(id), | ||
45 | ErrId = record(errId) * sync(m.P",") | ||
46 | } | ||
47 | |||
48 | |||
49 | function mymatch (g, s) | ||
50 | errors = {} | ||
51 | subject = s | ||
52 | local r, e, sfail = g:match(s) | ||
53 | if #errors > 0 then | ||
54 | local out = {} | ||
55 | for i, err in ipairs(errors) do | ||
56 | local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg | ||
57 | table.insert(out, msg) | ||
58 | end | ||
59 | return nil, table.concat(out, "\n") .. "\n" | ||
60 | end | ||
61 | return r | ||
62 | end | ||
63 | |||
64 | print(mymatch(grec, "one,two")) | ||
65 | print(mymatch(grec, "one two three")) | ||
66 | print(mymatch(grec, "1,\n two, \n3,")) | ||
67 | print(mymatch(grec, "one\n two123, \nthree,")) | ||
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 @@ | |||
1 | local m = require'lpeglabelrec' | ||
2 | local re = require'relabelrec' | ||
3 | |||
4 | local terror = {} | ||
5 | |||
6 | local function newError(s) | ||
7 | table.insert(terror, s) | ||
8 | return #terror | ||
9 | end | ||
10 | |||
11 | local errUndef = newError("undefined") | ||
12 | local errId = newError("expecting an identifier") | ||
13 | local errComma = newError("expecting ','") | ||
14 | |||
15 | local id = m.R'az'^1 | ||
16 | |||
17 | local g = m.P{ | ||
18 | "S", | ||
19 | S = m.V"Id" * m.V"List", | ||
20 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", | ||
21 | Id = m.V"Sp" * m.C(id) + m.T(errId), | ||
22 | Comma = m.V"Sp" * "," + m.T(errComma), | ||
23 | Sp = m.S" \n\t"^0, | ||
24 | } | ||
25 | |||
26 | local subject, errors | ||
27 | |||
28 | function recorderror(pos, lab) | ||
29 | local line, col = re.calcline(subject, pos) | ||
30 | table.insert(errors, { line = line, col = col, msg = terror[lab] }) | ||
31 | end | ||
32 | |||
33 | function record (lab) | ||
34 | return (m.Cp() * m.Cc(lab)) / recorderror | ||
35 | end | ||
36 | |||
37 | function sync (p) | ||
38 | return (-p * m.P(1))^0 | ||
39 | end | ||
40 | |||
41 | function defaultValue () | ||
42 | return m.Cc"NONE" | ||
43 | end | ||
44 | |||
45 | local 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 | |||
53 | function 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 | ||
74 | end | ||
75 | |||
76 | mymatch(grec, "one,two") | ||
77 | mymatch(grec, "one two three") | ||
78 | mymatch(grec, "1,\n two, \n3,") | ||
79 | mymatch(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 @@ | |||
1 | local m = require'lpeglabel' | ||
2 | |||
3 | local terror = {} | ||
4 | |||
5 | local function newError(s) | ||
6 | table.insert(terror, s) | ||
7 | return #terror | ||
8 | end | ||
9 | |||
10 | local errUndef = newError("undefined") | ||
11 | local errId = newError("expecting an identifier") | ||
12 | local errComma = newError("expecting ','") | ||
13 | |||
14 | local 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 | |||
26 | print(m.match(g, "one,two")) | ||
27 | print(m.match(g, "one two")) | ||
28 | print(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 @@ | |||
1 | local re = require'relabel' | ||
2 | |||
3 | local terror = {} | ||
4 | |||
5 | local function newError(l, msg) | ||
6 | table.insert(terror, { l = l, msg = msg } ) | ||
7 | end | ||
8 | |||
9 | newError("errId", "Error: expecting an identifier") | ||
10 | newError("errComma", "Error: expecting ','") | ||
11 | |||
12 | local labelCode = {} | ||
13 | local labelMsg = {} | ||
14 | for k, v in ipairs(terror) do | ||
15 | labelCode[v.l] = k | ||
16 | labelMsg[v.l] = v.msg | ||
17 | end | ||
18 | |||
19 | re.setlabels(labelCode) | ||
20 | |||
21 | local 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 | |||
30 | print(p:match("a,b")) | ||
31 | print(p:match("a b")) | ||
32 | print(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 @@ | |||
1 | local re = require 'relabel' | 1 | local re = require 'relabelrec' |
2 | 2 | ||
3 | local g = re.compile[[ | 3 | local 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 @@ | |||
1 | local re = require 'relabel' | 1 | local re = require 'relabelrec' |
2 | 2 | ||
3 | local errinfo = { | 3 | local errinfo = { |
4 | {"errUndef", "undefined"}, | 4 | {"errUndef", "undefined"}, |
@@ -18,23 +18,54 @@ re.setlabels(labels) | |||
18 | 18 | ||
19 | local g = re.compile[[ | 19 | local 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 | ||
27 | function mymatch (g, s) | 27 | local errors |
28 | local r, e, sfail = g:match(s) | 28 | |
29 | if not r then | 29 | function 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 | 33 | end |
34 | return r | 34 | |
35 | function sync (p) | ||
36 | return '( !(' .. p .. ') .)*' | ||
35 | end | 37 | end |
36 | 38 | ||
37 | print(mymatch(g, "one,two")) | 39 | local grec = re.compile( |
38 | print(mymatch(g, "one two")) | 40 | "S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" .. |
39 | print(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 | ||
45 | function 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 | ||
66 | end | ||
67 | |||
68 | mymatch(grec, "one,two") | ||
69 | mymatch(grec, "one two three") | ||
70 | mymatch(grec, "1,\n two, \n3,") | ||
71 | mymatch(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 @@ | |||
1 | local lpeg = require"lpeglabel" | ||
2 | |||
3 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | ||
4 | local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt | ||
5 | local 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. | ||
10 | local 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. | ||
21 | local 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) | ||
28 | end | ||
29 | |||
30 | -- The `errors` table will hold the list of errors recorded during parsing | ||
31 | local 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. | ||
37 | local 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) | ||
44 | end | ||
45 | |||
46 | local num = R("09")^1 / tonumber | ||
47 | local 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). | ||
52 | local 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 | ||
68 | end | ||
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. | ||
75 | local 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 | |||
90 | g = 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. | ||
96 | local 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 | ||
110 | end | ||
111 | |||
112 | print(eval "98-76*(54/32)") | ||
113 | --> 37.125 | ||
114 | |||
115 | print(eval "(1+1-1*2/2") | ||
116 | --> syntax error: missing a closing ')' after the expression (at index 11) | ||
117 | |||
118 | print(eval "(1+)-1*(2/2)") | ||
119 | --> syntax error: expected a term after the operator (at index 4) | ||
120 | |||
121 | print(eval "(1+1)-1*(/2)") | ||
122 | --> syntax error: expected an expression after the parenthesis (at index 10) | ||
123 | |||
124 | print(eval "1+(1-(1*2))/2x") | ||
125 | --> syntax error: extra chracters found after the expression (at index 14) | ||
126 | |||
127 | print(eval "-1+(1-(1*2))/2") | ||
128 | --> syntax error: no expression found (at index 1) | ||
129 | |||
130 | print(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 @@ | |||
1 | local lpeg = require"lpeglabel" | ||
2 | |||
3 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | ||
4 | local C, Cc, Ct, Cmt, Carg = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt, lpeg.Carg | ||
5 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec | ||
6 | |||
7 | local 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 | |||
15 | local 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) | ||
22 | end | ||
23 | |||
24 | local 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) | ||
32 | end | ||
33 | |||
34 | local num = R("09")^1 / tonumber | ||
35 | local op = S("+-*/") | ||
36 | |||
37 | local 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 | ||
53 | end | ||
54 | |||
55 | |||
56 | local 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 | |||
65 | g = expect(g, "NoExp", P(1)^0) * expect(-P(1), "Extra") | ||
66 | |||
67 | local 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 | ||
81 | end | ||
82 | |||
83 | print(eval "98-76*(54/32)") | ||
84 | --> 37.125 | ||
85 | |||
86 | print(eval "(1+1-1*2/2") | ||
87 | --> syntax error: missing a closing ')' after the expression (at index 11) | ||
88 | |||
89 | print(eval "(1+)-1*(2/2)") | ||
90 | --> syntax error: expected a term after the operator (at index 4) | ||
91 | |||
92 | print(eval "(1+1)-1*(/2)") | ||
93 | --> syntax error: expected an expression after the parenthesis (at index 10) | ||
94 | |||
95 | print(eval "1+(1-(1*2))/2x") | ||
96 | --> syntax error: extra chracters found after the expression (at index 14) | ||
97 | |||
98 | print(eval "-1+(1-(1*2))/2") | ||
99 | --> syntax error: no expression found (at index 1) | ||
100 | |||
101 | print(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 @@ | |||
1 | local lpeg = require"lpeglabel" | ||
2 | |||
3 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | ||
4 | local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt | ||
5 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec | ||
6 | |||
7 | local 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 | |||
15 | local 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) | ||
22 | end | ||
23 | |||
24 | local errors = {} | ||
25 | |||
26 | local 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) | ||
33 | end | ||
34 | |||
35 | local num = R("09")^1 / tonumber | ||
36 | local op = S("+-*/") | ||
37 | |||
38 | local 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 | ||
54 | end | ||
55 | |||
56 | |||
57 | local 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 | |||
67 | g = expect(g, "NoExp") * expect(-P(1), "Extra") | ||
68 | |||
69 | local 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 | ||
83 | end | ||
84 | |||
85 | print(eval "98-76*(54/32)") | ||
86 | --> 37.125 | ||
87 | |||
88 | print(eval "(1+1-1*2/2") | ||
89 | --> syntax error: missing a closing ')' after the expression (at index 11) | ||
90 | |||
91 | print(eval "(1+)-1*(2/2)") | ||
92 | --> syntax error: expected a term after the operator (at index 4) | ||
93 | |||
94 | print(eval "(1+1)-1*(/2)") | ||
95 | --> syntax error: expected an expression after the parenthesis (at index 10) | ||
96 | |||
97 | print(eval "1+(1-(1*2))/2x") | ||
98 | --> syntax error: extra chracters found after the expression (at index 14) | ||
99 | |||
100 | print(eval "-1+(1-(1*2))/2") | ||
101 | --> syntax error: no expression found (at index 1) | ||
102 | |||
103 | print(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 @@ | |||
1 | local re = require 'relabel' | 1 | local re = require 'relabelrec' |
2 | 2 | ||
3 | local terror = {} | 3 | local terror = {} |
4 | 4 | ||
@@ -25,21 +25,6 @@ newError("errFactor", "Error: expected '(', ID, or number after '*' or '/'") | |||
25 | newError("errExpFac", "Error: expected expression after '('") | 25 | newError("errExpFac", "Error: expected expression after '('") |
26 | newError("errClosePar", "Error: expected ')' after expression") | 26 | newError("errClosePar", "Error: expected ')' after expression") |
27 | 27 | ||
28 | local line | ||
29 | |||
30 | local function incLine() | ||
31 | line = line + 1 | ||
32 | return true | ||
33 | end | ||
34 | |||
35 | local 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 | ||
42 | end | ||
43 | 28 | ||
44 | local labelCode = {} | 29 | local labelCode = {} |
45 | for k, v in ipairs(terror) do | 30 | for k, v in ipairs(terror) do |
@@ -48,7 +33,7 @@ end | |||
48 | 33 | ||
49 | re.setlabels(labelCode) | 34 | re.setlabels(labelCode) |
50 | 35 | ||
51 | local g = re.compile([[ | 36 | local 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 | ||
108 | local function printError(n, e) | 92 | local 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 | ||
111 | end | 100 | end |
112 | 101 | ||
113 | local s = [[ | 102 | local s = [[ |
@@ -118,7 +107,7 @@ repeat | |||
118 | n := n - 1 | 107 | n := n - 1 |
119 | until (n < 1); | 108 | until (n < 1); |
120 | write f;]] | 109 | write f;]] |
121 | printError(g:match(s)) | 110 | print(mymatch(g, s)) |
122 | 111 | ||
123 | s = [[ | 112 | s = [[ |
124 | n := 5; | 113 | n := 5; |
@@ -128,14 +117,14 @@ repeat | |||
128 | n := n - 1; | 117 | n := n - 1; |
129 | until (n < 1); | 118 | until (n < 1); |
130 | read ;]] | 119 | read ;]] |
131 | printError(g:match(s)) | 120 | print(mymatch(g, s)) |
132 | 121 | ||
133 | s = [[ | 122 | s = [[ |
134 | if a < 1 then | 123 | if a < 1 then |
135 | b := 2; | 124 | b := 2; |
136 | else | 125 | else |
137 | b := 3;]] | 126 | b := 3;]] |
138 | printError(g:match(s)) | 127 | print(mymatch(g, s)) |
139 | 128 | ||
140 | s = [[ | 129 | s = [[ |
141 | n := 5; | 130 | n := 5; |
@@ -145,7 +134,7 @@ repeat | |||
145 | n := n - 1; | 134 | n := n - 1; |
146 | untill (n < 1); | 135 | untill (n < 1); |
147 | ]] | 136 | ]] |
148 | printError(g:match(s)) | 137 | print(mymatch(g, s)) |
149 | 138 | ||
150 | s = [[ | 139 | s = [[ |
151 | n := 5; | 140 | n := 5; |
@@ -155,9 +144,8 @@ repeat | |||
155 | n := n - 1; | 144 | n := n - 1; |
156 | 3 (n < 1); | 145 | 3 (n < 1); |
157 | ]] | 146 | ]] |
158 | printError(g:match(s)) | 147 | print(mymatch(g, s)) |
159 | |||
160 | printError(g:match("a : 2")) | ||
161 | printError(g:match("a := (2")) | ||
162 | 148 | ||
149 | print(mymatch(g, "a : 2")) | ||
150 | print(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 | ||
403 | s = [=[ | 403 | s = [=[ |
404 | --[[ testing | 404 | --[[ |
405 | |||
406 | testing | ||
405 | unfinished | 407 | unfinished |
408 | |||
406 | comment | 409 | comment |
407 | ]=] | 410 | ]=] |
408 | --[=[ | 411 | --[=[ |
409 | test.lua:3:1: syntax error, unexpected 'comment', expecting '=', ',', 'String', '{', '(', ':', '[', '.' | 412 | test.lua:3:1: syntax error, unexpected 'comment', expecting '=', ',', 'String', '{', '(', ':', '[', '.' |
410 | ]=] | 413 | ]=] |
411 | e = [=[ | 414 | e = [=[ |
412 | test.lua:1:1: unfinished long comment | 415 | test.lua:1:2: unfinished long comment |
413 | ]=] | 416 | ]=] |
414 | 417 | ||
415 | r, m = parse(s) | 418 | r, 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 @@ | |||
1 | local tllexer = {} | 1 | local tllexer = {} |
2 | 2 | ||
3 | local lpeg = require "lpeglabel" | 3 | local lpeg = require "lpeglabelrec" |
4 | lpeg.locale(lpeg) | 4 | lpeg.locale(lpeg) |
5 | 5 | ||
6 | local tlerror = require "tlerror" | 6 | local 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]) |
10 | end | 10 | end |
11 | 11 | ||
12 | function tllexer.catch (pat, label) | ||
13 | return lpeg.Lc(pat, lpeg.P(false), tlerror.labels[label]) | ||
14 | end | ||
15 | |||
16 | local function setffp (s, i, t, n) | 12 | local 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"), | |||
45 | local LongString = Open * (lpeg.P(1) - CloseEQ)^0 * tllexer.try(Close, "LongString") / | 41 | local 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 | ||
48 | local Comment = lpeg.Lc(lpeg.P("--") * LongString / function () return end, | 44 | local LongStringCm1 = Open * (lpeg.P(1) - CloseEQ)^0 * Close / |
45 | function (s, o) return s end | ||
46 | |||
47 | local 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 @@ | |||
1 | local tlparser = {} | 1 | local tlparser = {} |
2 | 2 | ||
3 | local lpeg = require "lpeglabel" | 3 | local lpeg = require "lpeglabelrec" |
4 | lpeg.locale(lpeg) | 4 | lpeg.locale(lpeg) |
5 | 5 | ||
6 | local tllexer = require "tllexer" | 6 | local tllexer = require "tllexer" |
@@ -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 */ |
710 | static 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 | |||
725 | static void coderecovery (CompileState *compst, TTree *p1, TTree *p2, int opt, | 710 | static 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; |
@@ -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 | } |
@@ -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 | */ |
724 | static int lp_throw (lua_State *L) { | 724 | static 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 | */ |
734 | static 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 | |||
747 | static int lp_recovery (lua_State *L) { | 734 | static 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 | ||
1364 | int luaopen_lpeglabel (lua_State *L); /* labeled failure */ | 1351 | int luaopen_lpeglabelrec (lua_State *L); /* labeled failure */ |
1365 | int luaopen_lpeglabel (lua_State *L) { /* labeled failure */ | 1352 | int 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); |
@@ -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 */ |
@@ -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 */ |
@@ -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 | ||
@@ -1,4 +1,4 @@ | |||
1 | LIBNAME = lpeglabel | 1 | LIBNAME = lpeglabelrec |
2 | LUADIR = ../lua/ | 2 | LUADIR = ../lua/ |
3 | 3 | ||
4 | COPT = -O2 | 4 | COPT = -O2 |
@@ -29,24 +29,24 @@ FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o | |||
29 | 29 | ||
30 | # For Linux | 30 | # For Linux |
31 | linux: | 31 | linux: |
32 | make lpeglabel.so "DLLFLAGS = -shared -fPIC" | 32 | make lpeglabelrec.so "DLLFLAGS = -shared -fPIC" |
33 | 33 | ||
34 | # For Mac OS | 34 | # For Mac OS |
35 | macosx: | 35 | macosx: |
36 | make lpeglabel.so "DLLFLAGS = -bundle -undefined dynamic_lookup" | 36 | make lpeglabelrec.so "DLLFLAGS = -bundle -undefined dynamic_lookup" |
37 | 37 | ||
38 | lpeglabel.so: $(FILES) | 38 | lpeglabelrec.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 | ||
43 | test: test.lua testlabel.lua testerrors.lua relabel.lua lpeglabel.so | 43 | test: 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 | ||
48 | clean: | 48 | clean: |
49 | rm -f $(FILES) lpeglabel.so | 49 | rm -f $(FILES) lpeglabelrec.so |
50 | 50 | ||
51 | 51 | ||
52 | lpcap.o: lpcap.c lpcap.h lptypes.h | 52 | lpcap.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 | |||
6 | local setmetatable = setmetatable | 6 | local setmetatable = setmetatable |
7 | local unpack, tinsert, concat = table.unpack or unpack, table.insert, table.concat | 7 | local unpack, tinsert, concat = table.unpack or unpack, table.insert, table.concat |
8 | local rep = string.rep | 8 | local rep = string.rep |
9 | local m = require"lpeglabel" | 9 | local 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 |
217 | end | 217 | end |
218 | 218 | ||
219 | local function labchoice (...) | 219 | local 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 | ||
233 | local exp = m.P{ "Exp", | 233 | local 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"); |
@@ -4,7 +4,7 @@ | |||
4 | 4 | ||
5 | -- require"strict" -- just to be pedantic | 5 | -- require"strict" -- just to be pedantic |
6 | 6 | ||
7 | local m = require"lpeglabel" | 7 | local 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 | ||
1113 | local re = require "relabel" | 1113 | local re = require "relabelrec" |
1114 | 1114 | ||
1115 | local match, compile = re.match, re.compile | 1115 | local 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 @@ | |||
1 | local m = require 'lpeglabel' | 1 | local m = require 'lpeglabelrec' |
2 | 2 | ||
3 | local p, r, l, s, serror | 3 | local p, r, l, s, serror |
4 | 4 | ||
@@ -11,6 +11,15 @@ local function checkeqlab (x, ...) | |||
11 | end | 11 | end |
12 | end | 12 | end |
13 | 13 | ||
14 | local function checkeq (x, y, p) | ||
15 | if 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 | ||
21 | end | ||
22 | |||
14 | -- throws a label | 23 | -- throws a label |
15 | p = m.T(1) | 24 | p = m.T(1) |
16 | s = "abc" | 25 | s = "abc" |
@@ -32,47 +41,97 @@ local g = m.P{ | |||
32 | r, l, serror = g:match(s) | 41 | r, l, serror = g:match(s) |
33 | assert(r == nil and l == 1 and serror == "abc") | 42 | assert(r == nil and l == 1 and serror == "abc") |
34 | 43 | ||
35 | -- throws a label that is not caught by labeled choice | 44 | |
36 | p = m.Lc(m.T(2), m.P"a", 1, 3) | 45 | -- throws a label that is not caught by the recovery operator |
46 | p = m.Rec(m.T(2), m.P"a", 1, 3) | ||
37 | r, l, serror = p:match(s) | 47 | r, l, serror = p:match(s) |
38 | assert(r == nil and l == 2 and serror == "abc") | 48 | assert(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" | 51 | p = m.Rec(p, m.P"a", 2) |
42 | p = m.Lc(p, m.P"a", 2) | ||
43 | assert(p:match(s) == 2) | 52 | assert(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 |
46 | p = m.Lc(m.T(25), m.P"a", 25) | 55 | p = m.Rec(m.T(25), m.P"a", 25) |
47 | assert(p:match(s) == 2) | 56 | assert(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 |
51 | s = "bola" | 60 | s = "bola" |
52 | r, l, serror = p:match("bola") | 61 | r, l, serror = p:match("bola") |
53 | assert(r == nil and l == 0 and serror == "bola") | 62 | assert(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 |
56 | p = m.Lc(m.P"b", m.P"a", 1) | 65 | p = m.Rec(m.P"b", m.P"a", 1) |
57 | 66 | ||
58 | r, l, serror = p:match("abc") | 67 | r, l, serror = p:match("abc") |
59 | assert(r == nil and l == 0 and serror == "abc") | 68 | assert(r == nil and l == 0 and serror == "abc") |
60 | 69 | ||
61 | assert(p:match("bola") == 2) | 70 | assert(p:match("bola") == 2) |
62 | 71 | ||
63 | -- labeled choice can catch "fail" | 72 | |
64 | p = m.Lc(m.P"b", m.P"a", 0) | 73 | -- recovery operator catches "1" or "3" |
74 | p = m.Rec((m.P"a" + m.T(1)) * m.T(3), (m.P"a" + m.P"b"), 1, 3) | ||
75 | assert(p:match("aac") == 3) | ||
76 | assert(p:match("abc") == 3) | ||
77 | r, l, serror = p:match("acc") | ||
78 | assert(r == nil and l == 0 and serror == "cc") | ||
79 | |||
80 | --throws 1, recovery pattern matches 'b', throw 3, and rec pat mathces 'a' | ||
81 | assert(p:match("bac") == 3) | ||
82 | |||
83 | r, l, serror = p:match("cab") | ||
84 | assert(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" | ||
91 | p = m.Rec(m.Rec(m.P"a" + m.T(1), m.P"b" + m.T(2), 1), m.P"c", 2) | ||
65 | assert(p:match("abc") == 2) | 92 | assert(p:match("abc") == 2) |
66 | assert(p:match("bola") == 2) | 93 | assert(p:match("bac") == 2) |
94 | assert(p:match("cab") == 2) | ||
95 | r, l, serror = p:match("dab") | ||
96 | assert(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 |
70 | p = m.Lc(m.P"a" * m.T(3), (m.P"a" + m.P"b"), 0, 3) | 100 | -- "a" //{1} ("b" //{2} "c") |
101 | p = m.Rec(m.P"a" + m.T(1), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1) | ||
71 | assert(p:match("abc") == 2) | 102 | assert(p:match("abc") == 2) |
72 | assert(p:match("bac") == 2) | 103 | assert(p:match("bac") == 2) |
104 | assert(p:match("cab") == 2) | ||
105 | r, l, serror = p:match("dab") | ||
106 | assert(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" | ||
114 | p = m.Rec(m.Rec(m.P"a" + m.T(2), m.P"b" + m.T(2), 1), m.P"c", 2) | ||
115 | assert(p:match("abc") == 2) | ||
116 | r, l, serror = p:match("bac") | ||
117 | assert(r == nil and l == 0 and serror == "bac") | ||
118 | assert(p:match("cab") == 2) | ||
119 | r, l, serror = p:match("dab") | ||
120 | assert(r == nil and l == 0 and serror == "dab") | ||
73 | 121 | ||
122 | |||
123 | -- righ-associativity | ||
124 | -- "a" //{1} ("b" //{2} "c") | ||
125 | p = m.Rec(m.P"a" + m.T(2), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1) | ||
126 | assert(p:match("abc") == 2) | ||
127 | r, l, serror = p:match("bac") | ||
128 | assert(r == nil and l == 2 and serror == "bac") | ||
74 | r, l, serror = p:match("cab") | 129 | r, l, serror = p:match("cab") |
75 | assert(r == nil and l == 0 and serror == "cab") | 130 | assert(r == nil and l == 2 and serror == "cab") |
131 | r, l, serror = p:match("dab") | ||
132 | assert(r == nil and l == 2 and serror == "dab") | ||
133 | |||
134 | |||
76 | 135 | ||
77 | -- tests related to predicates | 136 | -- tests related to predicates |
78 | p = #m.T(1) + m.P"a" | 137 | p = #m.T(1) + m.P"a" |
@@ -83,74 +142,75 @@ p = ##m.T(1) + m.P"a" | |||
83 | r, l, serror = p:match("abc") | 142 | r, l, serror = p:match("abc") |
84 | assert(r == nil and l == 1 and serror == "abc") | 143 | assert(r == nil and l == 1 and serror == "abc") |
85 | 144 | ||
86 | p = #m.T(0) * m.P"a" | ||
87 | assert(p:match("abc") == fail) | ||
88 | |||
89 | p = #m.T(0) + m.P"a" | ||
90 | assert(p:match("abc") == 2) | ||
91 | |||
92 | p = -m.T(1) * m.P"a" | 145 | p = -m.T(1) * m.P"a" |
93 | r, l, serror = p:match("abc") | 146 | r, l, serror = p:match("abc") |
94 | assert(r == nil and l == 1 and serror == "abc") | 147 | assert(r == nil and l == 1 and serror == "abc") |
95 | 148 | ||
149 | p = -m.T(1) * m.P"a" | ||
150 | r, l, serror = p:match("bbc") | ||
151 | assert(r == nil and l == 1 and serror == "bbc") | ||
152 | |||
96 | p = -(-m.T(1)) * m.P"a" | 153 | p = -(-m.T(1)) * m.P"a" |
97 | r, l, serror = p:match("abc") | 154 | r, l, serror = p:match("abc") |
98 | assert(r == nil and l == 1 and serror == "abc") | 155 | assert(r == nil and l == 1 and serror == "abc") |
99 | 156 | ||
100 | p = -m.T(0) * m.P"a" | 157 | p = m.Rec(-m.T(22), m.P"a", 22) |
101 | assert(p:match("abc") == 2) | 158 | r, l, serror = p:match("abc") |
159 | assert(r == nil and l == 0 and serror == "bc") | ||
102 | 160 | ||
103 | p = -m.T(0) + m.P"a" | 161 | assert(p:match("bbc") == 1) |
104 | assert(p:match("abc") == 1) | ||
105 | 162 | ||
106 | p = -(-m.T(0)) + m.P"a" | 163 | p = m.Rec(#m.T(22), m.P"a", 22) |
107 | assert(p:match("abc") == 2) | 164 | assert(p:match("abc") == 1) |
108 | 165 | ||
109 | p = m.Lc(-m.T(22), m.P"a", 22) | 166 | p = #m.Rec(m.T(22), m.P"a", 22) |
110 | assert(p:match("abc") == 2) | 167 | assert(p:match("abc") == 1) |
111 | 168 | ||
112 | p = m.Lc(-m.T(0), m.P"a", 0) | 169 | p = m.Rec(m.T(22), #m.P"a", 22) |
113 | assert(p:match("abc") == 1) | 170 | assert(p:match("abc") == 1) |
114 | 171 | ||
115 | p = m.Lc(#m.T(22), m.P"a", 22) | 172 | p = m.Rec(#m.T(22), m.P"a", 22) |
116 | assert(p:match("abc") == 2) | 173 | r, l, serror = p:match("bbc") |
174 | assert(r == nil and l == 0 and serror == "bbc") | ||
175 | |||
176 | p = m.Rec(#m.P("a") * m.T(22), m.T(15), 22) | ||
177 | r, l, serror = p:match("abc") | ||
178 | assert(r == nil and l == 15 and serror == "abc") | ||
179 | |||
180 | p = m.Rec(#(m.P("a") * m.T(22)), m.T(15), 22) | ||
181 | r, l, serror = p:match("abc") | ||
182 | assert(r == nil and l == 15 and serror == "bc") | ||
183 | |||
117 | 184 | ||
118 | p = m.Lc(#m.T(0), m.P"a", 0) | ||
119 | assert(p:match("abc") == 2) | ||
120 | 185 | ||
121 | -- tests related to repetition | 186 | -- tests related to repetition |
122 | p = m.T(1)^0 | 187 | p = m.T(1)^0 |
123 | r, l, serror = p:match("ab") | 188 | r, l, serror = p:match("ab") |
124 | assert(r == nil and l == 1 and serror == "ab") | 189 | assert(r == nil and l == 1 and serror == "ab") |
125 | 190 | ||
126 | p = m.T(0)^0 | ||
127 | assert(p:match("ab") == 1) | ||
128 | |||
129 | p = (m.P"a" + m.T(1))^0 | 191 | p = (m.P"a" + m.T(1))^0 |
130 | r, l, serror = p:match("aa") | 192 | r, l, serror = p:match("aa") |
131 | assert(r == nil and l == 1 and serror == "") | 193 | assert(r == nil and l == 1 and serror == "") |
132 | 194 | ||
133 | p = (m.P"a" + m.T(0))^0 | ||
134 | assert(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 |
139 | p = m.Lc(m.P"A", m.P(true), 1) + m.P("B") | 199 | p = m.Rec(m.P"A", m.P(true), 1) + m.P("B") |
140 | assert(p:match("B") == 2) | 200 | assert(p:match("B") == 2) |
141 | 201 | ||
142 | p = m.Lc(m.P"A", m.P(false), 1) + m.P("B") | 202 | p = m.Rec(m.P"A", m.P(false), 1) + m.P("B") |
143 | assert(p:match("B") == 2) | 203 | assert(p:match("B") == 2) |
144 | 204 | ||
145 | 205 | ||
146 | --[[ | 206 | --[[ |
147 | S -> A /{1} 'a' | 207 | S -> A //{1} 'a' |
148 | A -> B | 208 | A -> B |
149 | B -> %1 | 209 | B -> %1 |
150 | ]] | 210 | ]] |
151 | g = m.P{ | 211 | g = 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") | |||
176 | assert(r == nil and l == 1 and serror == "") | 236 | assert(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' |
180 | p = m.Lc(m.Lc(m.T(1), m.T(2), 1, 3), m.P"a", 2) | 240 | p = m.Rec(m.Rec(m.T(1), m.T(2), 1, 3), m.P"a", 2) |
181 | assert(p:match("abc") == 2) | 241 | assert(p:match("abc") == 2) |
182 | 242 | ||
183 | r, l, serror = p:match("") | 243 | r, l, serror = p:match("") |
184 | assert(r == nil and l == 0 and serror == "") | 244 | assert(r == nil and l == 0 and serror == "") |
185 | 245 | ||
186 | p = m.Lc(m.T(1), m.Lc(m.T(2), m.P"a", 2), 1, 3) | 246 | p = m.Rec(m.T(1), m.Rec(m.T(2), m.P"a", 2), 1, 3) |
187 | assert(p:match("abc") == 2) | 247 | assert(p:match("abc") == 2) |
188 | 248 | ||
189 | r, l, serror = p:match("") | 249 | r, l, serror = p:match("") |
190 | assert(r == nil and l == 0 and serror == "") | 250 | assert(r == nil and l == 0 and serror == "") |
191 | 251 | ||
252 | |||
253 | -- Infinte Loop TODO: check the semantics | ||
254 | -- %1 //{1} %1 | ||
255 | p = 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) | ||
260 | p = m.Rec(m.T(1), m.P"a" * (-m.P(1) + m.T(1)), 1) | ||
261 | r, l, serror = p:match("ab") | ||
262 | assert(r == nil and l == 0 and serror == "b") | ||
263 | |||
264 | r, l, serror = p:match("cd") | ||
265 | assert(r == nil and l == 0 and serror == "cd") | ||
266 | |||
267 | -- %1 //{1} . (!. / %1) | ||
268 | p = m.Rec(m.T(1), m.P(1) * (-m.P(1) + m.T(1)), 1) | ||
269 | assert(p:match("abc") == 4) | ||
270 | |||
271 | |||
192 | -- testing the limit of labels | 272 | -- testing the limit of labels |
193 | p = m.T(0) | 273 | -- can only throw labels between 1 and 255 |
194 | s = "abc" | 274 | local r = pcall(m.Rec, m.P"b", m.P"a", 0) |
195 | r, l, serror = p:match(s) | 275 | assert(r == false) |
196 | assert(r == nil and l == 0 and serror == "abc") | ||
197 | 276 | ||
198 | p = m.T(255) | 277 | local r = pcall(m.Rec, m.P"b", m.P"a", 256) |
199 | s = "abc" | 278 | assert(r == false) |
200 | r, l, serror = p:match(s) | ||
201 | assert(r == nil and l == 255 and serror == "abc") | ||
202 | 279 | ||
203 | local r = pcall(m.T, -1) | 280 | local r = pcall(m.Rec, m.P"b", m.P"a", -1) |
281 | assert(r == false) | ||
282 | |||
283 | local r = pcall(m.T, 0) | ||
204 | assert(r == false) | 284 | assert(r == false) |
205 | 285 | ||
206 | local r = pcall(m.T, 256) | 286 | local r = pcall(m.T, 256) |
207 | assert(r == false) | 287 | assert(r == false) |
208 | 288 | ||
289 | local r = pcall(m.T, -1) | ||
290 | assert(r == false) | ||
291 | |||
292 | |||
293 | local r = m.Rec(m.P"b", m.P"a", 255) | ||
294 | assert(p:match("a") == 2) | ||
295 | |||
296 | p = m.T(255) | ||
297 | s = "abc" | ||
298 | r, l, serror = p:match(s) | ||
299 | assert(r == nil and l == 255 and serror == "abc") | ||
300 | |||
301 | |||
209 | 302 | ||
210 | print("+") | 303 | print("+") |
211 | 304 | ||
212 | --[[ grammar based on Figure 8 of paper submitted to SCP | 305 | --[[ grammar based on Figure 8 of paper submitted to SCP |
213 | S -> S0 /{1} ID /{2} ID '=' Exp /{3} 'unsigned'* 'int' ID /{4} 'unsigned'* ID ID / %error | 306 | S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error |
214 | S0 -> ID S1 / 'unsigned' S2 / 'int' %3 | 307 | S0 -> S1 / S2 / &'int' %3 |
215 | S1 -> '=' %2 / !. %1 / ID %4 | 308 | S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4 |
216 | S2 -> 'unsigned' S2 / ID %4 / 'int' %3 | 309 | S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3 |
217 | ]] | 310 | ]] |
218 | |||
219 | local sp = m.S" \t\n"^0 | 311 | local sp = m.S" \t\n"^0 |
220 | local eq = sp * m.P"=" | 312 | local eq = sp * m.P"=" |
221 | 313 | ||
222 | g = m.P{ | 314 | g = 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 | ||
271 | print("+") | 363 | print("+") |
272 | 364 | ||
273 | local re = require 'relabel' | ||
274 | 365 | ||
275 | g = re.compile[['a' /{4,9} [a-z] | 366 | local re = require 'relabelrec' |
367 | |||
368 | g = re.compile[['a' //{4,9} [a-z] | ||
276 | ]] | 369 | ]] |
277 | assert(g:match("a") == 2) | 370 | assert(g:match("a") == 2) |
278 | r, l, serror = g:match("b") | 371 | r, l, serror = g:match("b") |
279 | assert(r == nil and l == 0 and serror == "b") | 372 | assert(r == nil and l == 0 and serror == "b") |
280 | 373 | ||
281 | g = re.compile[['a' /{4,9} [a-f] /{5, 7} [a-z] | 374 | g = re.compile[['a' //{4,9} [a-f] //{5, 7} [a-z] |
282 | ]] | 375 | ]] |
283 | assert(g:match("a") == 2) | 376 | assert(g:match("a") == 2) |
284 | r, l, serror = g:match("b") | 377 | r, l, serror = g:match("b") |
285 | assert(r == nil and l == 0 and serror == "b") | 378 | assert(r == nil and l == 0 and serror == "b") |
286 | 379 | ||
287 | g = re.compile[[%{1} /{4,9} [a-z] | 380 | g = re.compile[[%{1} //{4,9} [a-z] |
288 | ]] | 381 | ]] |
289 | r, l, serror = g:match("a") | 382 | r, l, serror = g:match("a") |
290 | assert(r == nil and l == 1 and serror == "a") | 383 | assert(r == nil and l == 1 and serror == "a") |
291 | 384 | ||
292 | 385 | ||
293 | g = re.compile[[%{1} /{4,1} [a-f] | 386 | g = re.compile[[%{1} //{4,1} [a-f] |
294 | ]] | 387 | ]] |
295 | assert(g:match("a") == 2) | 388 | assert(g:match("a") == 2) |
296 | r, l, serror = g:match("h") | 389 | r, l, serror = g:match("h") |
297 | assert(r == nil and l == 0 and serror == "h") | 390 | assert(r == nil and l == 0 and serror == "h") |
298 | 391 | ||
299 | g = re.compile[[[a-f]%{9} /{4,9} [a-c]%{7} /{5, 7} [a-z] ]] | 392 | g = re.compile[[[a-f]%{9} //{4,9} [a-c]%{7} //{5, 7} [a-z] ]] |
300 | assert(g:match("a") == 2) | 393 | r, l, serror = g:match("a") |
301 | assert(g:match("c") == 2) | 394 | assert(r == nil and l == 0 and serror == "") |
302 | r, l, serror = g:match("d") | 395 | r, l, serror = g:match("aa") |
396 | assert(r == nil and l == 0 and serror == "") | ||
397 | assert(g:match("aaa") == 4) | ||
398 | |||
399 | r, l, serror = g:match("ad") | ||
303 | assert(r == nil and l == 0 and serror == "d") | 400 | assert(r == nil and l == 0 and serror == "d") |
401 | |||
304 | r, l, serror = g:match("g") | 402 | r, l, serror = g:match("g") |
305 | assert(r == nil and l == 0 and serror == "g") | 403 | assert(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 |
308 | S -> S0 /{1} ID /{2} ID '=' Exp /{3} 'unsigned'* 'int' ID /{4} 'unsigned'* ID ID / %error | 407 | S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error |
309 | S0 -> ID S1 / 'unsigned' S2 / 'int' %3 | 408 | S0 -> S1 / S2 / &'int' %3 |
310 | S1 -> '=' %2 / !. %1 / ID %4 | 409 | S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4 |
311 | S2 -> 'unsigned' S2 / ID %4 / 'int' %3 | 410 | S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3 |
312 | ]] | 411 | ]] |
313 | 412 | ||
314 | |||
315 | g = re.compile([[ | 413 | g = 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" | |||
346 | r, l, serror = g:match(s) | 444 | r, l, serror = g:match(s) |
347 | assert(r == nil and l == 5 and serror == s) | 445 | assert(r == nil and l == 5 and serror == s) |
348 | 446 | ||
447 | |||
448 | |||
349 | local terror = { ['cmdSeq'] = "Missing ';' in CmdSeq", | 449 | local 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 | ||
369 | g = re.compile([[ | 469 | g = 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']) | |||
550 | print("+") | 650 | print("+") |
551 | 651 | ||
552 | 652 | ||
553 | -- test recovery operator | ||
554 | p = m.Rec("a", "b") | ||
555 | assert(p:match("a") == 2) | ||
556 | assert(p:match("b") == 2) | ||
557 | checkeqlab({nil, 0, "c"}, p:match("c")) | ||
558 | |||
559 | p = m.Rec("a", "b", 3) | 653 | p = m.Rec("a", "b", 3) |
560 | assert(p:match("a") == 2) | 654 | assert(p:match("a") == 2) |
561 | checkeqlab({nil, 0, "b"}, p:match("b")) | 655 | checkeqlab({nil, 0, "b"}, p:match("b")) |
562 | checkeqlab({nil, 0, "c"}, p:match("c")) | 656 | checkeqlab({nil, 0, "c"}, p:match("c")) |
563 | 657 | ||
564 | p = m.Rec(m.T(3), "b") | 658 | p = m.Rec(m.T(3), "b", 1) |
565 | checkeqlab({nil, 3, "a"}, p:match("a")) | 659 | checkeqlab({nil, 3, "a"}, p:match("a")) |
566 | checkeqlab({nil, 3, "b"}, p:match("b")) | 660 | checkeqlab({nil, 3, "b"}, p:match("b")) |
567 | 661 | ||
@@ -570,27 +664,28 @@ checkeqlab({nil, 0, "a"}, p:match("a")) | |||
570 | assert(p:match("b") == 2) | 664 | assert(p:match("b") == 2) |
571 | 665 | ||
572 | --[[ | 666 | --[[ |
573 | S -> (A //{fail} (!c .)*) C | 667 | S -> (A //{128} (!c .)*) C |
574 | A -> a*b | 668 | A -> a*b / %128 |
575 | C -> c+ | 669 | C -> c+ |
576 | ]] | 670 | ]] |
577 | g = m.P{ | 671 | g = 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 | ||
584 | assert(g:match("abc") == 4) | 678 | assert(g:match("abc") == 4) |
585 | assert(g:match("aabc") == 5) | 679 | assert(g:match("aabc") == 5) |
586 | assert(g:match("aadc") == 5) | 680 | assert(g:match("aadc") == 5) |
587 | assert(g:match("bc") == 3) | 681 | assert(g:match("dc") == 3) |
588 | checkeqlab({nil, 0, "bc"}, g:match("bbc")) | 682 | checkeqlab({nil, 0, "bc"}, g:match("bbc")) |
589 | assert(g:match("xxc") == 4) | 683 | assert(g:match("xxc") == 4) |
590 | assert(g:match("c") == 2) | 684 | assert(g:match("c") == 2) |
591 | checkeqlab({nil, 0, ""}, g:match("fail")) | 685 | checkeqlab({nil, 0, ""}, g:match("fail")) |
592 | checkeqlab({nil, 0, ""}, g:match("aaxx")) | 686 | checkeqlab({nil, 0, ""}, g:match("aaxx")) |
593 | 687 | ||
688 | |||
594 | --[[ | 689 | --[[ |
595 | S -> (A //{99} (!c .)*) C | 690 | S -> (A //{99} (!c .)*) C |
596 | A -> a+ (b / ^99) | 691 | A -> 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 | ||
824 | print("+") | ||
825 | |||
826 | local 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 | ||
833 | assert(g:match("a.") == 3) | ||
834 | assert(g:match("aa.") == 4) | ||
835 | assert(g:match(".") == 2) | ||
836 | checkeqlab({nil, 1, "ba."}, g:match("ba.")) | ||
837 | checkeqlab({nil, 1, "ba."}, g:match("aba.")) | ||
838 | checkeqlab({nil, 1, "cba."}, g:match("cba.")) | ||
839 | checkeqlab({nil, 2, "a"}, g:match("a.a")) | ||
730 | 840 | ||
731 | print("OK") | ||
732 | 841 | ||
842 | local g2 = m.P{ | ||
843 | "S", | ||
844 | S = m.Rec(g, V"B", 1), | ||
845 | B = P'b'^1 + T(3) | ||
846 | } | ||
847 | |||
848 | assert(g2:match("a.") == 3) | ||
849 | assert(g2:match("aa.") == 4) | ||
850 | assert(g2:match(".") == 2) | ||
851 | assert(g2:match("ba.") == 4) | ||
852 | assert(g2:match("aba.") == 5) | ||
853 | checkeqlab({nil, 3, "cba."}, g2:match("cba.")) | ||
854 | checkeqlab({nil, 2, "a"}, g2:match("a.a")) | ||
855 | |||
856 | local g3 = m.P{ | ||
857 | "S", | ||
858 | S = m.Rec(g2, V"C", 2, 3), | ||
859 | C = P'c'^1 + T(4) | ||
860 | } | ||
861 | |||
862 | assert(g3:match("a.") == 3) | ||
863 | assert(g3:match("aa.") == 4) | ||
864 | assert(g3:match(".") == 2) | ||
865 | assert(g3:match("ba.") == 4) | ||
866 | assert(g3:match("aba.") == 5) | ||
867 | assert(g3:match("cba.") == 5) | ||
868 | checkeqlab({nil, 4, "a"}, g3:match("a.a")) | ||
869 | checkeqlab({nil, 4, "dc"}, g3:match("dc")) | ||
870 | checkeqlab({nil, 4, "d"}, g3:match(".d")) | ||
871 | |||
872 | |||
873 | -- testing more captures | ||
874 | local g = re.compile[[ | ||
875 | S <- ( %s* &. {A} )* | ||
876 | A <- [0-9]+ / %{5} | ||
877 | ]] | ||
878 | |||
879 | checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")}) | ||
880 | checkeq({nil, 5, "a 123"}, {g:match("44 a 123")}) | ||
881 | |||
882 | local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5) | ||
883 | |||
884 | checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")}) | ||
885 | checkeq({"44", "a ", "58", "123"}, {g2:match("44 a 123")}) | ||
886 | |||
887 | |||
888 | local g = re.compile[[ | ||
889 | S <- ( %s* &. A )* | ||
890 | A <- {[0-9]+} / %{5} | ||
891 | ]] | ||
892 | |||
893 | checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")}) | ||
894 | checkeq({nil, 5, "a 123"}, {g:match("44 a 123")}) | ||
895 | |||
896 | local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5) | ||
897 | |||
898 | checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")}) | ||
899 | checkeq({"44", "58", "123"}, {g2:match("44 a 123")}) | ||
900 | |||
901 | |||
902 | local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V | ||
903 | local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt | ||
904 | local T, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec | ||
905 | |||
906 | local 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 | |||
914 | local 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) | ||
921 | end | ||
922 | |||
923 | local errors = {} | ||
924 | |||
925 | local 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) | ||
934 | end | ||
935 | |||
936 | local num = R("09")^1 / tonumber | ||
937 | local op = S("+-*/") | ||
938 | |||
939 | local 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 | ||
955 | end | ||
956 | |||
957 | |||
958 | local g = P { | ||
959 | "Exp", | ||
960 | Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute, | ||
961 | Operand = expect(V"Term", "ExpTerm"), | ||
962 | Term = num, | ||
963 | } | ||
964 | local rg = Rec(g, Cc(3), labelindex("ExpTerm")) | ||
965 | |||
966 | local 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 | ||
980 | end | ||
981 | |||
982 | assert(eval("98-76*54/32") == 37.125) | ||
983 | --> 37.125 | ||
984 | |||
985 | assert(eval("1+") == 4) | ||
986 | --> syntax error: expected a term after the operator (at index 3) | ||
987 | |||
988 | |||
989 | print("OK") | ||