diff options
| author | Sergio Queiroz <sqmedeiros@gmail.com> | 2016-12-13 13:53:49 -0300 |
|---|---|---|
| committer | Sergio Queiroz <sqmedeiros@gmail.com> | 2016-12-13 13:53:49 -0300 |
| commit | 09fab0decb7df93528ab40fcfd99587e9074c64f (patch) | |
| tree | ecd7a763c7a08712f122945bb5ce1ed7d7e5f077 | |
| parent | d80821d79376671371c15ded562fbe1a9bebc635 (diff) | |
| parent | 1322d612d72ac658f2aa443dca94954b819c0993 (diff) | |
| download | lpeglabel-09fab0decb7df93528ab40fcfd99587e9074c64f.tar.gz lpeglabel-09fab0decb7df93528ab40fcfd99587e9074c64f.tar.bz2 lpeglabel-09fab0decb7df93528ab40fcfd99587e9074c64f.zip | |
Merge branch 'recoveryresume'
| -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") | ||
