diff options
Diffstat (limited to 'README.md')
-rw-r--r-- | README.md | 201 |
1 files changed, 191 insertions, 10 deletions
@@ -43,6 +43,9 @@ of the new functions provided by LpegLabel: | |||
43 | <tr><td><a href="#re-lc"><code>p1 /{l1, ..., ln} p2</code></a></td> | 43 | <tr><td><a href="#re-lc"><code>p1 /{l1, ..., ln} p2</code></a></td> |
44 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.Lc(p1, p2, l1, ..., ln)</code> | 44 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.Lc(p1, p2, l1, ..., ln)</code> |
45 | </td></tr> | 45 | </td></tr> |
46 | <tr><td><a href="#re-line"><code>relabel.calcline(subject, i)</code></a></td> | ||
47 | <td>Calculates line and column information regarding position <i>i</i> of the subject</code> | ||
48 | </td></tr> | ||
46 | <tr><td><a href="#re-setl"><code>relabel.setlabels (tlabel)</code></a></td> | 49 | <tr><td><a href="#re-setl"><code>relabel.setlabels (tlabel)</code></a></td> |
47 | <td>Allows to specicify a table with mnemonic labels. | 50 | <td>Allows to specicify a table with mnemonic labels. |
48 | </td></tr> | 51 | </td></tr> |
@@ -92,6 +95,11 @@ but a single choice can not mix them. That is, the parser of `relabel` | |||
92 | module will not recognize a pattern as `p1 / p2 /{l1} p3`. | 95 | module will not recognize a pattern as `p1 / p2 /{l1} p3`. |
93 | 96 | ||
94 | 97 | ||
98 | #### <a name="re-line"></a><code>relabel.calcline (subject, i)</code> | ||
99 | |||
100 | Returns line and column information regarding position <i>i</i> of the subject. | ||
101 | |||
102 | |||
95 | #### <a name="re-setl"></a><code>relabel.setlabels (tlabel)</code> | 103 | #### <a name="re-setl"></a><code>relabel.setlabels (tlabel)</code> |
96 | 104 | ||
97 | Allows to specicify a table with labels. They keys of | 105 | Allows to specicify a table with labels. They keys of |
@@ -101,22 +109,21 @@ and the associated values should be strings. | |||
101 | 109 | ||
102 | ### Examples | 110 | ### Examples |
103 | 111 | ||
104 | #### Throwing a label | 112 | Below there a few examples of usage of LPegLabel. |
113 | The code of these and of other examples is available | ||
114 | in the *examples* directory. | ||
115 | |||
116 | |||
117 | #### Matching a list of identifiers separated by commas | ||
105 | 118 | ||
106 | The following example defines a grammar that matches | 119 | The following example defines a grammar that matches |
107 | a list of identifiers separated by commas. A label | 120 | a list of identifiers separated by commas. A label |
108 | is thrown when there is an error matching an identifier | 121 | is thrown when there is an error matching an identifier |
109 | or a comma: | 122 | or a comma: |
110 | 123 | ||
111 | ```lua | 124 | ```lua |
112 | local m = require'lpeglabel' | 125 | local m = require'lpeglabel' |
113 | 126 | local re = require'relabel' | |
114 | local function calcline (s, i) | ||
115 | if i == 1 then return 1, 1 end | ||
116 | local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") | ||
117 | local col = #rest | ||
118 | return 1 + line, col ~= 0 and col or 1 | ||
119 | end | ||
120 | 127 | ||
121 | local g = m.P{ | 128 | local g = m.P{ |
122 | "S", | 129 | "S", |
@@ -130,7 +137,7 @@ local g = m.P{ | |||
130 | function mymatch (g, s) | 137 | function mymatch (g, s) |
131 | local r, e, sfail = g:match(s) | 138 | local r, e, sfail = g:match(s) |
132 | if not r then | 139 | if not r then |
133 | local line, col = calcline(s, #s - #sfail) | 140 | local line, col = re.calcline(s, #s - #sfail) |
134 | local msg = "Error at line " .. line .. " (col " .. col .. ")" | 141 | local msg = "Error at line " .. line .. " (col " .. col .. ")" |
135 | if e == 1 then | 142 | if e == 1 then |
136 | return r, msg .. ": expecting an identifier before '" .. sfail .. "'" | 143 | return r, msg .. ": expecting an identifier before '" .. sfail .. "'" |
@@ -142,6 +149,61 @@ function mymatch (g, s) | |||
142 | end | 149 | end |
143 | return r | 150 | return r |
144 | end | 151 | end |
152 | |||
153 | print(mymatch(g, "one,two")) --> 8 | ||
154 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | ||
155 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | ||
156 | ``` | ||
157 | |||
158 | In this example we could think about writing rule <em>List</em> as follows: | ||
159 | ```lua | ||
160 | List = ((m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)))^0, | ||
161 | ``` | ||
162 | |||
163 | but when matching this expression agains the end of input | ||
164 | we would get a failure whose associated label would be **2**, | ||
165 | and this would cause the failure of the *whole* repetition. | ||
166 | |||
167 | |||
168 | ##### Mnemonics instead of numbers | ||
169 | |||
170 | In the previous example we could have created a table | ||
171 | with the error messages to improve the readbility of the PEG. | ||
172 | Below we rewrite the previous grammar following this approach: | ||
173 | |||
174 | ```lua | ||
175 | local m = require'lpeglabel' | ||
176 | local re = require'relabel' | ||
177 | |||
178 | local terror = {} | ||
179 | |||
180 | local function newError(s) | ||
181 | table.insert(terror, s) | ||
182 | return #terror | ||
183 | end | ||
184 | |||
185 | local errUndef = newError("undefined") | ||
186 | local errId = newError("expecting an identifier") | ||
187 | local errComma = newError("expecting ','") | ||
188 | |||
189 | local g = m.P{ | ||
190 | "S", | ||
191 | S = m.V"Id" * m.V"List", | ||
192 | List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", | ||
193 | Id = m.V"Sp" * m.R'az'^1, | ||
194 | Comma = m.V"Sp" * ",", | ||
195 | Sp = m.S" \n\t"^0, | ||
196 | } | ||
197 | |||
198 | function mymatch (g, s) | ||
199 | local r, e, sfail = g:match(s) | ||
200 | if not r then | ||
201 | local line, col = re.calcline(s, #s - #sfail) | ||
202 | local msg = "Error at line " .. line .. " (col " .. col .. "): " | ||
203 | return r, msg .. terror[e] .. " before '" .. sfail .. "'" | ||
204 | end | ||
205 | return r | ||
206 | end | ||
145 | 207 | ||
146 | print(mymatch(g, "one,two")) --> 8 | 208 | print(mymatch(g, "one,two")) --> 8 |
147 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | 209 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' |
@@ -149,6 +211,88 @@ print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expec | |||
149 | ``` | 211 | ``` |
150 | 212 | ||
151 | 213 | ||
214 | ##### *relabel* syntax | ||
215 | |||
216 | Now we rewrite the previous example using the syntax | ||
217 | supported by *relabel*: | ||
218 | |||
219 | ```lua | ||
220 | local re = require 'relabel' | ||
221 | |||
222 | local g = re.compile[[ | ||
223 | S <- Id List | ||
224 | List <- !. / (',' / %{2}) (Id / %{1}) List | ||
225 | Id <- Sp [a-z]+ | ||
226 | Comma <- Sp ',' | ||
227 | Sp <- %s* | ||
228 | ]] | ||
229 | |||
230 | function mymatch (g, s) | ||
231 | local r, e, sfail = g:match(s) | ||
232 | if not r then | ||
233 | local line, col = re.calcline(s, #s - #sfail) | ||
234 | local msg = "Error at line " .. line .. " (col " .. col .. ")" | ||
235 | if e == 1 then | ||
236 | return r, msg .. ": expecting an identifier before '" .. sfail .. "'" | ||
237 | elseif e == 2 then | ||
238 | return r, msg .. ": expecting ',' before '" .. sfail .. "'" | ||
239 | else | ||
240 | return r, msg | ||
241 | end | ||
242 | end | ||
243 | return r | ||
244 | end | ||
245 | |||
246 | print(mymatch(g, "one,two")) --> 8 | ||
247 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | ||
248 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | ||
249 | ``` | ||
250 | |||
251 | With the help of function *setlabels* we can also rewrite the previous example to use | ||
252 | mnemonic labels instead of plain numbers: | ||
253 | |||
254 | ```lua | ||
255 | local re = require 'relabel' | ||
256 | |||
257 | local errinfo = { | ||
258 | {"errUndef", "undefined"}, | ||
259 | {"errId", "expecting an identifier"}, | ||
260 | {"errComma", "expecting ','"}, | ||
261 | } | ||
262 | |||
263 | local errmsgs = {} | ||
264 | local labels = {} | ||
265 | |||
266 | for i, err in ipairs(errinfo) do | ||
267 | errmsgs[i] = err[2] | ||
268 | labels[err[1]] = i | ||
269 | end | ||
270 | |||
271 | re.setlabels(labels) | ||
272 | |||
273 | local g = re.compile[[ | ||
274 | S <- Id List | ||
275 | List <- !. / (',' / %{errComma}) (Id / %{errId}) List | ||
276 | Id <- Sp [a-z]+ | ||
277 | Comma <- Sp ',' | ||
278 | Sp <- %s* | ||
279 | ]] | ||
280 | |||
281 | function mymatch (g, s) | ||
282 | local r, e, sfail = g:match(s) | ||
283 | if not r then | ||
284 | local line, col = re.calcline(s, #s - #sfail) | ||
285 | local msg = "Error at line " .. line .. " (col " .. col .. "): " | ||
286 | return r, msg .. errmsgs[e] .. " before '" .. sfail .. "'" | ||
287 | end | ||
288 | return r | ||
289 | end | ||
290 | |||
291 | print(mymatch(g, "one,two")) --> 8 | ||
292 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' | ||
293 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' | ||
294 | ``` | ||
295 | |||
152 | #### Arithmetic Expressions | 296 | #### Arithmetic Expressions |
153 | 297 | ||
154 | Here's an example of an LPegLabel grammar that make its own function called | 298 | Here's an example of an LPegLabel grammar that make its own function called |
@@ -239,3 +383,40 @@ print(eval "-1+(1-(1*2))/2") | |||
239 | --> syntax error: no expression found (at index 1) | 383 | --> syntax error: no expression found (at index 1) |
240 | ``` | 384 | ``` |
241 | 385 | ||
386 | #### Catching labels | ||
387 | |||
388 | When a label is thrown, the grammar itself can handle this label | ||
389 | by using the labeled ordered choice. Below we rewrite the example | ||
390 | of the list of identifiers to show this feature: | ||
391 | |||
392 | |||
393 | ```lua | ||
394 | local m = require'lpeglabel' | ||
395 | |||
396 | local terror = {} | ||
397 | |||
398 | local function newError(s) | ||
399 | table.insert(terror, s) | ||
400 | return #terror | ||
401 | end | ||
402 | |||
403 | local errUndef = newError("undefined") | ||
404 | local errId = newError("expecting an identifier") | ||
405 | local errComma = newError("expecting ','") | ||
406 | |||
407 | local g = m.P{ | ||
408 | "S", | ||
409 | S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), | ||
410 | m.V"ErrComma", errComma), | ||
411 | List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", | ||
412 | Id = m.V"Sp" * m.R'az'^1, | ||
413 | Comma = m.V"Sp" * ",", | ||
414 | Sp = m.S" \n\t"^0, | ||
415 | ErrId = m.Cc(errId) / terror, | ||
416 | ErrComma = m.Cc(errComma) / terror | ||
417 | } | ||
418 | |||
419 | print(m.match(g, "one,two")) --> 8 | ||
420 | print(m.match(g, "one two")) --> expecting ',' | ||
421 | print(m.match(g, "one,\n two,\nthree,")) --> expecting an identifier | ||
422 | ``` | ||