diff options
-rw-r--r-- | README.md | 669 | ||||
-rw-r--r-- | examples/expect.lua (renamed from examples/expect.md) | 7 | ||||
-rw-r--r-- | examples/listId1.lua | 45 | ||||
-rw-r--r-- | examples/listId2.lua | 42 | ||||
-rw-r--r-- | examples/listIdCatch.lua | 48 | ||||
-rw-r--r-- | examples/listIdRe1.lua | 30 | ||||
-rw-r--r-- | examples/listIdRe2.lua | 49 |
7 files changed, 301 insertions, 589 deletions
@@ -1,588 +1,243 @@ | |||
1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | 1 | ### LPegLabel - Parsing Expression Grammars (with Labels) for Lua |
2 | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | 2 | |
3 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | 3 | <center> |
4 | <head> | 4 | <img src="https://github.com/sqmedeiros/lpeglabel/raw/master/lpeglabel-logo.gif" alt="LPegLabel" style="width: 128px;"/> |
5 | <title>LPegLabLabel - Parsing Expression Grammars For Lua</title> | 5 | </center> |
6 | <link rel="stylesheet" | 6 | |
7 | href="http://www.inf.puc-rio.br/~roberto/lpeg/doc.css" | 7 | --- |
8 | type="text/css"/> | 8 | |
9 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> | 9 | #### Introduction |
10 | </head> | 10 | |
11 | <body> | 11 | LPegLabel is a conservative extension of the |
12 | 12 | [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg) | |
13 | <div id="container"> | 13 | library that provides an implementation of Parsing |
14 | 14 | Expression Grammars (PEGs) with labeled failures. | |
15 | <div id="product"> | 15 | Labels can be used to signal different kinds of erros |
16 | <div id="product_logo"> | 16 | and to specify which alternative in a labeled ordered |
17 | <a href="https://github.com/sqmedeiros/lpeglabel"> | 17 | choice should handle a given label. Labels can also be |
18 | <img alt="LPegLabel logo" src="lpeglabel-logo.gif" width="128"/></a> | 18 | combined with the standard patterns of LPeg. |
19 | 19 | ||
20 | </div> | ||
21 | <div id="product_name"><big><strong>LPegLabel</strong></big></div> | ||
22 | <div id="product_description"> | ||
23 | Parsing Expression Grammars For Lua with Labels, version 0.1 | ||
24 | </div> | ||
25 | </div> <!-- id="product" --> | ||
26 | |||
27 | <div id="main"> | ||
28 | |||
29 | <div id="navigation"> | ||
30 | <h1>LPeg</h1> | ||
31 | |||
32 | <ul> | ||
33 | <li><strong>Home</strong> | ||
34 | <ul> | ||
35 | <li><a href="#intro">Introduction</a></li> | ||
36 | <li><a href="#func">Functions</a></li> | ||
37 | <li><a href="#ex">Some Examples</a></li> | ||
38 | <li><a href="#download">Download</a></li> | ||
39 | <li><a href="#license">License</a></li> | ||
40 | </ul> | ||
41 | </li> | ||
42 | </ul> | ||
43 | </div> <!-- id="navigation" --> | ||
44 | |||
45 | <div id="content"> | ||
46 | |||
47 | |||
48 | <h2><a name="intro">Introduction</a></h2> | ||
49 | |||
50 | <p> | ||
51 | <em>LPegLabel</em> is an extension of the | ||
52 | <a href="http://www.inf.puc-rio.br/~roberto/lpeg/">LPeg</a> | ||
53 | library that provides an implementation of Parsing Expression | ||
54 | Grammars (PEGs) with labeled failures. Labels can be | ||
55 | used to signal different kinds of erros and to | ||
56 | specify which alternative in a labeled ordered choice | ||
57 | should handle a given label. Labels can also be combined | ||
58 | with the standard patterns of LPeg. | ||
59 | </p> | ||
60 | |||
61 | <p> | ||
62 | This document describes the new functions available | 20 | This document describes the new functions available |
63 | in LpegLabel and presents some examples of usage. | 21 | in LpegLabel and presents some examples of usage. |
22 | For a more detailed discussion about PEGs with labeled failures | ||
23 | please see [A Parsing Machine for Parsing Expression | ||
24 | Grammars with Labeled Failures](https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxzcW1lZGVpcm9zfGd4OjMzZmE3YzM0Y2E2MGM5Y2M). | ||
25 | |||
26 | |||
64 | In LPegLabel, the result of an unsuccessful matching | 27 | In LPegLabel, the result of an unsuccessful matching |
65 | is a triple <code>nil, lab, sfail</code>, where <code>lab</code> | 28 | is a triple **nil, lab, sfail**, where **lab** |
66 | is the label associated with the failure, and | 29 | is the label associated with the failure, and |
67 | <code>sfail</code> is the suffix input where | 30 | **sfail** is the suffix input being matched when |
68 | the label was thrown. | 31 | **lab** was thrown. Below there is a brief summary |
69 | </p> | 32 | of the new functions provided by LpegLabel: |
70 | 33 | ||
71 | <p> | ||
72 | Below there is a brief summary of the new functions | ||
73 | provided by LpegLabel: | ||
74 | </p> | ||
75 | <table border="1"> | 34 | <table border="1"> |
76 | <tbody><tr><td><b>Function</b></td><td><b>Description</b></td></tr> | 35 | <tbody><tr><td><b>Function</b></td><td><b>Description</b></td></tr> |
77 | <tr><td><a href="#f-t"><code>lpeglabel.T (l)</code></a></td> | 36 | <tr><td><a href="#f-t"><code>lpeglabel.T (l)</code></a></td> |
78 | <td>Throws label <code>l</code></td></tr> | 37 | <td>Throws label <code>l</code></td></tr> |
79 | <tr><td><a href="#f-lc"><code>lpeglabel.Lc (p1, p2, l<sub>1</sub>, ..., l<sub>n</sub>)</code></a></td> | 38 | <tr><td><a href="#f-lc"><code>lpeglabel.Lc (p1, p2, l1, ..., ln)</code></a></td> |
80 | <td>Matches <code>p1</code> and tries to match <code>p2</code> | 39 | <td>Matches <code>p1</code> and tries to match <code>p2</code> |
81 | if the matching of <code>p1</code> gives one of l<sub>1</sub>, ..., l<sub>n</sub> | 40 | if the matching of <code>p1</code> gives one of l<sub>1</sub>, ..., l<sub>n</sub> |
82 | </td></tr> | 41 | </td></tr> |
83 | <tr><td><a href="#re-t"><code>%{l}</code></a></td> | 42 | <tr><td><a href="#re-t"><code>%{l}</code></a></td> |
84 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.T(l)</code> | 43 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.T(l)</code> |
85 | </td></tr> | 44 | </td></tr> |
86 | <tr><td><a href="#re-lc"><code>p1 /{l<sub>1</sub>, ..., l<sub>n</sub>} p2</code></a></td> | 45 | <tr><td><a href="#re-lc"><code>p1 /{l1, ..., ln} p2</code></a></td> |
87 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.Lc(p1, p2, l<sub>1</sub>, ..., l<sub>n</sub>)</code> | 46 | <td>Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.Lc(p1, p2, l1, ..., ln)</code> |
88 | </td></tr> | 47 | </td></tr> |
89 | <tr><td><a href="#re-setl"><code>relabel.setlabels (tlabel)</code></a></td> | 48 | <tr><td><a href="#re-setl"><code>relabel.setlabels (tlabel)</code></a></td> |
90 | <td>Allows to specicify a table with mnemonic labels. | 49 | <td>Allows to specicify a table with mnemonic labels. |
91 | </td></tr> | 50 | </td></tr> |
92 | </tbody></table> | 51 | </tbody></table> |
93 | 52 | ||
94 | <p> | 53 | |
95 | For a more detailed and formal discussion about | 54 | #### Functions |
96 | PEGs with labels please see | 55 | |
97 | <a href="http://www.inf.puc-rio.br/~roberto/docs/sblp2013-1.pdf"> | 56 | |
98 | Exception Handling for Error Reporting in Parsing Expression Grammars</a>, | 57 | ##### <a name="f-t"></a><code>lpeglabel.T(l)</code> |
99 | <a href="http://arxiv.org/abs/1405.6646">Error Reporting in Parsing Expression Grammars</a>, | 58 | |
100 | and <a href="http://dx.doi.org/10.1145/2851613.2851750"> | 59 | |
101 | A parsing machine for parsing expression grammars with labeled failures</a>. | 60 | Returns a pattern that throws the label `l`. |
102 | </p> | 61 | A label must be an integer between 0 and 63. |
103 | 62 | ||
104 | <!-- | 63 | The label 0 is equivalent to the regular failure of PEGs. |
105 | <p> | 64 | |
106 | In case of an unsucessful matching, the <em>match</em> function returns | 65 | |
107 | <code>nil</code> plus a list of labels. These labels may be used to build | 66 | #### <a name="f-lc"></a><code>lpeglabel.Lc(p1, p2, l1, ..., ln)</code># |
108 | a good error message. | 67 | |
109 | </p> | 68 | Returns a pattern equivalent to a *labeled ordered choice*. |
110 | --> | 69 | If the matching of `p1` gives one of the labels `l1, ..., ln`, |
111 | 70 | then the matching of `p2` is tried from the same position. Otherwise, | |
112 | <h2><a name="func">Functions</a></h2> | 71 | the result of the matching of `p1` is the pattern's result. |
113 | 72 | ||
114 | 73 | The labeled ordered choice `lpeg.Lc(p1, p2, 0)` is equivalent to the | |
115 | <h3><a name="f-t"></a><code>lpeglabel.T(l)</code></h3> | 74 | regular ordered choice `p1 / p2`. |
116 | <p> | 75 | |
117 | Returns a pattern that throws the label <code>l</code>. | ||
118 | A label must be an integer between <code>0</code> and <code>63</code>. | ||
119 | |||
120 | The label <code>0</code> is equivalent to the regular failure of PEGs. | ||
121 | |||
122 | |||
123 | <h3><a name="f-lc"></a><code>lpeglabel.Lc(p1, p2, l<sub>1</sub>, ..., l<sub>n</sub>)</code></h3> | ||
124 | <p> | ||
125 | Returns a pattern equivalent to a <em>labeled ordered choice</em>. | ||
126 | If the matching of <code>p1</code> gives one of the labels <code>l<sub>1</sub>, ..., l<sub>n</sub></code>, | ||
127 | then the matching of <code>p2</code> is tried from the same position. Otherwise, | ||
128 | the result of the matching of <code>p1</code> is the pattern's result. | ||
129 | </p> | ||
130 | |||
131 | <p> | ||
132 | The labeled ordered choice <code>lpeg.Lc(p1, p2, 0)</code> is equivalent to the | ||
133 | regular ordered choice <code>p1 / p2</code>. | ||
134 | </p> | ||
135 | |||
136 | <p> | ||
137 | Although PEG's ordered choice is associative, the labeled ordered choice is not. | 76 | Although PEG's ordered choice is associative, the labeled ordered choice is not. |
138 | When using this function, the user should take care to build a left-associative | 77 | When using this function, the user should take care to build a left-associative |
139 | labeled ordered choice pattern. | 78 | labeled ordered choice pattern. |
140 | </p> | ||
141 | 79 | ||
142 | 80 | ||
143 | <h3><a name="re-t"></a><code>%{l}</code></h3> | 81 | #### <a name="re-t"></a><code>%{l}</code> |
144 | <p> | 82 | |
145 | Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.T(l)</code>. | 83 | Syntax of *relabel* module. Equivalent to `lpeg.T(l)`. |
146 | </p> | ||
147 | 84 | ||
148 | 85 | ||
149 | <h3><a name="re-lc"></a><code>p1 /{l<sub>1</sub>, ..., l<sub>n</sub>} p2</code></h3> | 86 | #### <a name="re-lc"></a><code>p1 /{l1, ..., ln} p2</code> |
150 | <p> | ||
151 | Syntax of <em>relabel</em> module. Equivalent to <code>lpeg.Lc(p1, p2, l<sub>1</sub>, ..., l<sub>n</sub>)</code>. | ||
152 | </p> | ||
153 | 87 | ||
154 | <p> | 88 | Syntax of *relabel* module. Equivalent to `lpeg.Lc(p1, p2, l1, ..., ln)`. |
155 | The <code>/{}</code> operator is left-associative. | ||
156 | </p> | ||
157 | 89 | ||
158 | <p> | 90 | The `/{}` operator is left-associative. |
159 | A grammar can use both choice operators (<code>/</code> and <code>/{}</code>), | ||
160 | but a single choice can not mix them. That is, the parser | ||
161 | of <code>relabel</code> module will not recognize a pattern as | ||
162 | <code>p1 / p2 /{l<sub>1</sub>} p3</code>. | ||
163 | </p> | ||
164 | 91 | ||
92 | A grammar can use both choice operators (`/` and `/{}`), | ||
93 | but a single choice can not mix them. That is, the parser of `relabel` | ||
94 | module will not recognize a pattern as `p1 / p2 /{l1} p3`. | ||
165 | 95 | ||
166 | <h3><a name="re-setl"></a><code>relabel.setlabels (tlabel)</code></h3> | ||
167 | 96 | ||
168 | <p>Allows to specicify a table with labels. They keys of | 97 | #### <a name="re-setl"></a><code>relabel.setlabels (tlabel)</code> |
169 | <code>tlabel</code> must be integers between <code>0</code> and <code>63</code>, | 98 | |
99 | Allows to specicify a table with labels. They keys of | ||
100 | `tlabel` must be integers between 0 and 63, | ||
170 | and the associated values should be strings. | 101 | and the associated values should be strings. |
171 | </p> | ||
172 | 102 | ||
173 | 103 | ||
104 | ### Examples | ||
174 | 105 | ||
175 | <h2><a name="ex">Some Examples</a></h2> | 106 | #### Throwing a label |
176 | 107 | ||
177 | <h3>Throwing a label</h3> | ||
178 | <p> | ||
179 | The following example defines a grammar that matches | 108 | The following example defines a grammar that matches |
180 | a list of identifiers separated by commas. A label | 109 | a list of identifiers separated by commas. A label |
181 | is thrown when there is an error matching an identifier | 110 | is thrown when there is an error matching an identifier |
182 | or a comma: | 111 | or a comma: |
183 | </p> | 112 | |
184 | <pre class="example"> | 113 | ```lua |
185 | local m = require'lpeglabel' | 114 | local m = require'lpeglabel' |
186 | 115 | ||
116 | local function calcline (s, i) | ||
117 | if i == 1 then return 1, 1 end | ||
118 | local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") | ||
119 | local col = #rest | ||
120 | return 1 + line, col ~= 0 and col or 1 | ||
121 | end | ||
122 | |||
187 | local g = m.P{ | 123 | local g = m.P{ |
188 | "S", | 124 | "S", |
189 | S = m.V"Id" * m.V"List", | 125 | S = m.V"Id" * m.V"List", |
190 | List = -m.P(1) + ("," + m.T(2)) * m.V"Id" * m.V"List", | 126 | List = -m.P(1) + (m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)) * m.V"List", |
191 | Id = m.R'az'^1 + m.T(1), | 127 | Id = m.V"Sp" * m.R'az'^1, |
128 | Comma = m.V"Sp" * ",", | ||
129 | Sp = m.S" \n\t"^0, | ||
192 | } | 130 | } |
193 | 131 | ||
194 | function mymatch (g, s) | 132 | function mymatch (g, s) |
195 | local r, e = g:match(s) | 133 | local r, e, sfail = g:match(s) |
196 | if not r then | 134 | if not r then |
135 | local line, col = calcline(s, #s - #sfail) | ||
136 | local msg = "Error at line " .. line .. " (col " .. col .. ")" | ||
197 | if e == 1 then | 137 | if e == 1 then |
198 | return "Error: expecting an identifier" | 138 | return r, msg .. ": expecting an identifier before '" .. sfail .. "'" |
199 | elseif e == 2 then | 139 | elseif e == 2 then |
200 | return "Error: expecting ','" | 140 | return r, msg .. ": expecting ',' before '" .. sfail .. "'" |
201 | else | 141 | else |
202 | return "Error" | 142 | return r, msg |
203 | end | 143 | end |
204 | end | 144 | end |
205 | return r | 145 | return r |
206 | end | 146 | end |
207 | 147 | ||
208 | print(mymatch(g, "a,b")) | 148 | print(mymatch(g, "one,two")) --> 8 |
209 | print(mymatch(g, "a b")) | 149 | print(mymatch(g, "one two")) --> nil Error at line 1 (col 3): expecting ',' before ' two' |
210 | print(mymatch(g, ", b")) | 150 | print(mymatch(g, "one,\n two,\nthree,")) --> nil Error at line 3 (col 6): expecting an identifier before '' |
211 | </pre> | 151 | ``` |
212 | <p> | ||
213 | In this example we could think about writing rule <em>List</em> as follows: | ||
214 | <pre class="example"> | ||
215 | List = m.P(("," + m.T(2)) * m.V"Id")^0 | ||
216 | </pre> | ||
217 | but this would give us an expression that when matching | ||
218 | the end of input would result in a failure whose associated | ||
219 | label would be <em>2</em>. | ||
220 | </p> | ||
221 | |||
222 | <p> | ||
223 | In the previous example we could have also created a table | ||
224 | with the error messages to improve the readbility of the PEG. | ||
225 | Below we rewrite the grammar following this approach: | ||
226 | </p> | ||
227 | |||
228 | <pre class="example"> | ||
229 | local m = require'lpeglabel' | ||
230 | 152 | ||
231 | local errUndef = 0 | ||
232 | local errId = 1 | ||
233 | local errComma = 2 | ||
234 | 153 | ||
235 | local terror = { | 154 | #### Arithmetic Expressions |
236 | [errUndef] = "Error", | ||
237 | [errId] = "Error: expecting an identifier", | ||
238 | [errComma] = "Error: expecting ','", | ||
239 | } | ||
240 | 155 | ||
241 | local g = m.P{ | 156 | Here's an example of an LPegLabel grammar that make its own function called |
242 | "S", | 157 | 'expect', which takes a pattern and a label as parameters and throws the label |
243 | S = m.V"Id" * m.V"List", | 158 | if the pattern fails to be matched. This function can be extended later on to |
244 | List = -m.P(1) + ("," + m.T(errComma)) * m.V"Id" * m.V"List", | 159 | record all errors encountered once error recovery is implemented. |
245 | Id = m.R'az'^1 + m.T(errId), | 160 | |
161 | ```lua | ||
162 | local lpeg = require"lpeglabel" | ||
163 | |||
164 | local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T | ||
165 | |||
166 | local labels = { | ||
167 | {"NoExp", "no expression found"}, | ||
168 | {"Extra", "extra characters found after the expression"}, | ||
169 | {"ExpTerm", "expected a term after the operator"}, | ||
170 | {"ExpExp", "expected an expression after the parenthesis"}, | ||
171 | {"MisClose", "missing a closing ')' after the expression"}, | ||
246 | } | 172 | } |
247 | 173 | ||
248 | function mymatch (g, s) | 174 | local function expect(patt, labname) |
249 | local r, e = g:match(s) | 175 | for i, elem in ipairs(labels) do |
250 | if not r then | 176 | if elem[1] == labname then |
251 | return terror[e] | 177 | return patt + T(i) |
178 | end | ||
252 | end | 179 | end |
253 | return r | 180 | |
181 | error("could not find label: " .. labname) | ||
254 | end | 182 | end |
255 | |||
256 | print(mymatch(g, "a,b")) | ||
257 | print(mymatch(g, "a b")) | ||
258 | print(mymatch(g, ", b")) | ||
259 | </pre> | ||
260 | |||
261 | <h3>Throwing a label using the <em>relabel</em> module</h3> | ||
262 | |||
263 | <p> | ||
264 | We can also rewrite the previous example using the <em>relabel</em> module | ||
265 | as follows: | ||
266 | </p> | ||
267 | <pre class="example"> | ||
268 | local re = require 'relabel' | ||
269 | |||
270 | local g = re.compile[[ | ||
271 | S <- Id List | ||
272 | List <- !. / (',' / %{2}) Id List | ||
273 | Id <- [a-z] / %{1} | ||
274 | ]] | ||
275 | 183 | ||
276 | function mymatch (g, s) | 184 | local num = R("09")^1 / tonumber |
277 | local r, e = g:match(s) | 185 | local op = S("+-*/") |
278 | if not r then | 186 | |
279 | if e == 1 then | 187 | local function compute(tokens) |
280 | return "Error: expecting an identifier" | 188 | local result = tokens[1] |
281 | elseif e == 2 then | 189 | for i = 2, #tokens, 2 do |
282 | return "Error: expecting ','" | 190 | if tokens[i] == '+' then |
191 | result = result + tokens[i+1] | ||
192 | elseif tokens[i] == '-' then | ||
193 | result = result - tokens[i+1] | ||
194 | elseif tokens[i] == '*' then | ||
195 | result = result * tokens[i+1] | ||
196 | elseif tokens[i] == '/' then | ||
197 | result = result / tokens[i+1] | ||
283 | else | 198 | else |
284 | return "Error" | 199 | error('unknown operation: ' .. tokens[i]) |
285 | end | 200 | end |
286 | end | 201 | end |
287 | return r | 202 | return result |
288 | end | 203 | end |
289 | |||
290 | print(mymatch(g, "a,b")) | ||
291 | print(mymatch(g, "a b")) | ||
292 | print(mymatch(g, ", b")) | ||
293 | </pre> | ||
294 | |||
295 | <p> | ||
296 | Another way to describe the previous example using the <em>relabel</em> module | ||
297 | is by using a table with the description of the errors (<em>terror</em>) and | ||
298 | another table that associates a name to a given label (<em>tlabels</em>): | ||
299 | </p> | ||
300 | <pre class="example"> | ||
301 | local re = require 'relabel' | ||
302 | |||
303 | local errUndef, errId, errComma = 0, 1, 2 | ||
304 | |||
305 | local terror = { | ||
306 | [errUndef] = "Error", | ||
307 | [errId] = "Error: expecting an identifier", | ||
308 | [errComma] = "Error: expecting ','", | ||
309 | } | ||
310 | 204 | ||
311 | local tlabels = { ["errUndef"] = errUndef, | 205 | local g = P { |
312 | ["errId"] = errId, | 206 | "Exp", |
313 | ["errComma"] = errComma } | 207 | Exp = Ct(V"Term" * (C(op) * expect(V"Term", "ExpTerm"))^0) / compute; |
314 | 208 | Term = num + V"Group"; | |
315 | re.setlabels(tlabels) | 209 | Group = "(" * expect(V"Exp", "ExpExp") * expect(")", "MisClose"); |
316 | |||
317 | local g = re.compile[[ | ||
318 | S <- Id List | ||
319 | List <- !. / (',' / %{errComma}) Id List | ||
320 | Id <- [a-z] / %{errId} | ||
321 | ]] | ||
322 | |||
323 | function mymatch (g, s) | ||
324 | local r, e = g:match(s) | ||
325 | if not r then | ||
326 | return terror[e] | ||
327 | end | ||
328 | return r | ||
329 | end | ||
330 | |||
331 | print(mymatch(g, "a,b")) | ||
332 | print(mymatch(g, "a b")) | ||
333 | print(mymatch(g, ", b")) | ||
334 | </pre> | ||
335 | |||
336 | |||
337 | |||
338 | <h3>Throwing and catching a label</h3> | ||
339 | |||
340 | <p> | ||
341 | When a label is thrown, the grammar itself can handle this label | ||
342 | by using the labeled ordered choice. Below we rewrite the example | ||
343 | of the list of identifiers to show this feature: | ||
344 | </p> | ||
345 | <pre class="example"> | ||
346 | local m = require'lpeglabel' | ||
347 | |||
348 | local errUndef, errId, errComma = 0, 1, 2 | ||
349 | |||
350 | local terror = { | ||
351 | [errUndef] = "Error", | ||
352 | [errId] = "Error: expecting an identifier", | ||
353 | [errComma] = "Error: expecting ','", | ||
354 | } | 210 | } |
355 | 211 | ||
356 | g = m.P{ | 212 | g = expect(g, "NoExp") * expect(-P(1), "Extra") |
357 | "S", | ||
358 | S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), | ||
359 | m.V"ErrComma", errComma), | ||
360 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", | ||
361 | Id = m.R'az'^1 + m.T(errId), | ||
362 | Comma = "," + m.T(errComma), | ||
363 | ErrId = m.Cc(errId) / terror, | ||
364 | ErrComma = m.Cc(errComma) / terror | ||
365 | } | ||
366 | 213 | ||
367 | print(g:match("a,b")) | 214 | local function eval(input) |
368 | print(g:match("a b")) | 215 | local result, label, suffix = g:match(input) |
369 | print(g:match(",b")) | 216 | if result ~= nil then |
370 | </pre> | 217 | return result |
371 | 218 | else | |
372 | <p> | 219 | local pos = input:len() - suffix:len() + 1 |
373 | As was pointed out <a href="#f-lc">before</a>, the labeled ordered | 220 | local msg = labels[label][2] |
374 | choice is not associative, so we should impose a left-associative | 221 | return nil, "syntax error: " .. msg .. " (at index " .. pos .. ")" |
375 | order when using function <code>Lc</code>. | 222 | end |
376 | </p> | ||
377 | <p> | ||
378 | Below we use the <em>re</em> module to throw and catch labels. | ||
379 | As was pointed out <a href="#re-lc">before</a>, the <code>/{}</code> | ||
380 | operator is left-associative, so we do not need to manually impose | ||
381 | a left-associative order as we did in the previous example that | ||
382 | used <code>Lc</code>: | ||
383 | </p> | ||
384 | <pre class="example"> | ||
385 | local re = require'relabel' | ||
386 | |||
387 | local terror = {} | ||
388 | |||
389 | local function newError(l, msg) | ||
390 | table.insert(terror, { l = l, msg = msg } ) | ||
391 | end | 223 | end |
392 | 224 | ||
393 | newError("errId", "Error: expecting an identifier") | 225 | print(eval "98-76*(54/32)") |
394 | newError("errComma", "Error: expecting ','") | 226 | --> 37.125 |
395 | |||
396 | local labelCode = {} | ||
397 | local labelMsg = {} | ||
398 | for k, v in ipairs(terror) do | ||
399 | labelCode[v.l] = k | ||
400 | labelMsg[v.l] = v.msg | ||
401 | end | ||
402 | 227 | ||
403 | re.setlabels(labelCode) | 228 | print(eval "(1+1-1*2/2") |
404 | 229 | --> syntax error: missing a closing ')' after the expression (at index 11) | |
405 | local p = re.compile([[ | ||
406 | S <- Id List /{errId} ErrId /{errComma} ErrComma | ||
407 | List <- !. / Comma Id List | ||
408 | Id <- [a-z]+ / %{errId} | ||
409 | Comma <- ',' / %{errComma} | ||
410 | ErrId <- '' -> errId | ||
411 | ErrComma <- '' -> errComma | ||
412 | ]], labelMsg) | ||
413 | |||
414 | print(p:match("a,b")) | ||
415 | print(p:match("a b")) | ||
416 | print(p:match(",b")) | ||
417 | </pre> | ||
418 | |||
419 | |||
420 | <h3>Tiny Language</h3> | ||
421 | <p> | ||
422 | As a more complex example, below we have the grammar | ||
423 | for the Tiny language, as described in | ||
424 | <a href="http://arxiv.org/abs/1405.6646">this</a> paper. | ||
425 | The example below can also show the line where the syntactic | ||
426 | error probably happened. | ||
427 | </p> | ||
428 | <pre class="example"> | ||
429 | local re = require 'relabel' | ||
430 | |||
431 | local terror = {} | ||
432 | |||
433 | local function newError(l, msg) | ||
434 | table.insert(terror, { l = l, msg = msg} ) | ||
435 | end | ||
436 | 230 | ||
437 | newError("errSemi", "Error: missing ';'") | 231 | print(eval "(1+)-1*(2/2)") |
438 | newError("errExpIf", "Error: expected expression after 'if'") | 232 | --> syntax error: expected a term after the operator (at index 4) |
439 | newError("errThen", "Error: expected 'then' keyword") | ||
440 | newError("errCmdSeq1", "Error: expected at least a command after 'then'") | ||
441 | newError("errCmdSeq2", "Error: expected at least a command after 'else'") | ||
442 | newError("errEnd", "Error: expected 'end' keyword") | ||
443 | newError("errCmdSeqRep", "Error: expected at least a command after 'repeat'") | ||
444 | newError("errUntil", "Error: expected 'until' keyword") | ||
445 | newError("errExpRep", "Error: expected expression after 'until'") | ||
446 | newError("errAssignOp", "Error: expected ':=' in assigment") | ||
447 | newError("errExpAssign", "Error: expected expression after ':='") | ||
448 | newError("errReadName", "Error: expected an identifier after 'read'") | ||
449 | newError("errWriteExp", "Error: expected expression after 'write'") | ||
450 | newError("errSimpExp", "Error: expected '(', ID, or number after '<' or '='") | ||
451 | newError("errTerm", "Error: expected '(', ID, or number after '+' or '-'") | ||
452 | newError("errFactor", "Error: expected '(', ID, or number after '*' or '/'") | ||
453 | newError("errExpFac", "Error: expected expression after '('") | ||
454 | newError("errClosePar", "Error: expected ')' after expression") | ||
455 | |||
456 | local line | ||
457 | |||
458 | local function incLine() | ||
459 | line = line + 1 | ||
460 | return true | ||
461 | end | ||
462 | 233 | ||
463 | local function countLine(s, i) | 234 | print(eval "(1+1)-1*(/2)") |
464 | line = 1 | 235 | --> syntax error: expected an expression after the parenthesis (at index 10) |
465 | local p = re.compile([[ | ||
466 | S <- (%nl -> incLine / .)* | ||
467 | ]], { incLine = incLine}) | ||
468 | p:match(s:sub(1, i)) | ||
469 | return true | ||
470 | end | ||
471 | 236 | ||
472 | local labelCode = {} | 237 | print(eval "1+(1-(1*2))/2x") |
473 | for k, v in ipairs(terror) do | 238 | --> syntax error: extra chracters found after the expression (at index 14) |
474 | labelCode[v.l] = k | ||
475 | end | ||
476 | 239 | ||
477 | re.setlabels(labelCode) | 240 | print(eval "-1+(1-(1*2))/2") |
478 | 241 | --> syntax error: no expression found (at index 1) | |
479 | local g = re.compile([[ | 242 | ``` |
480 | Tiny <- CmdSeq | ||
481 | CmdSeq <- (Cmd (SEMICOLON / ErrSemi)) (Cmd (SEMICOLON / ErrSemi))* | ||
482 | Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd | ||
483 | IfCmd <- IF (Exp / ErrExpIf) (THEN / ErrThen) (CmdSeq / ErrCmdSeq1) (ELSE (CmdSeq / ErrCmdSeq2) / '') (END / ErrEnd) | ||
484 | RepeatCmd <- REPEAT (CmdSeq / ErrCmdSeqRep) (UNTIL / ErrUntil) (Exp / ErrExpRep) | ||
485 | AssignCmd <- NAME (ASSIGNMENT / ErrAssignOp) (Exp / ErrExpAssign) | ||
486 | ReadCmd <- READ (NAME / ErrReadName) | ||
487 | WriteCmd <- WRITE (Exp / ErrWriteExp) | ||
488 | Exp <- SimpleExp ((LESS / EQUAL) (SimpleExp / ErrSimpExp) / '') | ||
489 | SimpleExp <- Term ((ADD / SUB) (Term / ErrTerm))* | ||
490 | Term <- Factor ((MUL / DIV) (Factor / ErrFactor))* | ||
491 | Factor <- OPENPAR (Exp / ErrExpFac) (CLOSEPAR / ErrClosePar) / NUMBER / NAME | ||
492 | ErrSemi <- ErrCount %{errSemi} | ||
493 | ErrExpIf <- ErrCount %{errExpIf} | ||
494 | ErrThen <- ErrCount %{errThen} | ||
495 | ErrCmdSeq1 <- ErrCount %{errCmdSeq1} | ||
496 | ErrCmdSeq2 <- ErrCount %{errCmdSeq2} | ||
497 | ErrEnd <- ErrCount %{errEnd} | ||
498 | ErrCmdSeqRep <- ErrCount %{errCmdSeqRep} | ||
499 | ErrUntil <- ErrCount %{errUntil} | ||
500 | ErrExpRep <- ErrCount %{errExpRep} | ||
501 | ErrAssignOp <- ErrCount %{errAssignOp} | ||
502 | ErrExpAssign <- ErrCount %{errExpAssign} | ||
503 | ErrReadName <- ErrCount %{errReadName} | ||
504 | ErrWriteExp <- ErrCount %{errWriteExp} | ||
505 | ErrSimpExp <- ErrCount %{errSimpExp} | ||
506 | ErrTerm <- ErrCount %{errTerm} | ||
507 | ErrFactor <- ErrCount %{errFactor} | ||
508 | ErrExpFac <- ErrCount %{errExpFac} | ||
509 | ErrClosePar <- ErrCount %{errClosePar} | ||
510 | ErrCount <- '' => countLine | ||
511 | ADD <- Sp '+' | ||
512 | ASSIGNMENT <- Sp ':=' | ||
513 | CLOSEPAR <- Sp ')' | ||
514 | DIV <- Sp '/' | ||
515 | IF <- Sp 'if' | ||
516 | ELSE <- Sp 'else' | ||
517 | END <- Sp 'end' | ||
518 | EQUAL <- Sp '=' | ||
519 | LESS <- Sp '<' | ||
520 | MUL <- Sp '*' | ||
521 | NAME <- Sp !RESERVED [a-z]+ | ||
522 | NUMBER <- Sp [0-9]+ | ||
523 | OPENPAR <- Sp '(' | ||
524 | READ <- Sp 'read' | ||
525 | REPEAT <- Sp 'repeat' | ||
526 | SEMICOLON <- Sp ';' | ||
527 | SUB <- Sp '-' | ||
528 | THEN <- Sp 'then' | ||
529 | UNTIL <- Sp 'until' | ||
530 | WRITE <- Sp 'write' | ||
531 | RESERVED <- (IF / ELSE / END / READ / REPEAT / THEN / UNTIL / WRITE) ![a-z]+ | ||
532 | Sp <- %s* | ||
533 | ]], { countLine = countLine }) | ||
534 | </pre> | ||
535 | |||
536 | |||
537 | <h2><a name="download"></a>Download</h2> | ||
538 | |||
539 | <p>LPegLabel | ||
540 | <a href="https://github.com/sqmedeiros/lpeglabel/archive/master.zip">source code</a>.</p> | ||
541 | |||
542 | |||
543 | <h2><a name="license">License</a></h2> | ||
544 | |||
545 | <p> | ||
546 | The MIT License (MIT) | ||
547 | </p> | ||
548 | <p> | ||
549 | Copyright (c) 2014-2015 Sérgio Medeiros | ||
550 | </p> | ||
551 | <p> | ||
552 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
553 | of this software and associated documentation files (the "Software"), to deal | ||
554 | in the Software without restriction, including without limitation the rights | ||
555 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
556 | copies of the Software, and to permit persons to whom the Software is | ||
557 | furnished to do so, subject to the following conditions: | ||
558 | </p> | ||
559 | <p> | ||
560 | The above copyright notice and this permission notice shall be included in all | ||
561 | copies or substantial portions of the Software. | ||
562 | </p> | ||
563 | <p> | ||
564 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
565 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
566 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
567 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
568 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
569 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
570 | SOFTWARE. | ||
571 | |||
572 | |||
573 | |||
574 | |||
575 | </p> | ||
576 | |||
577 | </div> <!-- id="content" --> | ||
578 | |||
579 | </div> <!-- id="main" --> | ||
580 | |||
581 | <div id="about"> | ||
582 | </div> <!-- id="about" --> | ||
583 | |||
584 | </div> <!-- id="container" --> | ||
585 | |||
586 | </body> | ||
587 | </html> | ||
588 | 243 | ||
diff --git a/examples/expect.md b/examples/expect.lua index d7d2d3e..2b7e904 100644 --- a/examples/expect.md +++ b/examples/expect.lua | |||
@@ -1,9 +1,3 @@ | |||
1 | Here's an example of an LPegLabel grammar that make its own function called | ||
2 | 'expect', which takes a pattern and a label as parameters and throws the label | ||
3 | if the pattern fails to be matched. This function can be extended later on to | ||
4 | record all errors encountered once error recovery is implemented. | ||
5 | |||
6 | ```lua | ||
7 | local lpeg = require"lpeglabel" | 1 | local lpeg = require"lpeglabel" |
8 | 2 | ||
9 | local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T | 3 | local R, S, P, V, C, Ct, T = lpeg.R, lpeg.S, lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.T |
@@ -84,4 +78,3 @@ print(eval "1+(1-(1*2))/2x") | |||
84 | 78 | ||
85 | print(eval "-1+(1-(1*2))/2") | 79 | print(eval "-1+(1-(1*2))/2") |
86 | --> syntax error: no expression found (at index 1) | 80 | --> syntax error: no expression found (at index 1) |
87 | ``` | ||
diff --git a/examples/listId1.lua b/examples/listId1.lua index 0bf26a1..12c0678 100644 --- a/examples/listId1.lua +++ b/examples/listId1.lua | |||
@@ -1,27 +1,38 @@ | |||
1 | local m = require'lpeglabel' | 1 | local m = require'lpeglabel' |
2 | 2 | ||
3 | local function calcline (s, i) | ||
4 | if i == 1 then return 1, 1 end | ||
5 | local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") | ||
6 | local col = #rest | ||
7 | return 1 + line, col ~= 0 and col or 1 | ||
8 | end | ||
9 | |||
3 | local g = m.P{ | 10 | local g = m.P{ |
4 | "S", | 11 | "S", |
5 | S = m.V"Id" * m.V"List", | 12 | S = m.V"Id" * m.V"List", |
6 | List = -m.P(1) + ("," + m.T(2)) * m.V"Id" * m.V"List", | 13 | List = -m.P(1) + (m.V"Comma" + m.T(2)) * (m.V"Id" + m.T(1)) * m.V"List", |
7 | Id = m.R'az'^1 + m.T(1), | 14 | Id = m.V"Sp" * m.R'az'^1, |
15 | Comma = m.V"Sp" * ",", | ||
16 | Sp = m.S" \n\t"^0, | ||
8 | } | 17 | } |
9 | 18 | ||
10 | function mymatch (g, s) | 19 | function mymatch (g, s) |
11 | local r, e, sfail = g:match(s) | 20 | local r, e, sfail = g:match(s) |
12 | if not r then | 21 | if not r then |
13 | if e == 1 then | 22 | local line, col = calcline(s, #s - #sfail) |
14 | return r, "Error: expecting an identifier before '" .. sfail .. "'" | 23 | local msg = "Error at line " .. line .. " (col " .. col .. ")" |
15 | elseif e == 2 then | 24 | if e == 1 then |
16 | return r, "Error: expecting ',' before '" .. sfail .. "'" | 25 | return r, msg .. ": expecting an identifier before '" .. sfail .. "'" |
17 | else | 26 | elseif e == 2 then |
18 | return r, "Error" | 27 | return r, msg .. ": expecting ',' before '" .. sfail .. "'" |
19 | end | 28 | else |
20 | end | 29 | return r, msg |
21 | return r | 30 | end |
31 | end | ||
32 | return r | ||
22 | end | 33 | end |
23 | 34 | ||
24 | print(mymatch(g, "a,b")) | 35 | print(mymatch(g, "one,two")) |
25 | print(mymatch(g, "a b")) | 36 | print(mymatch(g, "one two")) |
26 | print(mymatch(g, ", b")) | 37 | print(mymatch(g, "one,\n two,\nthree,")) |
27 | 38 | ||
diff --git a/examples/listId2.lua b/examples/listId2.lua index 75060f9..48157f1 100644 --- a/examples/listId2.lua +++ b/examples/listId2.lua | |||
@@ -1,32 +1,42 @@ | |||
1 | local m = require'lpeglabel' | 1 | local m = require'lpeglabel' |
2 | 2 | ||
3 | local errUndef = 0 | 3 | local terror = {} |
4 | local errId = 1 | ||
5 | local errComma = 2 | ||
6 | 4 | ||
7 | local terror = { | 5 | local function newError(s) |
8 | [errUndef] = "Error", | 6 | table.insert(terror, s) |
9 | [errId] = "Error: expecting an identifier", | 7 | return #terror |
10 | [errComma] = "Error: expecting ','", | 8 | end |
11 | } | 9 | |
10 | local errUndef = newError("undefined") | ||
11 | local errId = newError("expecting an identifier") | ||
12 | local errComma = newError("expecting ','") | ||
13 | |||
14 | local function calcline (s, i) | ||
15 | if i == 1 then return 1, 1 end | ||
16 | local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") | ||
17 | local col = #rest | ||
18 | return 1 + line, col ~= 0 and col or 1 | ||
19 | end | ||
12 | 20 | ||
13 | local g = m.P{ | 21 | local g = m.P{ |
14 | "S", | 22 | "S", |
15 | S = m.V"Id" * m.V"List", | 23 | S = m.V"Id" * m.V"List", |
16 | List = -m.P(1) + ("," + m.T(errComma)) * m.V"Id" * m.V"List", | 24 | List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", |
17 | Id = m.R'az'^1 + m.T(errId), | 25 | Id = m.V"Sp" * m.R'az'^1, |
26 | Comma = m.V"Sp" * ",", | ||
27 | Sp = m.S" \n\t"^0, | ||
18 | } | 28 | } |
19 | 29 | ||
20 | function mymatch (g, s) | 30 | function mymatch (g, s) |
21 | local r, e, sfail = g:match(s) | 31 | local r, e, sfail = g:match(s) |
22 | if not r then | 32 | if not r then |
23 | return r, terror[e] .. " before '" .. sfail .. "'" | 33 | local line, col = calcline(s, #s - #sfail) |
34 | local msg = "Error at line " .. line .. " (col " .. col .. "): " | ||
35 | return r, msg .. terror[e] .. " before '" .. sfail .. "'" | ||
24 | end | 36 | end |
25 | return r | 37 | return r |
26 | end | 38 | end |
27 | 39 | ||
28 | print(mymatch(g, "a,b")) | 40 | print(mymatch(g, "one,two")) |
29 | print(mymatch(g, "a b")) | 41 | print(mymatch(g, "one two")) |
30 | print(mymatch(g, ", b")) | 42 | print(mymatch(g, "one,\n two,\nthree,")) |
31 | |||
32 | |||
diff --git a/examples/listIdCatch.lua b/examples/listIdCatch.lua index 38ad2e5..3cbc834 100644 --- a/examples/listIdCatch.lua +++ b/examples/listIdCatch.lua | |||
@@ -1,25 +1,45 @@ | |||
1 | local m = require'lpeglabel' | 1 | local m = require'lpeglabel' |
2 | 2 | ||
3 | local errUndef, errId, errComma = 0, 1, 2 | 3 | local terror = {} |
4 | 4 | ||
5 | local terror = { | 5 | local function newError(s) |
6 | [errUndef] = "Error", | 6 | table.insert(terror, s) |
7 | [errId] = "Error: expecting an identifier", | 7 | return #terror |
8 | [errComma] = "Error: expecting ','", | 8 | end |
9 | } | 9 | |
10 | local errUndef = newError("undefined") | ||
11 | local errId = newError("expecting an identifier") | ||
12 | local errComma = newError("expecting ','") | ||
13 | |||
14 | local function calcline (s, i) | ||
15 | if i == 1 then return 1, 1 end | ||
16 | local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") | ||
17 | local col = #rest | ||
18 | return 1 + line, col ~= 0 and col or 1 | ||
19 | end | ||
10 | 20 | ||
11 | g = m.P{ | 21 | local g = m.P{ |
12 | "S", | 22 | "S", |
13 | S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), | 23 | S = m.Lc(m.Lc(m.V"Id" * m.V"List", m.V"ErrId", errId), |
14 | m.V"ErrComma", errComma), | 24 | m.V"ErrComma", errComma), |
15 | List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List", | 25 | List = -m.P(1) + (m.V"Comma" + m.T(errComma)) * (m.V"Id" + m.T(errId)) * m.V"List", |
16 | Id = m.R'az'^1 + m.T(errId), | 26 | Id = m.V"Sp" * m.R'az'^1, |
17 | Comma = "," + m.T(errComma), | 27 | Comma = m.V"Sp" * ",", |
28 | Sp = m.S" \n\t"^0, | ||
18 | ErrId = m.Cc(errId) / terror, | 29 | ErrId = m.Cc(errId) / terror, |
19 | ErrComma = m.Cc(errComma) / terror | 30 | ErrComma = m.Cc(errComma) / terror |
20 | } | 31 | } |
21 | 32 | ||
22 | print(g:match("a,b")) | 33 | function mymatch (g, s) |
23 | print(g:match("a b")) | 34 | local r, e, sfail = g:match(s) |
24 | print(g:match(",b")) | 35 | if not r then |
36 | local line, col = calcline(s, #s - #sfail) | ||
37 | local msg = "Error at line " .. line .. " (col " .. col .. "): " | ||
38 | return r, msg .. terror[e] .. " before '" .. sfail .. "'" | ||
39 | end | ||
40 | return r | ||
41 | end | ||
25 | 42 | ||
43 | print(mymatch(g, "one,two")) | ||
44 | print(mymatch(g, "one two")) | ||
45 | print(mymatch(g, "one,\n two,\nthree,")) | ||
diff --git a/examples/listIdRe1.lua b/examples/listIdRe1.lua index fc213bc..361e53f 100644 --- a/examples/listIdRe1.lua +++ b/examples/listIdRe1.lua | |||
@@ -1,27 +1,37 @@ | |||
1 | local re = require 'relabel' | 1 | local re = require 'relabel' |
2 | 2 | ||
3 | local function calcline (s, i) | ||
4 | if i == 1 then return 1, 1 end | ||
5 | local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") | ||
6 | local col = #rest | ||
7 | return 1 + line, col ~= 0 and col or 1 | ||
8 | end | ||
9 | |||
3 | local g = re.compile[[ | 10 | local g = re.compile[[ |
4 | S <- Id List | 11 | S <- Id List |
5 | List <- !. / (',' / %{2}) Id List | 12 | List <- !. / (',' / %{2}) (Id / %{1}) List |
6 | Id <- [a-z] / %{1} | 13 | Id <- Sp [a-z]+ |
14 | Comma <- Sp ',' | ||
15 | Sp <- %s* | ||
7 | ]] | 16 | ]] |
8 | 17 | ||
9 | function mymatch (g, s) | 18 | function mymatch (g, s) |
10 | local r, e, sfail = g:match(s) | 19 | local r, e, sfail = g:match(s) |
11 | if not r then | 20 | if not r then |
21 | local line, col = calcline(s, #s - #sfail) | ||
22 | local msg = "Error at line " .. line .. " (col " .. col .. ")" | ||
12 | if e == 1 then | 23 | if e == 1 then |
13 | return r, "Error: expecting an identifier before '" .. sfail .. "'" | 24 | return r, msg .. ": expecting an identifier before '" .. sfail .. "'" |
14 | elseif e == 2 then | 25 | elseif e == 2 then |
15 | return r, "Error: expecting ',' before '" .. sfail .. "'" | 26 | return r, msg .. ": expecting ',' before '" .. sfail .. "'" |
16 | else | 27 | else |
17 | return r, "Error" | 28 | return r, msg |
18 | end | 29 | end |
19 | end | 30 | end |
20 | return r | 31 | return r |
21 | end | 32 | end |
22 | |||
23 | print(mymatch(g, "a,b")) | ||
24 | print(mymatch(g, "a b")) | ||
25 | print(mymatch(g, ", b")) | ||
26 | 33 | ||
34 | print(mymatch(g, "one,two")) | ||
35 | print(mymatch(g, "one two")) | ||
36 | print(mymatch(g, "one,\n two,\nthree,")) | ||
27 | 37 | ||
diff --git a/examples/listIdRe2.lua b/examples/listIdRe2.lua index 4cd140b..ccb7ce5 100644 --- a/examples/listIdRe2.lua +++ b/examples/listIdRe2.lua | |||
@@ -1,35 +1,48 @@ | |||
1 | local re = require 'relabel' | 1 | local re = require 'relabel' |
2 | 2 | ||
3 | local errUndef, errId, errComma = 0, 1, 2 | 3 | local errinfo = { |
4 | 4 | {"errUndef", "undefined"}, | |
5 | local terror = { | 5 | {"errId", "expecting an identifier"}, |
6 | [errUndef] = "Error", | 6 | {"errComma", "expecting ','"}, |
7 | [errId] = "Error: expecting an identifier", | ||
8 | [errComma] = "Error: expecting ','", | ||
9 | } | 7 | } |
10 | 8 | ||
11 | local tlabels = { ["errUndef"] = errUndef, | 9 | local errmsgs = {} |
12 | ["errId"] = errId, | 10 | local labels = {} |
13 | ["errComma"] = errComma } | 11 | |
12 | for i, err in ipairs(errinfo) do | ||
13 | errmsgs[i] = err[2] | ||
14 | labels[err[1]] = i | ||
15 | end | ||
16 | |||
17 | re.setlabels(labels) | ||
18 | |||
19 | local function calcline (s, i) | ||
20 | if i == 1 then return 1, 1 end | ||
21 | local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") | ||
22 | local col = #rest | ||
23 | return 1 + line, col ~= 0 and col or 1 | ||
24 | end | ||
14 | 25 | ||
15 | re.setlabels(tlabels) | ||
16 | 26 | ||
17 | local g = re.compile[[ | 27 | local g = re.compile[[ |
18 | S <- Id List | 28 | S <- Id List |
19 | List <- !. / (',' / %{errComma}) Id List | 29 | List <- !. / (',' / %{errComma}) (Id / %{errId}) List |
20 | Id <- [a-z] / %{errId} | 30 | Id <- Sp [a-z]+ |
31 | Comma <- Sp ',' | ||
32 | Sp <- %s* | ||
21 | ]] | 33 | ]] |
22 | 34 | ||
23 | function mymatch (g, s) | 35 | function mymatch (g, s) |
24 | local r, e, sfail = g:match(s) | 36 | local r, e, sfail = g:match(s) |
25 | if not r then | 37 | if not r then |
26 | return r, terror[e] .. " before '" .. sfail .. "'" | 38 | local line, col = calcline(s, #s - #sfail) |
39 | local msg = "Error at line " .. line .. " (col " .. col .. "): " | ||
40 | return r, msg .. errmsgs[e] .. " before '" .. sfail .. "'" | ||
27 | end | 41 | end |
28 | return r | 42 | return r |
29 | end | 43 | end |
30 | |||
31 | print(mymatch(g, "a,b")) | ||
32 | print(mymatch(g, "a b")) | ||
33 | print(mymatch(g, ", b")) | ||
34 | 44 | ||
45 | print(mymatch(g, "one,two")) | ||
46 | print(mymatch(g, "one two")) | ||
47 | print(mymatch(g, "one,\n two,\nthree,")) | ||
35 | 48 | ||