├── .gitignore ├── README.md ├── leftry-scm-3.rockspec ├── leftry.lua ├── leftry ├── ast.lua ├── elements │ ├── any.lua │ ├── factor.lua │ ├── opt.lua │ ├── rep.lua │ ├── span.lua │ ├── term.lua │ ├── traits.lua │ ├── traits │ │ ├── hash.lua │ │ ├── left_nonterminals.lua │ │ ├── search_left_nonterminal.lua │ │ └── search_left_nonterminals.lua │ └── utils.lua ├── grammar.lua ├── immutable │ ├── memoize.lua │ └── set.lua ├── initializers.lua ├── language │ ├── line.lua │ └── lua.lua ├── reducers.lua ├── trait.lua └── utils.lua ├── prototype.lua └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | jit/ 2 | profile.lua 3 | .DS_Store 4 | strict.lua 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leftry - A left recursion enabled recursive-descent parser combinator library. # 2 | 3 | This library is for creating and composing parsers. 4 | 5 | [Example Lua Parser](http://github.com/meric/l2l/blob/master/l2l/lua.lua#L410) 6 | 7 | For example: 8 | 9 | ``` 10 | local grammar = require("leftry") 11 | local factor = grammar.factor 12 | local span = grammar.span 13 | 14 | # Declaring a Non-Terminal, "A" 15 | local A = factor("A", function(A) return 16 | span(A, "1"), # 1st alternative, A := A "1" 17 | "1" # 2nd alternative, A := "1" 18 | end) 19 | 20 | # Declaring a Non-Terminal, "B" 21 | local B = factor("B", function(B) return 22 | span(B, "2"), # 1st alternative, B := B "2" 23 | A # 2nd alternative, B := A 24 | end) 25 | 26 | # Using the composed parser. 27 | # The first argument is the input string. 28 | # The second argument is the string index to start from. 29 | print(B("111122222", 1)) 30 | ``` 31 | 32 | This creates a parser `B` that can parse the string "111122222". 33 | 34 | The purpose of the anonymous function in declaration of the non-terminal 35 | enables self-reference and referencing other non-terminals that are not fully 36 | initialized yet. 37 | 38 | 39 | ## Install ## 40 | 41 | `luarocks install --server=http://luarocks.org/dev leftry` 42 | 43 | ## Algorithm ## 44 | 45 | First trace with a [left-factored](http://www.csd.uwo.ca/~moreno//CS447/Lectures/Syntax.html/node9.html) 46 | version of the grammar, then apply the left-recursive grammar. Like how a human would intuitively do it. 47 | 48 | ## Other Top-down left recursion enabled parser combinator implementations ## 49 | 50 | * http://hafiz.myweb.cs.uwindsor.ca/xsaiga/fullAg.html 51 | * https://github.com/djspiewak/gll-combinators 52 | 53 | ## Running unit tests ## 54 | 55 | `lua test.lua` 56 | 57 | ## Usage ## 58 | 59 | * A parser has the following function signature. 60 | 61 | ``` 62 | rest, values = parser(invariant, position, [peek]) 63 | ``` 64 | 65 | 1. `rest` is the next index to parse. `rest-1` is the last index of the parsed 66 | value. If `rest` is `nil`, it means the parse is invalid. 67 | 2. `values` is the data created as a result of a successful parse. 68 | 3. `invariant` is the Lua string that is being parsed. 69 | 4. `position` is the integer index to start the parse from. 70 | It must be 1 or greater. 71 | 5. `peek` (optional). If true, validates only, and `values` will be `nil`. 72 | Validating without creating the data is several times faster. 73 | 74 | * As iterator: 75 | 76 | The function signature of a parser allows it to be used as a Lua iterator 77 | in a for-loop. 78 | 79 | ``` 80 | local actual = {} 81 | for rest, values in span("1", "2", "3"), "123123123", 1 do 82 | table.insert(actual, rest) 83 | end 84 | -- actual == {4, 7, 10} 85 | ``` 86 | 87 | This can be useful, for example in a programming language parser, to iterate 88 | through each parsed statement. 89 | 90 | * Composition: 91 | 92 | Parsers can be nested. Left recursion is allowed. 93 | 94 | ``` 95 | local A = factor("A", function(A) return 96 | span(A, "1"), "1" 97 | end) 98 | local B = factor("B", function(B) return 99 | span(B, "2"), A 100 | end) 101 | local src = "11112222" 102 | assert(B(src, 1, true) == #src + 1) 103 | ``` 104 | 105 | * Data initialization: 106 | 107 | You can customise how the data is generated from a parse. 108 | 109 | ``` 110 | local A = factor("A", function(A) return 111 | span(A, "1") % function(initial, value) 112 | return (initial or "") .. value 113 | end, "1" 114 | end) 115 | local src = "111" 116 | A(src, 1) -- returns #src + 1, "111" 117 | ``` 118 | 119 | ## Left recursion ## 120 | 121 | Leftry can handle some examples of left recursion. 122 | 123 | * Simple left recursion: 124 | 125 | ``` 126 | local A = factor("A", function(A) return 127 | span(A, "1"), "1" 128 | end) 129 | ``` 130 | 131 | * Nested left recursion: 132 | 133 | ``` 134 | local A = factor("A", function(A) return 135 | span(A, "1"), "1" 136 | end) 137 | local B = factor("B", function(B) return 138 | span(B, "2"), A 139 | end) 140 | ``` 141 | 142 | ## Performance ## 143 | 144 | Performance of the built-in Lua parser. 145 | 146 | * Macbook Pro 2.6 Ghz i7 with 16 GB RAM: 147 | * Lua: 148 | 1. Validate the grammar of around 0.4 megabytes of Lua per second. 149 | 2. Parse 0.1 megabytes of Lua into abstract syntax tree representation per second. 150 | * LuaJIT: 151 | 1. Validate the grammar of around 4 megabytes of Lua per second. 152 | 2. Parse 0.68 megabytes of Lua into abstract syntax tree representation per second. 153 | * For comparison: 154 | 1. Lua interpreter can load a 15 megabyte Lua function in one second. 155 | 2. LuaJIT can load a 25 megabyte Lua function in one second. 156 | 157 | 158 | ## Elements ## 159 | 160 | * `factor(name, generate, [initializer])` 161 | 162 | Create a non-terminal element. 163 | 164 | * `name` is the tostring value of the element. 165 | * `generate` is the function that, when called with this element, returns the 166 | definition of this non-terminal. The values returned with this function 167 | will be wrapped in an `any`. You may optionally, explicitly return a single 168 | `any` that contains all the alternatives. Strings literals are 169 | automatically converted into `term` elements. 170 | * `initializer` is the function that will be called with values parsed 171 | from this element to let the user convert the parsed value into something 172 | useful. See "Data Constructors" section. 173 | 174 | Usage: 175 | 176 | ``` 177 | local A = factor("A", function(A) return 178 | span(A, "1"), "1" 179 | end) 180 | ``` 181 | 182 | * `rep(element, [reducer])` 183 | 184 | Create a repeating element. It can be used only in an `any` or a `span`. 185 | 186 | * `element` is the element that can appear 0 or more times (if this element 187 | is in a `span`), or 1 or more times (if this element is in an `any`). 188 | Strings literals are automatically converted into `term` elements. 189 | * `reducer` is the function that will be called with values parsed 190 | from this element to let the user convert the parsed values into something 191 | useful. See "Data Constructors" section. 192 | 193 | Usage: 194 | 195 | ``` 196 | span( 197 | "1", 198 | rep("2", function(a, b) return (a or "")..b end), 199 | "3") 200 | ``` 201 | * `opt(element)` 202 | 203 | Create an optional element. It can be used only in a `span`. 204 | 205 | * `element` is the element that can appear 0 or 1 times. 206 | Strings literals are automatically converted into `term` elements. 207 | 208 | Usage: 209 | 210 | ``` 211 | span("1", opt("2"), "3") 212 | ``` 213 | 214 | * `any(...)` 215 | 216 | Create an any element. The any element contains a set of alternatives, that 217 | will be attempted from left to right. 218 | 219 | The `any` element is used internally to wrap the alternatives returned 220 | by the `factor` "generate" function. 221 | 222 | You do not need to worry about using `any`. 223 | 224 | * `span(...)` 225 | 226 | Create an span element. The span element can be assigned a reducer with the 227 | `%` operator. See the "Data Constructors" section. 228 | 229 | * `...` the children of the span element. Each child must be encountered in 230 | order to provide a valid parse, unless it is an `opt` or `rep` element. 231 | 232 | Usage: 233 | 234 | ``` 235 | local A = factor("A", function(A) return 236 | span(A, "1") % function(initial, value) 237 | return (initial or "") .. value 238 | end, "1" 239 | end) 240 | ``` 241 | 242 | The span element can also be assigned a spacing rule using the `^` operator: 243 | 244 | ``` 245 | local function span(...) 246 | -- Apply spacing rule to all spans we use in the Lua grammar. 247 | return grammar.span(...) ^ {spacing=spacing, spaces=" \t\r\n"} 248 | end 249 | ``` 250 | 251 | See built-in Lua parser for an example on what spacing function looks like. 252 | 253 | * `term(literal, [initializer])` 254 | 255 | Create a literal element. 256 | 257 | * `literal` the string literal that must be encountered to provide a valid 258 | parse. 259 | * `initializer` is the function that will be called with the literal whenever 260 | there is a valid parse. 261 | 262 | Usage: 263 | 264 | ``` 265 | term("hello") 266 | ``` 267 | 268 | ## Data Constructors ## 269 | 270 | ### Initializer ### 271 | 272 | * `factor` 273 | 274 | ``` 275 | function( 276 | value, -- The parsed value to transform. 277 | self, -- The element responsible for parsing being transformed. 278 | position, -- The index where `value` was found. 279 | rest, -- The next index after `value` ends. 280 | choice) -- The index of the alternative `value` was parsed from. 281 | 282 | -- Default implementation 283 | return value 284 | end 285 | ``` 286 | 287 | * `term` 288 | 289 | ``` 290 | function( 291 | value, -- The parsed value to transform. (The literal.) 292 | self, -- The element responsible for parsing being transformed. 293 | position, -- The index where `value` was found. 294 | rest) -- The next index after `value` ends. 295 | 296 | -- Default implementation 297 | return value 298 | end 299 | ``` 300 | 301 | ### Reducer ### 302 | 303 | Reducers are functions that will be folded over each value that will be parsed. 304 | 305 | * `span` 306 | 307 | ``` 308 | function( 309 | accumulated, -- The accumulated value. In the first iteration it is `nil`. 310 | value, -- The current value that is parsed. 311 | self, -- The element parsing the current value. 312 | position, -- The index where the current value begins. 313 | rest, -- The next index after `value` ends. 314 | i) -- `value` belongs to the `i`th element of this `span` element. 315 | 316 | -- Default implementation 317 | return rawset(initial or {}, i, value) 318 | end 319 | ``` 320 | 321 | * `rep` 322 | 323 | ``` 324 | function( 325 | accumulated, -- The accumulated value. In the first iteration it is `nil`. 326 | value, -- The current value that is parsed. 327 | self, -- The element parsing the current value. 328 | position, -- The index where the current value begins. 329 | rest, -- The next index after `value` ends. 330 | i) -- The `i`th time the child element has been encountered. 331 | 332 | -- Default implementation 333 | return rawset(initial or {}, i, value) 334 | end 335 | ``` 336 | 337 | ## Caveats ## 338 | 339 | The current implementation does not enforce the following rules properly. 340 | 341 | 1. A `span` must have more than one child. 342 | 2. In a left recursion alternative, only the first element may be the 343 | left-recurring non-terminal. More than one consecutive left-recurring 344 | non-terminal is not supported, even if it currently works. 345 | 346 | ``` 347 | -- This is OK 348 | local A = factor("A", function(A) return 349 | span(A, "1", A, "2"), "1" 350 | end) 351 | -- This is not OK 352 | local A = factor("A", function(A) return 353 | span(A, A, "1", "2"), "1" 354 | end) 355 | ``` 356 | 3. An `any` element must not have any `opt` children. 357 | 4. A `rep` element that is a child of an `any` element requires 1 or more 358 | elements to match. 359 | 5. A `rep` element that is a child of an `span` element requires 0 or more 360 | elements to match. 361 | 6. The first nonterminal element of a span that is part of a left recursion 362 | path, cannot be wrapped in `opt` or `rep`. 363 | 364 | ## TODO ## 365 | 366 | * ~~Implement Lua grammar in Leftry to prove it can handle the grammar of a 367 | programming language.~~ 368 | * ~~Implement whitespacing support.~~ 369 | * ~~Add appropriate data initializers for the builtin Lua parser, to override the 370 | default ones.~~ 371 | 372 | * Test the following grammar: 373 | 374 | ``` 375 | "b" 376 | | s ~ s ^^ { _ + _ } 377 | | s ~ s ~ s ^^ { _ + _ + _ } 378 | https://github.com/djspiewak/gll-combinators 379 | ``` 380 | -------------------------------------------------------------------------------- /leftry-scm-3.rockspec: -------------------------------------------------------------------------------- 1 | package = "Leftry" 2 | version = "scm-3" 3 | source = { 4 | url = "git://github.com/meric/leftry" 5 | } 6 | description = { 7 | summary = "A left recursion enabled recursive-descent parser combinator library.", 8 | detailed = [[ 9 | This library is for creating and composing parsers. 10 | 11 | For example: 12 | 13 | ``` 14 | local grammar = require("leftry") 15 | local factor = grammar.factor 16 | local span = grammar.span 17 | local A = factor("A", function(A) return 18 | span(A, "1"), "1" 19 | end) 20 | local B = factor("B", function(B) return 21 | span(B, "2"), A 22 | end) 23 | print(B("111122222", 1)) 24 | ``` 25 | 26 | This creates a parser `B` that can parse the string "111122222". 27 | ]], 28 | homepage = "http://github.com/meric/leftry", 29 | license = "MIT/X11" 30 | } 31 | dependencies = { 32 | "lua >= 5.2" 33 | } 34 | build = { 35 | type = "builtin", 36 | modules = { 37 | ["leftry"]="leftry.lua", 38 | ["leftry.elements.any"]="leftry/elements/any.lua", 39 | ["leftry.elements.factor"]="leftry/elements/factor.lua", 40 | ["leftry.elements.opt"]="leftry/elements/opt.lua", 41 | ["leftry.elements.rep"]="leftry/elements/rep.lua", 42 | ["leftry.elements.span"]="leftry/elements/span.lua", 43 | ["leftry.elements.term"]="leftry/elements/term.lua", 44 | ["leftry.elements.traits"]="leftry/elements/traits.lua", 45 | ["leftry.elements.traits.hash"]="leftry/elements/traits/hash.lua", 46 | ["leftry.elements.traits.left_nonterminals"]="leftry/elements/traits/left_nonterminals.lua", 47 | ["leftry.elements.traits.search_left_nonterminal"]="leftry/elements/traits/search_left_nonterminal.lua", 48 | ["leftry.elements.traits.search_left_nonterminals"]="leftry/elements/traits/search_left_nonterminals.lua", 49 | ["leftry.elements.utils"]="leftry/elements/utils.lua", 50 | ["leftry.immutable.memoize"]="leftry/immutable/memoize.lua", 51 | ["leftry.immutable.set"]="leftry/immutable/set.lua", 52 | ["leftry.language.lua"]="leftry/language/lua.lua", 53 | ["leftry.grammar"]="leftry/grammar.lua", 54 | ["leftry.trait"]="leftry/trait.lua", 55 | ["leftry.utils"]="leftry/utils.lua", 56 | ["leftry.ast"]="leftry/ast.lua", 57 | ["leftry.reducers"]="leftry/reducers.lua", 58 | ["leftry.initializers"]="leftry/initializers.lua", 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /leftry.lua: -------------------------------------------------------------------------------- 1 | require("leftry.elements.traits.hash") 2 | require("leftry.elements.traits.left_nonterminals") 3 | require("leftry.elements.traits.search_left_nonterminal") 4 | require("leftry.elements.traits.search_left_nonterminals") 5 | 6 | return { 7 | grammar = require("leftry.grammar"), 8 | span = require("leftry.elements.span"), 9 | any = require("leftry.elements.any"), 10 | term = require("leftry.elements.term"), 11 | factor = require("leftry.elements.factor"), 12 | opt = require("leftry.elements.opt"), 13 | rep = require("leftry.elements.rep"), 14 | utils = require("leftry.utils"), 15 | trait = require("leftry.trait"), 16 | } 17 | -------------------------------------------------------------------------------- /leftry/ast.lua: -------------------------------------------------------------------------------- 1 | local utils = require("leftry.utils") 2 | local prototype = utils.prototype 3 | local map = utils.map 4 | 5 | local unpack = table.unpack or unpack 6 | 7 | 8 | -- Create a reducer to be used with `span`. 9 | -- For now, see leftry/language/lua.lua for usage. 10 | local function reduce(name, indices, __tostring) 11 | local reverse = {} 12 | local template = {} 13 | local terms = {} 14 | local arguments = {} 15 | local proto = prototype(name, function(self, values, value, i, _, index, rest) 16 | if not values then 17 | values = setmetatable({index=index, rest=rest}, self) 18 | end 19 | if not indices or reverse[i] then 20 | values[i] = value 21 | end 22 | values.rest = rest 23 | return values 24 | end) 25 | 26 | proto.indices = indices 27 | proto.reverse = reverse 28 | proto.arguments = arguments 29 | proto.template = template 30 | proto.terms = terms 31 | 32 | function proto:match(pattern, g) 33 | if (not pattern or proto == pattern) and (not g or g(self)) then 34 | return self 35 | end 36 | for i, v in ipairs(arguments) do 37 | if self[v[2]] then 38 | if type(self[v[2]]) == "table" and self[v[2]].match then 39 | local value = self[v[2]]:match(pattern, g) 40 | if value ~= nil then 41 | return value 42 | end 43 | end 44 | end 45 | end 46 | return 47 | end 48 | 49 | function proto:gsub(pattern, f, g) 50 | -- In-place substitution. Highly recommended to make a :copy() before 51 | -- calling gsub. 52 | if (not pattern or proto == pattern) and (not g or g(self)) then 53 | return f(self, self) 54 | end 55 | for i, v in ipairs(arguments) do 56 | if self[v[2]] then 57 | if (not pattern or utils.hasmetatable(self[v[2]], pattern)) and 58 | (not g or g(self[v[2]])) then 59 | self[v[2]] = f(self[v[2]], self, v[2]) 60 | elseif type(self[v[2]]) == "table" and self[v[2]].gsub then 61 | self[v[2]] = self[v[2]]:gsub(pattern, f, g) 62 | end 63 | end 64 | end 65 | return self 66 | end 67 | 68 | 69 | if indices then 70 | for k, i in pairs(indices) do 71 | if type(k) == "string" then 72 | reverse[i] = k 73 | template[i] = true 74 | table.insert(arguments, {k, i}) 75 | else 76 | table.insert(terms, {k, i}) 77 | end 78 | end 79 | 80 | table.sort(terms, function(a, b) 81 | return a[1] < b[1] 82 | end) 83 | table.sort(arguments, function(a, b) 84 | return a[2] < b[2] 85 | end) 86 | 87 | function proto.new(...) 88 | local self = setmetatable({}, proto) 89 | for i, v in ipairs(arguments) do 90 | self[v[2]] = select(i, ...) 91 | end 92 | return self 93 | end 94 | 95 | for i, v in ipairs(terms) do 96 | -- Reliable cross-version way to find the nil index. 97 | local j =1 98 | while true do 99 | if template[j] == nil then 100 | break 101 | end 102 | j = j + 1 103 | end 104 | template[j] = v[2] 105 | end 106 | 107 | if not __tostring then 108 | __tostring = function(self) 109 | local t = "" 110 | for i, v in ipairs(template) do 111 | local value = self[i] 112 | t = t .. (v == true and tostring(value ~= nil and value or "") or v) 113 | end 114 | return t 115 | end 116 | end 117 | 118 | function proto:__index(index) 119 | if indices[index] then 120 | return self[indices[index]] 121 | end 122 | return proto[index] 123 | end 124 | 125 | function proto:__newindex(index, value) 126 | if indices[index] then 127 | self[indices[index]] = value 128 | else 129 | rawset(self, index, value) 130 | end 131 | end 132 | 133 | function proto:repr() 134 | return self 135 | end 136 | 137 | function proto:__eq(other) 138 | return tostring(self) == tostring(other) 139 | end 140 | 141 | function proto:copy() 142 | local t = setmetatable({}, proto) 143 | for i, v in ipairs(arguments) do 144 | if type(self[i]) == "string" then 145 | t[v[2]] = self[v[2]] 146 | elseif self[v[2]] then 147 | if self[v[2]].copy then 148 | t[v[2]] = self[v[2]]:copy() 149 | else 150 | t[v[2]] = self[v[2]] 151 | end 152 | end 153 | end 154 | return t 155 | end 156 | end 157 | proto.__tostring = __tostring 158 | return proto 159 | end 160 | 161 | -- Create an identity initializer to be used in function parsers and 162 | -- nonterminals. 163 | -- For now, see leftry/language/lua.lua for usage. 164 | local function id(name, key, __tostring, validate) 165 | local proto = prototype(name, function(self, value) 166 | if validate then 167 | local validated = validate(value) 168 | if validated ~= nil then 169 | value = validated 170 | end 171 | end 172 | return setmetatable({value}, self) 173 | end) 174 | 175 | function proto:__index(index) 176 | if index == (key or "value") then 177 | return self[1] or "" 178 | end 179 | return proto[index] 180 | end 181 | 182 | proto.__tostring = __tostring or function(self) 183 | return tostring(self[1] or "") 184 | end 185 | 186 | function proto:repr() 187 | return self 188 | end 189 | 190 | function proto:gsub(pattern, f, g) 191 | if (not pattern or proto == pattern) and (not g or g(self)) then 192 | return f(self, self) 193 | end 194 | return self 195 | end 196 | 197 | function proto:match(pattern, g) 198 | if (not pattern or proto == pattern) and (not g or g(self)) then 199 | return self 200 | end 201 | end 202 | 203 | function proto:__eq(p) 204 | return getmetatable(self) == getmetatable(p) and self[1] == p[1] 205 | end 206 | 207 | function proto:copy() 208 | return proto(self[1]) 209 | end 210 | return proto 211 | end 212 | 213 | 214 | local function const(name, value) 215 | local constant = {value} 216 | local proto = prototype(name, function(self) 217 | return constant 218 | end) 219 | setmetatable(constant, proto) 220 | function proto:__tostring() 221 | return tostring(value) 222 | end 223 | 224 | function proto:repr() 225 | return tostring(getmetatable(self)).."()" 226 | end 227 | 228 | function proto:gsub(pattern, f, g) 229 | if (not pattern or proto == pattern) and (not g or g(self)) then 230 | return f(self, self) 231 | end 232 | return self 233 | end 234 | 235 | function proto:match(pattern, g) 236 | if (not pattern or proto == pattern) and (not g or g(self)) then 237 | return self 238 | end 239 | end 240 | return proto 241 | end 242 | 243 | 244 | -- Create an initializer that casts an array (from `leftflat` or `rightflat`) 245 | -- into this type. 246 | -- For now, see leftry/language/lua.lua for usage. 247 | local function list(name, separator, __tostring, validate) 248 | local proto = prototype(name, function(self, obj, _, index, rest) 249 | obj = obj or {} 250 | obj.n = obj.n or #obj 251 | if validate then 252 | local validated = validate(obj) 253 | if validated ~= nil then 254 | obj = validated 255 | end 256 | end 257 | obj = setmetatable(obj, self) 258 | obj.index = index 259 | obj.rest = rest 260 | obj.n = obj.n or #obj 261 | return obj 262 | end) 263 | function proto:__index(index) 264 | if indices and indices[index] then 265 | return tostring(self[indices[index]]) 266 | end 267 | return proto[index] 268 | end 269 | 270 | function proto:repr() 271 | return self 272 | end 273 | 274 | 275 | function proto:match(pattern, g) 276 | -- if (not pattern or proto == pattern) and (not g or g(self)) then 277 | -- return self 278 | -- end 279 | for i=1, self.n do 280 | if (not pattern or utils.hasmetatable(self[i], pattern)) and 281 | (not g or g(self[i])) then 282 | return self[i] 283 | elseif type(self[i]) == "table" and self[i].match then 284 | local value = self[i]:match(pattern, g) 285 | if value ~= nil then 286 | return value 287 | end 288 | end 289 | end 290 | end 291 | 292 | function proto:gsub(pattern, f, g) 293 | for i=1, self.n do 294 | if type(self[i]) == "string" then 295 | if pattern == "string" then 296 | self[i] = f(self[i], self, i) 297 | else 298 | self[i] = self[i] 299 | end 300 | elseif (not pattern or utils.hasmetatable(self[i], pattern)) and 301 | (not g or g(self[i])) then 302 | self[i] = f(self[i], self, i) 303 | elseif type(self[i]) == "table" and self[i].gsub then 304 | self[i]=self[i]:gsub(pattern, f, g) 305 | end 306 | end 307 | return self 308 | end 309 | 310 | function proto:copy() 311 | local t = {n=self.n} 312 | for i=1, self.n do 313 | if type(self[i]) == "string" then 314 | t[i] = self[i] 315 | else 316 | if self[i] and self[i].copy then 317 | t[i] = self[i]:copy() 318 | else 319 | t[i] = self[i] 320 | end 321 | end 322 | end 323 | 324 | return proto(t, nil, self.index, self.rest) 325 | end 326 | 327 | function proto:insert(value) 328 | self.n = self.n + 1 329 | self[self.n] = value 330 | return self 331 | end 332 | 333 | function proto:__eq(other) 334 | return tostring(self) == tostring(other) 335 | end 336 | 337 | 338 | function proto:next(i) 339 | if i < self.n then 340 | return i + 1, self[i + 1] 341 | end 342 | end 343 | 344 | function proto:__ipairs() 345 | return proto.next, self, 0 346 | end 347 | 348 | proto.__tostring = __tostring or function(self) return 349 | table.concat(map(tostring, self, self.n), separator or "") 350 | end 351 | return proto 352 | end 353 | 354 | 355 | return { 356 | list = list, 357 | const = const, 358 | id = id, 359 | reduce = reduce 360 | } 361 | -------------------------------------------------------------------------------- /leftry/elements/any.lua: -------------------------------------------------------------------------------- 1 | local utils = require("leftry.utils") 2 | local termize = require("leftry.elements.utils").termize 3 | local invariantize = require("leftry.elements.utils").invariantize 4 | local traits = require("leftry.elements.traits") 5 | local memoize = require("leftry.immutable.memoize") 6 | local hash = traits.hash 7 | 8 | local prototype = utils.prototype 9 | local dotmap = utils.dotmap 10 | 11 | local torepresentation = utils.torepresentation 12 | 13 | local search_left_nonterminal = traits.search_left_nonterminal 14 | local search_left_nonterminals = traits.search_left_nonterminals 15 | local left_nonterminals = traits.left_nonterminals 16 | 17 | local any = prototype("any", function(self, ...) 18 | return setmetatable({dotmap(termize, ...)}, self) 19 | end) 20 | 21 | function any:__tostring() 22 | return torepresentation(any, self) 23 | end 24 | 25 | function any:index() 26 | self.cache = {} 27 | self.reverse = {} 28 | for i=1, 255 do 29 | self.cache[i] = {} 30 | end 31 | for i=1, #self do 32 | self.reverse[self[i]] = i 33 | local h = {hash(self[i])} 34 | for j=1, #h do 35 | table.insert(self.cache[h[j]], self[i]) 36 | end 37 | end 38 | return self.cache 39 | end 40 | 41 | local _search_left_nonterminals = memoize(search_left_nonterminals, 2) 42 | 43 | function any:__call(invariant, position, peek, expect, exclude, nonterminals) 44 | invariant = invariantize(invariant) 45 | if position > #invariant.source then 46 | return 47 | end 48 | local alts = (self.cache or self:index())[invariant.source:byte(position)] 49 | for i=1, #alts do 50 | if not exclude or not exclude[alts[i]] then 51 | -- Note: A `rep` element in `any` acts like a non-optional element. 52 | if not nonterminals or 53 | _search_left_nonterminals[alts[i]][nonterminals] then 54 | local rest, value = alts[i](invariant, position, peek, expect, 55 | exclude, nonterminals) 56 | if rest and rest > position then 57 | return rest, value, self.reverse[alts[i]] 58 | end 59 | end 60 | end 61 | end 62 | end 63 | 64 | return any 65 | -------------------------------------------------------------------------------- /leftry/elements/factor.lua: -------------------------------------------------------------------------------- 1 | local opt = require("leftry.elements.opt") 2 | local termize = require("leftry.elements.utils").termize 3 | local invariantize = require("leftry.elements.utils").invariantize 4 | local utils = require("leftry.utils") 5 | local traits = require("leftry.elements.traits") 6 | local any = require("leftry.elements.any") 7 | 8 | local set = require("leftry.immutable.set") 9 | local set_insert, set_empty = set.insert, set.empty 10 | 11 | local prototype = utils.prototype 12 | local copy = utils.copy 13 | local filter = utils.filter 14 | local search_left_nonterminal = traits.search_left_nonterminal 15 | local left_nonterminals = traits.left_nonterminals 16 | local unpack = table.unpack or unpack 17 | 18 | local factor = prototype("factor", function(self, name, canonize, initializer) 19 | return setmetatable({ 20 | name=name, 21 | canonize=canonize, 22 | initializer=initializer}, self) 23 | end) 24 | 25 | function factor.initializer(value, self, position, rest, choice) 26 | return value 27 | end 28 | 29 | function factor:__tostring() 30 | return self.name 31 | end 32 | 33 | function factor:setup() 34 | self.canon = self:canonize() 35 | if getmetatable(self.canon) ~= any then 36 | local canonize = self.canonize 37 | self.canonize = function() 38 | return any(canonize(self)) 39 | end 40 | self.canon = self:canonize() 41 | end 42 | self.setup = function() return self.canon end 43 | self.recursions = any(unpack(filter(function(alt) return 44 | search_left_nonterminal(alt, self) 45 | end, self.canon))) 46 | return self.canon 47 | end 48 | 49 | function factor:actualize() 50 | -- Prioritise left recursion alternatives. 51 | local canon = self.canon 52 | table.sort(canon, function(a, b) 53 | return search_left_nonterminal(a, self) 54 | and not search_left_nonterminal(b, self) 55 | end) 56 | for i, v in ipairs(canon) do 57 | table.insert(self, v) 58 | end 59 | self.canonize = function() return canon end 60 | self.actualize = function() end 61 | end 62 | 63 | function factor:wrap(invariant, position, peek, expect, exclude, skip) 64 | local rest, value, choice = self.canon(invariant, position, peek, expect, 65 | exclude, skip) 66 | if not peek and rest and rawget(self, "initializer") then 67 | return rest, self.initializer(value, self, position, rest, choice) 68 | end 69 | return rest, value 70 | end 71 | 72 | function factor:measure(invariant, rest, exclude, skip) 73 | local sections 74 | local done = rest 75 | local recursions = self.recursions 76 | local length = #invariant.source 77 | 78 | while rest and length >= rest do 79 | local position = rest 80 | for i=1, #recursions do 81 | local alt = recursions[i] 82 | if not exclude[alt] then 83 | rest = alt(invariant, position, true, nil, nil, skip) 84 | if rest and rest > position then 85 | done = rest 86 | if not sections then 87 | sections = {position, rest} 88 | else 89 | table.insert(sections, position) 90 | table.insert(sections, rest) 91 | end 92 | break 93 | end 94 | end 95 | end 96 | end 97 | return done, sections 98 | end 99 | 100 | function factor.trace(top, invariant, skip, sections) 101 | local span = require("leftry.elements.span") 102 | local index = #sections/2 103 | local paths = {} 104 | while index > 0 do 105 | local position = sections[index*2-1] 106 | local expect = sections[index*2] 107 | local rest, _, choice = top.canon(invariant, position, true, expect, 108 | nil, skip) 109 | local alternative = top.canon[choice] 110 | table.insert(paths, {choice=choice, expect=expect, nonterminal=top}) 111 | 112 | if getmetatable(alternative) ~= factor then 113 | index = index - 1 114 | top = alternative 115 | while getmetatable(top) == span do 116 | top = top[1] 117 | end 118 | else 119 | top = alternative 120 | end 121 | -- assert(getmetatable(top) == factor) 122 | end 123 | 124 | return top, paths 125 | end 126 | 127 | function factor:left(invariant, position, peek, expect, exclude) 128 | if position > #invariant.source then 129 | return 130 | end 131 | 132 | exclude = set_insert(exclude or set_empty, self) 133 | 134 | local prefix_rest, value, choice = self.canon(invariant, 135 | position, peek, nil, exclude) 136 | 137 | if not prefix_rest then 138 | return 139 | elseif prefix_rest == expect then 140 | return prefix_rest, not peek and self.initializer(value, self, position, 141 | prefix_rest, choice) or nil 142 | end 143 | 144 | local skip = self.left_nonterminals 145 | local rest, sections = self:measure(invariant, prefix_rest, exclude, skip) 146 | 147 | if expect and rest ~= expect then 148 | return 149 | end 150 | 151 | if peek then 152 | if invariant.subscribers and invariant.subscribers[self] then 153 | invariant.subscribers[self](self, position, rest, peek) 154 | end 155 | return rest 156 | end 157 | 158 | if not sections then 159 | return rest, self.initializer(value, self, position, rest, choice) 160 | end 161 | 162 | local top, paths = self:trace(invariant, skip, sections) 163 | 164 | if not paths then 165 | return 166 | end 167 | 168 | local paths_length = #paths 169 | while getmetatable(top) == factor do 170 | local _, __, choice = top.canon(invariant, position, true, prefix_rest) 171 | if not choice or _ ~= prefix_rest then 172 | break 173 | end 174 | table.insert(paths, {choice=choice, expect=prefix_rest, nonterminal=top}) 175 | top = top.canon[choice] 176 | end 177 | 178 | if paths_length == #paths then 179 | error("cannot find prefix") 180 | end 181 | 182 | local rest, value 183 | for i=#paths, 1, -1 do 184 | local path = paths[i] 185 | local top = path.nonterminal 186 | local alternative = top.canon[path.choice] 187 | if i == #paths then 188 | rest, value = alternative(invariant, position, peek, path.expect) 189 | elseif getmetatable(alternative) == factor then 190 | rest, value = paths[i+1].expect, value 191 | else 192 | rest, value = alternative(invariant, position, peek, path.expect, 193 | nil, nil, paths[i+1].expect, value) 194 | end 195 | value = top.initializer(value, self, position, rest, path.choice) 196 | end 197 | if invariant.subscribers and invariant.subscribers[self] then 198 | invariant.subscribers[self](self, position, rest, peek) 199 | end 200 | return rest, value 201 | end 202 | 203 | function factor:call(invariant, position, peek, expect, exclude) 204 | if not self.canon then 205 | self:setup() 206 | self:actualize() 207 | end 208 | if not self.left_nonterminals then 209 | self.left_nonterminals = left_nonterminals(self) 210 | end 211 | if search_left_nonterminal(self.canon, self) then 212 | self.call = self.left 213 | else 214 | self.call = self.wrap 215 | end 216 | return self:call(invariant, position, peek, expect, exclude) 217 | end 218 | 219 | function factor:__call(invariant, position, peek, expect, exclude, skip) 220 | invariant = invariantize(invariant) 221 | local rest, value 222 | if skip and skip[self] then 223 | rest, value = self.canon(invariant, position, peek, expect, exclude, skip) 224 | else 225 | rest, value = self:call(invariant, position, peek, expect, exclude) 226 | end 227 | if rest and invariant.events[self] then 228 | invariant.events[self](position, rest, value, peek) 229 | end 230 | return rest, value 231 | end 232 | 233 | function factor:match(text, nonterminal, pattern, index) 234 | assert(type(text) == "string") 235 | pattern = pattern or nonterminal 236 | local invariant = invariantize(text) 237 | local matched, index 238 | self({ 239 | source=text, 240 | events={ 241 | [nonterminal] = function(position, rest) 242 | if matched == nil and position >= (index or 1) then 243 | if pattern(invariant, position, true) then 244 | matched, index = select(2, pattern(invariant, position)), position 245 | end 246 | end 247 | end 248 | }}, 1, true) 249 | return matched 250 | end 251 | 252 | function factor:find(text, nonterminal, pattern, from) 253 | assert(type(text) == "string") 254 | pattern = pattern or nonterminal 255 | local invariant = invariantize(text) 256 | local index, to 257 | self({ 258 | source=text, 259 | events={ 260 | [nonterminal] = function(position, rest) 261 | if index == nil and position >= (from or 1) then 262 | if pattern(invariant, position, true) then 263 | index = position 264 | to = rest - 1 265 | end 266 | end 267 | end 268 | }}, 1, true) 269 | return index, to 270 | end 271 | 272 | function factor:gfind(text, nonterminal, pattern) 273 | pattern = pattern or nonterminal 274 | local invariant = invariantize(text) 275 | local thread = coroutine.create(function() 276 | 277 | self({source=text, events={ 278 | [nonterminal] = function(position, rest) 279 | if pattern(invariant, position, true) then 280 | coroutine.yield(position, rest - 1) 281 | end 282 | end} 283 | }, 1, true) 284 | end) 285 | return function() 286 | 287 | local ok, position, rest = coroutine.resume(thread) 288 | -- print(ok, "hello", position, rest) 289 | if ok then 290 | return position, rest 291 | end 292 | end 293 | end 294 | 295 | function factor:gmatch(text, nonterminal, pattern) 296 | pattern = pattern or nonterminal 297 | local invariant = invariantize(text) 298 | local thread = coroutine.create(function() 299 | self({source=text, events={ 300 | [nonterminal] = function(position, rest) 301 | if pattern(invariant, position, true) then 302 | coroutine.yield(select(2, pattern(invariant, position)), position) 303 | end 304 | end} 305 | }, 1, true) 306 | end) 307 | return function() 308 | local ok, position, rest = coroutine.resume(thread) 309 | if ok then 310 | return position, rest 311 | end 312 | end 313 | end 314 | 315 | return factor 316 | -------------------------------------------------------------------------------- /leftry/elements/opt.lua: -------------------------------------------------------------------------------- 1 | local utils = require("leftry.utils") 2 | local termize = require("leftry.elements.utils").termize 3 | 4 | local prototype = utils.prototype 5 | local torepresentation = utils.torepresentation 6 | 7 | local opt = prototype("opt", function(self, element) 8 | return setmetatable({element=termize(element)}, self) 9 | end) 10 | 11 | function opt:__tostring() 12 | return torepresentation(opt, {self.element}) 13 | end 14 | 15 | function opt:__call(invariant, position, peek) 16 | local rest, value = self.element(invariant, position, peek) 17 | if not rest then 18 | return position 19 | end 20 | return rest, value 21 | end 22 | 23 | return opt 24 | -------------------------------------------------------------------------------- /leftry/elements/rep.lua: -------------------------------------------------------------------------------- 1 | local utils = require("leftry.utils") 2 | local termize = require("leftry.elements.utils").termize 3 | 4 | local prototype = utils.prototype 5 | local torepresentation = utils.torepresentation 6 | 7 | local rep = prototype("rep", function(self, element, reducer) 8 | return setmetatable({element=termize(element), reducer=reducer}, self) 9 | end) 10 | 11 | function rep:__tostring() 12 | return torepresentation(rep, {self.element}) 13 | end 14 | 15 | function rep.reducer(initial, value, i, self, position, rest) 16 | return rawset(initial or {}, i, value) 17 | end 18 | 19 | function rep:__mod(reducer) 20 | return rawset(self, "reducer", reducer) 21 | end 22 | 23 | function rep:__call(invariant, position, peek) 24 | local initial 25 | local rest, value 26 | local element = self.element 27 | local i = 1 28 | while true do 29 | local sub = rest or position 30 | if sub > #invariant.source then 31 | return sub, initial 32 | end 33 | rest, value = element(invariant, sub, peek) 34 | if not rest then 35 | return sub, initial 36 | end 37 | if not peek then 38 | initial = self.reducer(initial, value, i, self, sub, rest) 39 | i = i + 1 40 | end 41 | end 42 | end 43 | 44 | return rep 45 | -------------------------------------------------------------------------------- /leftry/elements/span.lua: -------------------------------------------------------------------------------- 1 | local opt = require("leftry.elements.opt") 2 | local termize = require("leftry.elements.utils").termize 3 | local invariantize = require("leftry.elements.utils").invariantize 4 | local utils = require("leftry.utils") 5 | local factor = require("leftry.elements.factor") 6 | local traits = require("leftry.elements.traits") 7 | 8 | local prototype = utils.prototype 9 | local dotmap = utils.dotmap 10 | local map = utils.map 11 | local torepresentation = utils.torepresentation 12 | 13 | local search_left_nonterminal = utils.search_left_nonterminal 14 | local left_nonterminals = traits.left_nonterminals 15 | 16 | 17 | local term = require("leftry.elements.term") 18 | local span = prototype("span", function(self, ...) 19 | assert(select("#", ...) > 1, "span must consist of two or more elements.") 20 | return setmetatable({dotmap(termize, ...)}, self) 21 | end) 22 | 23 | function span:__pow(options) 24 | -- Apply white spacing rule. 25 | for k, v in pairs(options) do 26 | self[k] = v 27 | assert(k == "spacing" or k == "spaces") 28 | end 29 | return self 30 | end 31 | 32 | function span.reducer(initial, value, i, self, position, rest) 33 | return rawset(initial or {}, i, value) 34 | end 35 | 36 | function span:__mod(reducer) 37 | return rawset(self, "reducer", reducer) 38 | end 39 | 40 | function span:__tostring() 41 | return torepresentation(span, self) 42 | end 43 | 44 | function span:__call(invariant, position, peek, expect, met, nonterminals, 45 | given_rest, given_value) 46 | invariant = invariantize(invariant) 47 | if position > #invariant.source or met and met[self[1]] then 48 | return 49 | end 50 | 51 | local rest, reducer, spacing, values = position, self.reducer, self.spacing 52 | 53 | if not nonterminals or not nonterminals[self[1]] then 54 | if spacing then 55 | rest = spacing(invariant, rest, nil, self[1]) 56 | if not rest then 57 | return 58 | end 59 | end 60 | local value 61 | if given_rest then 62 | rest, value = given_rest, given_value 63 | else 64 | rest, value = self[1](invariant, rest, peek, nil, met, nonterminals) 65 | if not rest then 66 | return 67 | end 68 | end 69 | if not peek then 70 | values = reducer(values, value, 1, self, position, rest) 71 | end 72 | end 73 | if spacing then 74 | rest = spacing(invariant, rest, self[1], self[2]) 75 | if not rest then 76 | return 77 | end 78 | end 79 | for i=2, #self do 80 | local sub = rest 81 | local value 82 | rest, value = self[i](invariant, sub, peek) 83 | if not rest then 84 | return 85 | end 86 | if not peek then 87 | values = reducer(values, value, i, self, sub, rest) 88 | end 89 | if spacing then 90 | rest = spacing(invariant, rest, self[i], self[i+1]) 91 | if not rest then 92 | return 93 | end 94 | end 95 | end 96 | return rest, values 97 | end 98 | 99 | return span 100 | -------------------------------------------------------------------------------- /leftry/elements/term.lua: -------------------------------------------------------------------------------- 1 | local utils = require("leftry.utils") 2 | local prototype = utils.prototype 3 | local dotmap = utils.dotmap 4 | local map = utils.map 5 | local torepresentation = utils.torepresentation 6 | 7 | local term = prototype("term", function(self, constant, initializer) 8 | return setmetatable({constant=constant, initializer=initializer}, self) 9 | end) 10 | 11 | function term:__tostring() 12 | return torepresentation(term, {self.constant}) 13 | end 14 | 15 | function term:__mod(initializer) 16 | return rawset(self, "initializer", initializer) 17 | end 18 | 19 | function term:__call(invariant, position, peek) 20 | local constant = self.constant 21 | local count = #constant 22 | local initializer = self.initializer 23 | local rest = position + count 24 | if position > #invariant.source or 25 | invariant.source:sub(position, rest - 1) ~= constant then 26 | return nil 27 | end 28 | if initializer and not peek then 29 | return rest, initializer(constant, self, position, rest) 30 | end 31 | return rest, constant 32 | end 33 | 34 | return term 35 | -------------------------------------------------------------------------------- /leftry/elements/traits.lua: -------------------------------------------------------------------------------- 1 | local trait = require("leftry.trait") 2 | 3 | return { 4 | left_nonterminals = trait "left_nonterminals", 5 | search_left_nonterminal = trait "search_left_nonterminal", 6 | search_left_nonterminals = trait "search_left_nonterminals", 7 | hash = trait "hash" 8 | } 9 | -------------------------------------------------------------------------------- /leftry/elements/traits/hash.lua: -------------------------------------------------------------------------------- 1 | local grammar = require("leftry.grammar") 2 | local traits = require("leftry.elements.traits") 3 | local utils = require("leftry.utils") 4 | 5 | local any = grammar.any 6 | local span = grammar.span 7 | local opt = grammar.opt 8 | local rep = grammar.rep 9 | local term = grammar.term 10 | local factor = grammar.factor 11 | 12 | local contains = utils.contains 13 | 14 | local hash = traits.hash 15 | local search_left_nonterminal = traits.search_left_nonterminal 16 | 17 | local unpack = table.unpack or unpack 18 | 19 | local all = function() 20 | local t = {} 21 | for i=1, 255 do 22 | table.insert(t, i) 23 | end 24 | return unpack(t) 25 | end 26 | 27 | hash:where(any, function(self) 28 | local t = {} 29 | for i=1, #self do 30 | local h = {hash(self[i])} 31 | for j=1, #h do 32 | table.insert(t, h[j]) 33 | end 34 | end 35 | return unpack(t) 36 | end) 37 | 38 | hash:where(factor, function(self) 39 | if search_left_nonterminal(self.canon or self:setup(), self) then 40 | return all() 41 | end 42 | return hash(self.canon) 43 | end) 44 | 45 | hash:where("function", all) 46 | 47 | hash:where(term, function(self) 48 | return string.byte(self.constant) 49 | end) 50 | 51 | hash:where(opt, function(self) 52 | return hash(self.element) 53 | end) 54 | 55 | hash:where(rep, function(self) 56 | return hash(self.element) 57 | end) 58 | 59 | hash:where(span, function(self) 60 | local t = {} 61 | for i=1, #self do 62 | local h = {hash(self[i])} 63 | for j=1, #h do 64 | table.insert(t, h[j]) 65 | end 66 | local mt = getmetatable(self[i]) 67 | if mt ~= opt and mt ~= rep then 68 | break 69 | end 70 | end 71 | if self.spaces then 72 | for i=1, #self.spaces do 73 | table.insert(t, self.spaces:byte(i)) 74 | end 75 | end 76 | return unpack(t) 77 | end) 78 | -------------------------------------------------------------------------------- /leftry/elements/traits/left_nonterminals.lua: -------------------------------------------------------------------------------- 1 | local grammar = require("leftry.grammar") 2 | local traits = require("leftry.elements.traits") 3 | local set = require("leftry.immutable.set") 4 | 5 | local set_insert, set_empty = set.insert, set.empty 6 | 7 | local any = grammar.any 8 | local span = grammar.span 9 | local opt = grammar.opt 10 | local rep = grammar.rep 11 | local term = grammar.term 12 | local factor = grammar.factor 13 | 14 | local search_left_nonterminal = traits.search_left_nonterminal 15 | local left_nonterminals = traits.left_nonterminals 16 | 17 | 18 | left_nonterminals:where(factor, function(self, nonterminals) 19 | self:setup() 20 | if #self.recursions == 0 then 21 | return set_empty 22 | end 23 | if nonterminals and nonterminals[self] then 24 | return nonterminals 25 | end 26 | nonterminals = set_insert(nonterminals or set_empty, self) 27 | for i, alt in ipairs(self.recursions) do 28 | nonterminals = left_nonterminals(alt, nonterminals) 29 | end 30 | return nonterminals 31 | end, 1) 32 | 33 | local return_empty = function() return set_empty end 34 | local proxy_element = function(self, nonterminals) 35 | return left_nonterminals(self.element, nonterminals) 36 | end 37 | 38 | left_nonterminals:where("function", return_empty) 39 | left_nonterminals:where(opt, proxy_element, 2) 40 | left_nonterminals:where(rep, proxy_element, 2) 41 | left_nonterminals:where(term, return_empty) 42 | left_nonterminals:where(span, function(self, nonterminals) 43 | return left_nonterminals(self[1], nonterminals) 44 | end) 45 | -------------------------------------------------------------------------------- /leftry/elements/traits/search_left_nonterminal.lua: -------------------------------------------------------------------------------- 1 | local grammar = require("leftry.grammar") 2 | local traits = require("leftry.elements.traits") 3 | local set = require("leftry.immutable.set") 4 | 5 | local set_insert, set_empty = set.insert, set.empty 6 | 7 | local any = grammar.any 8 | local span = grammar.span 9 | local opt = grammar.opt 10 | local rep = grammar.rep 11 | local term = grammar.term 12 | local factor = grammar.factor 13 | 14 | local search_left_nonterminal = traits.search_left_nonterminal 15 | 16 | 17 | search_left_nonterminal:where(factor, function(self, target, seen) 18 | if self == target then 19 | return true 20 | end 21 | if seen and seen[self] then 22 | return false 23 | end 24 | return search_left_nonterminal(self.canon or self:setup(), target, 25 | set_insert(seen or set_empty, self)) 26 | end, 2) 27 | 28 | search_left_nonterminal:where(any, function(self, target, seen) 29 | for i=1, #self do 30 | if search_left_nonterminal(self[i], target, seen) then 31 | return true 32 | end 33 | end 34 | return false 35 | end, 2) 36 | 37 | local return_false = function() return false end 38 | local proxy_element = function(self, target, seen) 39 | return search_left_nonterminal(self.element, target, seen) 40 | end 41 | 42 | search_left_nonterminal:where(opt, proxy_element, 2) 43 | search_left_nonterminal:where(rep, proxy_element, 2) 44 | search_left_nonterminal:where("function", return_false) 45 | search_left_nonterminal:where(term, return_false) 46 | search_left_nonterminal:where(span, function(self, target, seen) 47 | return search_left_nonterminal(self[1], target, seen) 48 | end, 2) 49 | -------------------------------------------------------------------------------- /leftry/elements/traits/search_left_nonterminals.lua: -------------------------------------------------------------------------------- 1 | local grammar = require("leftry.grammar") 2 | local traits = require("leftry.elements.traits") 3 | local set = require("leftry.immutable.set") 4 | 5 | local set_insert, set_empty = set.insert, set.empty 6 | 7 | local any = grammar.any 8 | local span = grammar.span 9 | local opt = grammar.opt 10 | local rep = grammar.rep 11 | local term = grammar.term 12 | local factor = grammar.factor 13 | 14 | local search_left_nonterminals = traits.search_left_nonterminals 15 | local search_left_nonterminal = traits.search_left_nonterminal 16 | 17 | 18 | search_left_nonterminals:where(factor, function(self, targets) 19 | for target in pairs(targets) do 20 | if search_left_nonterminal(self, target) then 21 | return true 22 | end 23 | end 24 | return false 25 | end, 2) 26 | 27 | search_left_nonterminals:where(any, function(self, targets) 28 | for target in pairs(targets) do 29 | if search_left_nonterminal(self, target) then 30 | return true 31 | end 32 | end 33 | return false 34 | end, 2) 35 | 36 | local return_false = function() return false end 37 | local proxy_element = function(self, target, seen) 38 | return search_left_nonterminals(self.element, target, seen) 39 | end 40 | 41 | search_left_nonterminals:where(opt, proxy_element, 2) 42 | search_left_nonterminals:where(rep, proxy_element, 2) 43 | search_left_nonterminals:where("function", return_false) 44 | search_left_nonterminals:where(term, return_false) 45 | search_left_nonterminals:where(span, function(self, target) 46 | return search_left_nonterminals(self[1], target) 47 | end, 2) 48 | -------------------------------------------------------------------------------- /leftry/elements/utils.lua: -------------------------------------------------------------------------------- 1 | local term = require("leftry.elements.term") 2 | 3 | local termize = function(value) 4 | if type(value) == "string" then 5 | return term(value) 6 | end 7 | return value 8 | end 9 | 10 | local invariantize = function(value) 11 | if type(value) == "string" then 12 | return {source=value, events={}} 13 | end 14 | return value 15 | end 16 | 17 | return { 18 | termize=termize, 19 | invariantize=invariantize 20 | } 21 | -------------------------------------------------------------------------------- /leftry/grammar.lua: -------------------------------------------------------------------------------- 1 | return { 2 | span=require("leftry.elements.span"), 3 | any=require("leftry.elements.any"), 4 | term = require("leftry.elements.term"), 5 | factor = require("leftry.elements.factor"), 6 | opt = require("leftry.elements.opt"), 7 | rep = require("leftry.elements.rep") 8 | } 9 | -------------------------------------------------------------------------------- /leftry/immutable/memoize.lua: -------------------------------------------------------------------------------- 1 | local reverse = require("leftry.utils").reverse 2 | local unpack = table.unpack or unpack 3 | 4 | local function curry(f, n, ...) 5 | local curried = {...} 6 | if n > 1 then 7 | return function(self, a) 8 | local u = setmetatable({}, { __mode='k', __index = curry(f, n-1, a, 9 | unpack(curried)) }) 10 | self[a] = u 11 | return u 12 | end 13 | else 14 | local parameters = reverse(curried) 15 | return function(self, a) 16 | table.insert(parameters, a) 17 | local u = f(unpack(parameters)) 18 | self[a] = u 19 | return u 20 | end 21 | end 22 | end 23 | 24 | local cache_memoize = setmetatable({}, {__mode='k', __index=curry( 25 | function(f, n) 26 | return setmetatable({}, {__mode='k', __index=curry(f, n)}) 27 | end, 2)}) 28 | 29 | local function memoize(f, n) 30 | return cache_memoize[f][n] 31 | end 32 | 33 | return memoize 34 | -------------------------------------------------------------------------------- /leftry/immutable/set.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | Immutable Set 3 | 4 | Only use `insert` and `remove` to add or remove objects from sets. 5 | 6 | There can only be one immutable set for a particular set of content objects. 7 | --]]-- 8 | 9 | local utils = require("leftry.utils") 10 | local copy = utils.copy 11 | 12 | local cache_remove = setmetatable({}, { 13 | __mode='k', 14 | __index=function(self, s) 15 | local t = setmetatable({}, {__mode='k'}) 16 | rawset(self, s, t) 17 | return t 18 | end 19 | }) 20 | 21 | local cache_insert = setmetatable({}, { 22 | __mode='k', 23 | __index=function(self, s) 24 | local t = setmetatable({}, {__mode='k', 25 | __index=function(cache, x) 26 | local u = rawset(copy(s), x, true) 27 | cache_remove[u][x] = s 28 | cache[x] = u 29 | return u 30 | end}) 31 | rawset(self, s, t) 32 | return t 33 | end 34 | }) 35 | 36 | -- The empty set. 37 | local empty = {} 38 | 39 | local function id(s) 40 | if s then 41 | return s 42 | end 43 | return empty 44 | end 45 | 46 | local function insert(s, x) 47 | if s[x] then 48 | return s 49 | end 50 | return cache_insert[s][x] 51 | end 52 | 53 | local function remove(s, x) 54 | if not s[x] then 55 | return s 56 | end 57 | return cache_remove[s][x] 58 | end 59 | 60 | return { 61 | id=id, 62 | insert=insert, 63 | remove=remove, 64 | empty=empty 65 | } 66 | -------------------------------------------------------------------------------- /leftry/initializers.lua: -------------------------------------------------------------------------------- 1 | local function id(value) 2 | return value 3 | end 4 | 5 | local function none() 6 | return 7 | end 8 | 9 | local function metatable(mt) 10 | return function(value) 11 | return setmetatable(value, mt) 12 | end 13 | end 14 | 15 | local function leftflat(values, value) 16 | if not values then 17 | return value or {} 18 | end 19 | if value ~= nil then 20 | values.n = math.max(#values, values.n or 0) + 1 21 | end 22 | return rawset(values, #values + 1, value) 23 | end 24 | 25 | local function rightflat(values, value, i, self) 26 | if i == 1 then 27 | values = {} 28 | end 29 | if i < #self then 30 | return rawset(values, #values + 1, value) 31 | elseif value then 32 | for i, v in ipairs(value) do 33 | rawset(values, #values + 1, v) 34 | end 35 | end 36 | return values 37 | end 38 | 39 | return { 40 | id=id, 41 | leftflat=leftflat, 42 | rightflat=rightflat, 43 | none=none, 44 | metatable=metatable 45 | } -------------------------------------------------------------------------------- /leftry/language/line.lua: -------------------------------------------------------------------------------- 1 | local function Line(invariant, position, peek) 2 | if position > #invariant then 3 | return 4 | end 5 | 6 | local rest = string.find(invariant, "\n", position + 1) or #invariant + 1 7 | local value 8 | 9 | if rest and not peek then 10 | value = invariant:sub(position+1, rest-1) 11 | end 12 | return rest, value 13 | end 14 | 15 | local index = setmetatable({}, { 16 | __index = function(self, invariant) 17 | self[invariant] = {} 18 | local n = 0 19 | local i = 1 20 | for rest, line in Line, invariant, i do 21 | n = n + 1 22 | self[invariant][n] = i 23 | i = rest 24 | end 25 | return self[invariant] 26 | end 27 | }) 28 | 29 | local inverse = setmetatable({}, { 30 | __index = function(self, invariant) 31 | self[invariant] = {} 32 | local n = 0 33 | local i = 1 34 | for rest, line in Line, invariant, i do 35 | n = n + 1 36 | for j = i, rest do 37 | self[invariant][j] = n 38 | end 39 | i = rest 40 | end 41 | return self[invariant] 42 | end 43 | }) 44 | 45 | return { 46 | index = index, 47 | inverse = inverse, 48 | Line = Line 49 | } 50 | -------------------------------------------------------------------------------- /leftry/language/lua.lua: -------------------------------------------------------------------------------- 1 | local utils = require("leftry.utils") 2 | 3 | local ast = require("leftry.ast") 4 | local map = utils.map 5 | local each = utils.each 6 | 7 | local grammar = require("leftry.grammar") 8 | 9 | local opt = grammar.opt 10 | local rep = grammar.rep 11 | local factor = grammar.factor 12 | local term = grammar.term 13 | 14 | 15 | local r = each(function(v, k) return 16 | -- Define the Lua syntax tree representations, and corresponding output 17 | -- format using `ast.reduce`. 18 | ast.reduce(k, 19 | type(v) ~= "function" and v or false, 20 | type(v) == "function" and v or nil) 21 | end, { 22 | lua_assign = {varlist=1, "=", explist=3}, 23 | lua_dot = {prefix=1, ".", name=3}, 24 | lua_index = {prefix=1, "[", key=3, "]"}, 25 | lua_goto = {"goto ", label=2}, 26 | lua_do = {"do ", block=2, " end"}, 27 | lua_while = {"while ", condition=2, " do ", block=4, " end"}, 28 | lua_repeat = {"repeat ", block=2, " until ", condition=4}, 29 | lua_if = {"if ", condition=2, " then ", block=4, _elseifs=5, _else=6," end"}, 30 | lua_elseif = {" elseif ", condition=2, " then ", block=4}, 31 | lua_else = {" else ", block=2}, 32 | lua_elseifs = function(self) return 33 | table.concat(map(tostring, self), " ") 34 | end, 35 | lua_for = {"for ", var=2, "=", initial=4, ",", limit=6, step=7, " do ", 36 | block=9, " end"}, 37 | lua_step = {",", step=2}, 38 | lua_for_in = {"for ", namelist=2, " in ", explist=4, " do ", block=6, 39 | " end"}, 40 | lua_function = {"function ", name=2, body=3}, 41 | lua_lambda_function = {"function", body=2}, 42 | lua_local_function = {"local", " function ", name=3, body=4}, 43 | lua_retstat = {"return ", explist=2}, 44 | lua_label = {"::", name=2, "::"}, 45 | lua_binop_exp = {left=1, binop=2, right=3}, 46 | lua_unop_exp = {unop=1, exp=2}, 47 | lua_functioncall = {exp=1, args=2}, 48 | lua_colon_functioncall = {exp=1, ":", name=3, args=4}, 49 | lua_args = {"(", explist=2, ")" }, 50 | lua_table_args = {"{", fieldlist=2, "}"}, 51 | lua_funcbody = {"(", namelist=2, ")", block=4, " end"}, 52 | lua_paren_exp = {"(", exp=2, ")"}, 53 | lua_field_name = {name=1, "=", exp=3}, 54 | lua_field_key = {"[", key=2, "]", "=", exp=5} 55 | }) 56 | 57 | local lua_assign = r.lua_assign 58 | local lua_dot = r.lua_dot 59 | local lua_index = r.lua_index 60 | local lua_goto = r.lua_goto 61 | local lua_do = r.lua_do 62 | local lua_while = r.lua_while 63 | local lua_repeat = r.lua_repeat 64 | local lua_if = r.lua_if 65 | local lua_elseif = r.lua_elseif 66 | local lua_else = r.lua_else 67 | local lua_elseifs = r.lua_elseifs 68 | local lua_for = r.lua_for 69 | local lua_step = r.lua_step 70 | local lua_for_in = r.lua_for_in 71 | local lua_function = r.lua_function 72 | local lua_lambda_function = r.lua_lambda_function 73 | local lua_local_function = r.lua_local_function 74 | local lua_retstat = r.lua_retstat 75 | local lua_label = r.lua_label 76 | local lua_binop_exp = r.lua_binop_exp 77 | local lua_unop_exp = r.lua_unop_exp 78 | local lua_functioncall = r.lua_functioncall 79 | local lua_colon_functioncall = r.lua_colon_functioncall 80 | local lua_args = r.lua_args 81 | local lua_table_args = r.lua_table_args 82 | local lua_funcbody = r.lua_funcbody 83 | local lua_paren_exp = r.lua_paren_exp 84 | local lua_field_name = r.lua_field_name 85 | local lua_field_key = r.lua_field_key 86 | 87 | local lua_local = ast.reduce("lua_local", {"local", namelist=2, explist=3}, 88 | function(self) 89 | return table.concat({ 90 | "local", tostring(self.namelist), "=", tostring(self.explist) 91 | }, " ") 92 | end) 93 | 94 | local lua_varlist = ast.list("lua_varlist", ",") 95 | local lua_namelist = ast.list("lua_namelist", ",") 96 | local lua_explist = ast.list("lua_explist", ",") 97 | local lua_block = ast.list("lua_block", ";") 98 | local lua_funcname = ast.list("lua_funcname") 99 | local lua_fieldlist = ast.list("lua_fieldlist", ",") 100 | 101 | local lua_nil = ast.const("lua_nil", "nil") 102 | local lua_true = ast.const("lua_true", "true") 103 | local lua_false = ast.const("lua_false", "false") 104 | local lua_break = ast.const("lua_break", "break") 105 | local lua_vararg = ast.const("lua_vararg", "...") 106 | local lua_semicolon = ast.const("lua_semicolon", ";") 107 | 108 | local lua_name = ast.id("lua_name") 109 | local lua_number = ast.id("lua_number") 110 | local lua_string = ast.id("lua_string", "value", function(self) 111 | return utils.escape(self.value) 112 | end) 113 | local lua_chunk = ast.id("lua_chunk", "block") 114 | local lua_binop = ast.id("lua_binop", "value", function(self) 115 | return " "..self.value.." " 116 | end) 117 | local lua_unop = ast.id("lua_unop", "value", function(self) 118 | return " "..self.value.." " 119 | end) 120 | 121 | -- Lua Grammar 122 | -- chunk ::= block 123 | -- block ::= {stat} [retstat] 124 | -- stat ::= ‘;’ | 125 | -- varlist ‘=’ explist | 126 | -- functioncall | 127 | -- label | 128 | -- break | 129 | -- goto Name | 130 | -- do block end | 131 | -- while exp do block end | 132 | -- repeat block until exp | 133 | -- if exp then block {elseif exp then block} [else block] end | 134 | -- for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end | 135 | -- for namelist in explist do block end | 136 | -- function funcname funcbody | 137 | -- local function Name funcbody | 138 | -- local namelist [‘=’ explist] 139 | 140 | -- retstat ::= return [explist] [‘;’] 141 | -- label ::= ‘::’ Name ‘::’ 142 | -- funcname ::= Name {‘.’ Name} [‘:’ Name] 143 | -- varlist ::= var {‘,’ var} 144 | -- var ::= Name | prefixexp ‘[’ exp ‘]’ | prefixexp ‘.’ Name 145 | -- namelist ::= Name {‘,’ Name} 146 | -- explist ::= exp {‘,’ exp} 147 | -- exp ::= nil | false | true | Numeral | LiteralString | ‘...’ | functiondef | 148 | -- prefixexp | tableconstructor | exp binop exp | unop exp 149 | -- prefixexp ::= var | functioncall | ‘(’ exp ‘)’ 150 | -- functioncall ::= prefixexp args | prefixexp ‘:’ Name args 151 | -- args ::= ‘(’ [explist] ‘)’ | tableconstructor | LiteralString 152 | -- functiondef ::= function funcbody 153 | -- funcbody ::= ‘(’ [parlist] ‘)’ block end 154 | -- parlist ::= namelist [‘,’ ‘...’] | ‘...’ 155 | -- tableconstructor ::= ‘{’ [fieldlist] ‘}’ 156 | -- fieldlist ::= field {fieldsep field} [fieldsep] 157 | -- field ::= ‘[’ exp ‘]’ ‘=’ exp | Name ‘=’ exp | exp 158 | -- fieldsep ::= ‘,’ | ‘;’ 159 | -- binop ::= ‘+’ | ‘-’ | ‘*’ | ‘/’ | ‘//’ | ‘^’ | ‘%’ | 160 | -- ‘&’ | ‘~’ | ‘|’ | ‘>>’ | ‘<<’ | ‘..’ | 161 | -- ‘<’ | ‘<=’ | ‘>’ | ‘>=’ | ‘==’ | ‘~=’ | 162 | -- and | or 163 | -- unop ::= ‘-’ | not | ‘#’ | ‘~’ 164 | 165 | -- Non-Terminals 166 | 167 | 168 | local initializers = require("leftry.initializers") 169 | local reducers = require("leftry.reducers") 170 | 171 | local none = initializers.none 172 | local leftflat = initializers.leftflat 173 | local rightflat = initializers.rightflat 174 | local first = reducers.first 175 | local second = reducers.second 176 | local concat = reducers.concat 177 | 178 | 179 | local Chunk, Block, Stat, RetStat, Label, FuncName, VarList, Var, NameList, 180 | ExpList, Exp, PrefixExp, FunctionCall, Args, FunctionDef, FuncBody, 181 | ParList, TableConstructor, FieldList, Field, FieldSep, BinOp, UnOp, 182 | numeral, Numeral, LiteralString, Name, Space, Comment, LongString 183 | 184 | local dquoted, squoted 185 | 186 | local is_space = { 187 | [(" "):byte()] = true, 188 | [("\t"):byte()] = true, 189 | [("\r"):byte()] = true, 190 | [("\n"):byte()] = true, 191 | } 192 | 193 | local underscore, alpha, zeta, ALPHA, ZETA = 95, 97, 122, 65, 90 194 | local zero, nine = 48, 57 195 | 196 | local function isalphanumeric(byte) 197 | return byte and (byte == underscore or byte >= alpha and byte <= zeta or 198 | byte >= ALPHA and byte <= ZETA or byte >= zero and byte <= nine) 199 | end 200 | 201 | local function isalpha(byte) 202 | return byte and (byte == underscore or byte >= alpha and byte <= zeta or 203 | byte >= ALPHA and byte <= ZETA) 204 | end 205 | 206 | Comment = factor("Comment", function() return 207 | grammar.span("--", function(invariant, position, peek) 208 | -- Parse --[[ comment ]] 209 | local value 210 | 211 | if LongString(invariant, position, true) then 212 | position, value = LongString(invariant, position, peek) 213 | else 214 | while invariant.source:sub(position, position) ~= "\n" do 215 | position = position + 1 216 | end 217 | end 218 | return position, value 219 | end) end) 220 | 221 | local spaces = " \t\r\n" 222 | 223 | local function spacing(invariant, position, previous, current) 224 | local src = invariant.source 225 | local byte = src:byte(position) 226 | 227 | -- Skip whitespace and comments 228 | local comment = position 229 | local rest 230 | repeat 231 | rest = comment 232 | byte = src:byte(rest) 233 | while is_space[byte] do 234 | rest = rest + 1 235 | byte = src:byte(rest) 236 | end 237 | comment = Comment(invariant, rest, true) 238 | until not comment 239 | 240 | -- Check for required whitespace between two alphanumeric nonterminals. 241 | if rest == position and getmetatable(previous) == term then 242 | if isalphanumeric(src:byte(position-1)) and isalphanumeric(byte) then 243 | return 244 | end 245 | end 246 | 247 | -- Return advanced cursor. 248 | return rest 249 | end 250 | 251 | local function span(...) 252 | -- Apply spacing rule to all spans we use in the Lua grammar. 253 | return grammar.span(...) ^ {spacing=spacing, spaces=spaces} 254 | end 255 | 256 | Chunk = factor("Chunk", function() return Block end, lua_chunk) 257 | Block = factor("Block", function() return 258 | span(rep(Stat), opt(RetStat)) % leftflat end, lua_block) 259 | Stat = factor("Stat", function() return 260 | term(';') % lua_semicolon, 261 | span(VarList, "=", ExpList) % lua_assign, 262 | FunctionCall, 263 | Label, 264 | term("break") % lua_break, 265 | span("goto", Name) % lua_goto, 266 | span("do", opt(Block), "end") % lua_do, 267 | span("while", Exp, "do", opt(Block), "end") % lua_while, 268 | span("repeat", Block, "until", Exp) % lua_repeat, 269 | span("if", Exp, "then", opt(Block), 270 | rep(span("elseif", Exp, "then", opt(Block)) % lua_elseif) % lua_elseifs, 271 | opt(span("else", opt(Block)) % lua_else), "end") % lua_if, 272 | span("for", Name, "=", Exp, ",", Exp, opt(span(",", Exp) % lua_step), 273 | "do", Block, "end") % lua_for, 274 | span("for", NameList, "in", ExpList, "do", opt(Block), "end") % lua_for_in, 275 | span("function", FuncName, FuncBody) % lua_function, 276 | span("local", "function", Name, FuncBody) % lua_local_function, 277 | span("local", NameList, opt(span("=", ExpList) % second)) % lua_local end) 278 | RetStat = factor("RetStat", function() return 279 | span("return", opt(ExpList), opt(term(";") % none)) % lua_retstat end) 280 | Label = factor("Label", function() return 281 | span("::", Name, "::") % lua_label end) 282 | FuncName = factor("FuncName", function() return 283 | span( 284 | rep(span(Name, ".") % concat), Name, 285 | opt(span(":", Name) % concat)) % leftflat end, 286 | lua_funcname) 287 | VarList = factor("VarList", function() return 288 | span(Var, rep(span(",", Var) % second)) % rightflat end, lua_varlist) 289 | Var = factor("Var", function() return 290 | Name, 291 | span(PrefixExp, "[", Exp, "]") % lua_index, 292 | span(PrefixExp, ".", Name) % lua_dot end) 293 | NameList = factor("NameList", function() return 294 | span(Name, rep(span(",", Name) % second)) % rightflat end, lua_namelist) 295 | ExpList = factor("ExpList", function() return 296 | span(Exp, rep(span(",", Exp) % second)) % rightflat end, lua_explist) 297 | Exp = factor("Exp", function(Exp) return 298 | term("nil") % lua_nil, 299 | term("false") % lua_false, 300 | term("true") % lua_true, 301 | Numeral, 302 | LiteralString, 303 | term("...") % lua_vararg, 304 | FunctionDef, 305 | PrefixExp, 306 | TableConstructor, 307 | span(Exp, BinOp, Exp) % lua_binop_exp, 308 | span(UnOp, Exp) % lua_unop_exp end) 309 | FunctionCall = factor("FunctionCall", function() return 310 | span(PrefixExp, Args) % lua_functioncall, 311 | span(PrefixExp, ":", Name, Args) % lua_colon_functioncall end) 312 | Args = factor("Args", function() return 313 | span("(", opt(ExpList), ")") % lua_args, 314 | TableConstructor, 315 | LiteralString end) 316 | FunctionDef = factor("FunctionDef", function() return 317 | span("function", FuncBody) % lua_lambda_function end) 318 | PrefixExp = factor("PrefixExp", function() return 319 | Var, FunctionCall, span("(", Exp, ")") % lua_paren_exp end) 320 | FuncBody = factor("FuncBody", function() return 321 | span("(", opt(ParList), ")", opt(Block), "end") % lua_funcbody end) 322 | ParList = factor("ParList", function() return 323 | span(NameList, opt(span(",", term("...") % lua_vararg) % second)) % leftflat, 324 | term("...") % lua_vararg end) 325 | TableConstructor = factor("TableConstructor", function() return 326 | span("{", opt(FieldList), "}") % lua_table_args end) 327 | FieldList = factor("FieldList", function() return 328 | span(span(Field, rep(span(FieldSep, Field) % second)) % rightflat, 329 | opt(FieldSep)) % first end, lua_fieldlist) 330 | FieldSep = factor("FieldSep", function() return 331 | ",", ";" end) 332 | Field = factor("Field", function() return 333 | span("[", Exp, "]", "=", Exp) % lua_field_key, 334 | span(Name, "=", Exp) % lua_field_name, Exp end) 335 | BinOp = factor("BinOp", function() return 336 | "^", "*", "/", "//", "%", "+", "-", "..", "<<", ">>", "&", "|", "<=", 337 | ">=", "<", ">", "~=", "==", "and", "or" end, lua_binop) 338 | UnOp = factor("UnOp", function() return 339 | "-", "not", "#", "~" end, lua_unop) 340 | LiteralString = factor("LiteralString", function() return 341 | grammar.span("\"", opt(dquoted), "\"") % second, 342 | grammar.span("\'", opt(squoted), "\'") % second, 343 | LongString end, lua_string) 344 | 345 | long_string_quote = grammar.span("[", rep("="), "[") 346 | LongString = function(invariant, position, peek) 347 | local rest = long_string_quote(invariant, position, true) 348 | if not rest then 349 | return 350 | end 351 | local level = position - rest - 2 352 | local endquote = "]"..("="):rep(level) .. "]" 353 | local endquotestart, endquoteend = invariant.source:find(endquote, rest) 354 | if not endquotestart then 355 | return 356 | end 357 | local value = invariant.source:sub(rest, endquotestart-1) 358 | rest = endquoteend + 1 359 | if peek then 360 | return rest 361 | end 362 | return rest, value 363 | end 364 | 365 | -- Functions 366 | local function stringcontent(quotechar) 367 | return function(invariant, position) 368 | local src = invariant.source 369 | local limit = #src 370 | if position > limit then 371 | return 372 | end 373 | local escaped = false 374 | local value = {} 375 | local byte 376 | for i=position, limit do 377 | if not escaped and byte == "\\" then 378 | escaped = true 379 | else 380 | if escaped and byte == "n" then 381 | byte = "\n" 382 | end 383 | escaped = false 384 | end 385 | if not escaped then 386 | table.insert(value, byte) 387 | end 388 | byte = string.char(invariant.source:byte(i)) 389 | if byte == quotechar and not escaped then 390 | return i, table.concat(value) 391 | end 392 | end 393 | raise(UnmatchedQuoteException(src, limit)) 394 | end 395 | end 396 | 397 | dquoted = stringcontent("\"") 398 | squoted = stringcontent("\'") 399 | 400 | numeral = function(invariant, position, peek) 401 | local sign, numbers = position 402 | local src = invariant.source 403 | local byte = src:byte(position) 404 | local dot, zero, nine, minus = 46, 48, 57, 45 405 | if byte == minus then 406 | sign = position + 1 407 | end 408 | local decimal = false 409 | local rest 410 | for i=sign, #src do 411 | local byte = src:byte(i) 412 | if i ~= sign and byte == dot and decimal == false then 413 | decimal = true 414 | elseif not (byte >= zero and byte <= nine) then 415 | rest = i 416 | break 417 | elseif i == #src then 418 | rest = #src + 1 419 | end 420 | end 421 | if rest == position or rest == sign then 422 | -- Not a number 423 | return nil 424 | end 425 | if peek then 426 | return rest 427 | end 428 | return rest, tonumber(src:sub(position, rest-1)) 429 | end 430 | 431 | Numeral = function(invariant, position, peek) 432 | local rest, value = numeral(invariant, position, peek) 433 | if value then 434 | value = lua_number(value) 435 | end 436 | return rest, value 437 | end 438 | 439 | local keywords = { 440 | ["return"] = true, 441 | ["function"] = true, 442 | ["end"] = true, 443 | ["in"] = true, 444 | ["not"] = true, 445 | ["and"] = true, 446 | ["break"] = true, 447 | ["do"] = true, 448 | ["else"] = true, 449 | ["elseif"] = true, 450 | ["for"] = true, 451 | ["if"] = true, 452 | ["local"] = true, 453 | ["or"] = true, 454 | ["repeat"] = true, 455 | ["then"] = true, 456 | ["until"] = true, 457 | ["while"] = true 458 | } 459 | 460 | Name = function(invariant, position, peek) 461 | local underscore, alpha, zeta, ALPHA, ZETA = 95, 97, 122, 65, 90 462 | local zero, nine = 48, 57 463 | local src = invariant.source 464 | local byte = src:byte(position) 465 | 466 | if not isalpha(byte) then 467 | return nil 468 | end 469 | 470 | local rest = position + 1 471 | for i=position+1, #src do 472 | byte = src:byte(i) 473 | if not isalphanumeric(byte) then 474 | break 475 | end 476 | rest = i + 1 477 | end 478 | 479 | local value = src:sub(position, rest-1) 480 | 481 | if keywords[value] then 482 | return 483 | end 484 | 485 | if peek then 486 | return rest 487 | end 488 | 489 | return rest, lua_name(value) 490 | end 491 | 492 | 493 | local lua_ast = { 494 | [lua_assign] = lua_assign, 495 | [lua_dot] = lua_dot, 496 | [lua_index] = lua_index, 497 | [lua_goto] = lua_goto, 498 | [lua_do] = lua_do, 499 | [lua_while] = lua_while, 500 | [lua_repeat] = lua_repeat, 501 | [lua_if] = lua_if, 502 | [lua_elseif] = lua_elseif, 503 | [lua_else] = lua_else, 504 | [lua_elseifs] = lua_elseifs, 505 | [lua_for] = lua_for, 506 | [lua_step] = lua_step, 507 | [lua_for_in] = lua_for_in, 508 | [lua_function] = lua_function, 509 | [lua_lambda_function] = lua_lambda_function, 510 | [lua_local_function] = lua_local_function, 511 | [lua_retstat] = lua_retstat, 512 | [lua_label] = lua_label, 513 | [lua_binop_exp] = lua_binop_exp, 514 | [lua_unop_exp] = lua_unop_exp, 515 | [lua_functioncall] = lua_functioncall, 516 | [lua_colon_functioncall] = lua_colon_functioncall, 517 | [lua_args] = lua_args, 518 | [lua_table_args] = lua_table_args, 519 | [lua_funcbody] = lua_funcbody, 520 | [lua_paren_exp] = lua_paren_exp, 521 | [lua_local] = lua_local, 522 | [lua_varlist] = lua_varlist, 523 | [lua_namelist] = lua_namelist, 524 | [lua_explist] = lua_explist, 525 | [lua_block] = lua_block, 526 | [lua_funcname] = lua_funcname, 527 | [lua_fieldlist] = lua_fieldlist, 528 | [lua_nil] = lua_nil, 529 | [lua_true] = lua_true, 530 | [lua_false] = lua_false, 531 | [lua_break] = lua_break, 532 | [lua_vararg] = lua_vararg, 533 | [lua_semicolon] = lua_semicolon, 534 | [lua_number] = lua_number, 535 | [lua_string] = lua_string, 536 | [lua_chunk] = lua_chunk, 537 | [lua_binop] = lua_binop, 538 | [lua_unop] = lua_unop, 539 | [lua_name] = lua_name, 540 | } 541 | 542 | 543 | local exports = { 544 | Lua=Chunk, 545 | Chunk=Chunk, 546 | Block=Block, 547 | Stat=Stat, 548 | RetStat=RetStat, 549 | Label=Label, 550 | FuncName=FuncName, 551 | VarList=VarList, 552 | Var=Var, 553 | NameList=NameList, 554 | ExpList=ExpList, 555 | Exp=Exp, 556 | PrefixExp=PrefixExp, 557 | FunctionCall=FunctionCall, 558 | Args=Args, 559 | FunctionDef=FunctionDef, 560 | FuncBody=FuncBody, 561 | ParList=ParList, 562 | TableConstructor=TableConstructor, 563 | FieldList=FieldList, 564 | Field=Field, 565 | FieldSep=FieldSep, 566 | BinOp=BinOp, 567 | isalphanumeric=isalphanumeric, 568 | isalpha=isalpha, 569 | UnOp=UnOp, 570 | Numeral=Numeral, 571 | numeral=numeral, 572 | LiteralString=LiteralString, 573 | Name=Name, 574 | Space=Space, 575 | Comment=Comment, 576 | LongString=LongString, 577 | span=span, 578 | rep=rep, 579 | spacing=spacing, 580 | term=term, 581 | factor=factor, 582 | spaces=spaces, 583 | is_space=is_space, 584 | lua_ast = lua_ast, 585 | lua_assign = lua_assign, 586 | lua_dot = lua_dot, 587 | lua_index = lua_index, 588 | lua_goto = lua_goto, 589 | lua_do = lua_do, 590 | lua_while = lua_while, 591 | lua_repeat = lua_repeat, 592 | lua_if = lua_if, 593 | lua_elseif = lua_elseif, 594 | lua_else = lua_else, 595 | lua_elseifs = lua_elseifs, 596 | lua_for = lua_for, 597 | lua_step = lua_step, 598 | lua_for_in = lua_for_in, 599 | lua_function = lua_function, 600 | lua_lambda_function = lua_lambda_function, 601 | lua_local_function = lua_local_function, 602 | lua_retstat = lua_retstat, 603 | lua_label = lua_label, 604 | lua_binop_exp = lua_binop_exp, 605 | lua_unop_exp = lua_unop_exp, 606 | lua_functioncall = lua_functioncall, 607 | lua_colon_functioncall = lua_colon_functioncall, 608 | lua_args = lua_args, 609 | lua_table_args = lua_table_args, 610 | lua_funcbody = lua_funcbody, 611 | lua_paren_exp = lua_paren_exp, 612 | lua_local = lua_local, 613 | lua_varlist = lua_varlist, 614 | lua_namelist = lua_namelist, 615 | lua_explist = lua_explist, 616 | lua_block = lua_block, 617 | lua_funcname = lua_funcname, 618 | lua_fieldlist = lua_fieldlist, 619 | lua_nil = lua_nil, 620 | lua_true = lua_true, 621 | lua_false = lua_false, 622 | lua_break = lua_break, 623 | lua_vararg = lua_vararg, 624 | lua_semicolon = lua_semicolon, 625 | lua_number = lua_number, 626 | lua_string = lua_string, 627 | lua_chunk = lua_chunk, 628 | lua_binop = lua_binop, 629 | lua_unop = lua_unop, 630 | } 631 | 632 | for k, v in pairs(exports) do 633 | if getmetatable(v) == grammar.factor then 634 | v:setup() 635 | v:actualize() 636 | end 637 | end 638 | 639 | return exports 640 | -------------------------------------------------------------------------------- /leftry/reducers.lua: -------------------------------------------------------------------------------- 1 | local nth = function(j) 2 | return function(a, b, i) 3 | if i == j then 4 | return b 5 | end 6 | return a 7 | end 8 | end 9 | 10 | local first = nth(1) 11 | local second = nth(2) 12 | 13 | local concat = function(a, b, i) 14 | if not a then 15 | return tostring(b) 16 | end 17 | return tostring(a) .. tostring(b) 18 | end 19 | 20 | return { 21 | nth = nth, 22 | first = first, 23 | second = second, 24 | concat = concat 25 | } 26 | -------------------------------------------------------------------------------- /leftry/trait.lua: -------------------------------------------------------------------------------- 1 | local prototype = require("leftry.utils").prototype 2 | 3 | local trait = prototype("trait", function(trait, name) 4 | return setmetatable({name=name, memoize={}}, trait) 5 | end) 6 | 7 | function trait:__tostring() 8 | return self.name 9 | end 10 | 11 | function trait:has(mt) 12 | return self[mt] 13 | end 14 | 15 | function trait:where(mt, impl, n) 16 | if n then 17 | if n < 1 or n > 2 then 18 | error("can only memoize one or two arguments. not ".. tostring(n)) 19 | end 20 | rawset(self.memoize, mt, n) 21 | end 22 | return rawset(self, mt, impl) 23 | end 24 | 25 | function trait:__call(this, ...) 26 | local t = type(this) 27 | if self[t] then 28 | return self[t](this, ...) 29 | end 30 | local mt = getmetatable(this) 31 | if self[mt] then 32 | local n = self.memoize[mt] 33 | local i = select("#", ...) 34 | if n == 1 and i == 0 then 35 | local attr = "_"..self.name 36 | local cache = this[attr] 37 | if cache ~= nil then 38 | return cache 39 | end 40 | rawset(this, attr, self[mt](this, ...)) 41 | return this[attr] 42 | elseif n == 2 and i == 1 then 43 | local attr = "_"..self.name 44 | local cache = this[attr] or rawset(this, attr, {}) 45 | local param = ... 46 | local value = cache[param] 47 | if value ~= nil then 48 | return value 49 | end 50 | rawset(cache, param, self[mt](this, ...)) 51 | return cache[param] 52 | end 53 | return self[mt](this, ...) 54 | end 55 | error(tostring(self).." not implemented for: ".. tostring(this).. 56 | " :: ".. tostring(getmetatable(this) or "")..", "..type(this)) 57 | end 58 | 59 | return trait 60 | -------------------------------------------------------------------------------- /leftry/utils.lua: -------------------------------------------------------------------------------- 1 | local __ipairs = ipairs 2 | 3 | local ipairs = ipairs 4 | 5 | local function len(t) 6 | if not t then 7 | return 0 8 | end 9 | return #t 10 | end 11 | 12 | if ipairs(setmetatable({}, {__ipairs=function() end})) == ipairs({}) then 13 | ipairs = function(t) 14 | local mt = getmetatable(t) 15 | if not mt or not mt.__ipairs then 16 | return __ipairs(t) 17 | end 18 | return mt.__ipairs(t) 19 | end 20 | end 21 | 22 | if #setmetatable({}, {__len=function() return 5 end}) == 0 then 23 | len = function(t) 24 | if not t then 25 | return 0 26 | end 27 | local mt = getmetatable(t) 28 | if not mt or not mt.__len then 29 | return #t 30 | end 31 | return mt.__len(t) 32 | end 33 | end 34 | 35 | local function prototype(name, initializer) 36 | local self = setmetatable({}, { 37 | __tostring = function(self) 38 | return name 39 | end, 40 | __call = initializer 41 | }) 42 | self.__index = self 43 | return self 44 | end 45 | 46 | local function hasmetatable(value, mt) 47 | return getmetatable(value) == mt 48 | end 49 | 50 | local function dotmap(f, ...) 51 | if select("#", ...) > 0 then 52 | return f(select(1, ...)), dotmap(f, select(2, ...)) 53 | end 54 | end 55 | 56 | local function escape(text) 57 | return "\""..text 58 | :gsub("\\", "\\\\") 59 | :gsub("\"", "\\\"") 60 | :gsub("\n", "\\n").."\"" 61 | end 62 | 63 | local function inserts(f, t, n) 64 | local u = {} 65 | for i, v in ipairs(t) do 66 | table.insert(u, f(v, i, u)) 67 | end 68 | return u 69 | end 70 | 71 | local function map(f, t, n) 72 | local u = {} 73 | for i=1, n or len(t) do 74 | u[i]=f(t[i], i) 75 | end 76 | return u 77 | end 78 | 79 | local function each(f, t) 80 | local u = {} 81 | for k, v in pairs(t) do 82 | u[k]=f(v, k) 83 | end 84 | return u 85 | end 86 | 87 | local function filter(f, t, n) 88 | local u = {} 89 | for i=1, n or len(t) do 90 | local x = t[i] 91 | if f(x) then 92 | table.insert(u, x) 93 | end 94 | end 95 | return u 96 | end 97 | 98 | local function contains(t, u, n) 99 | for i=1, n or len(t) do 100 | if t[i] == u then 101 | return true 102 | end 103 | end 104 | end 105 | 106 | function reverse(t) 107 | local u = {} 108 | for k, v in ipairs(t) do 109 | u[len(t) + 1 - k] = v 110 | end 111 | return u 112 | end 113 | 114 | local function copy(t, u) 115 | u = u or {} 116 | for k, v in pairs(t) do 117 | u[k] = v 118 | end 119 | return u 120 | end 121 | 122 | local function keys(t) 123 | local k = {} 124 | for key, _ in pairs(t) do 125 | table.insert(k, key) 126 | end 127 | table.sort(k, function(a, b) 128 | return tostring(a) < tostring(b) 129 | end) 130 | return k 131 | end 132 | 133 | local function torepresentation(callable, arguments) 134 | return string.format("%s(%s)", tostring(callable), 135 | table.concat(map(tostring, arguments), ",")) 136 | end 137 | 138 | local function compose(a, ...) 139 | if not ... then 140 | return a 141 | end 142 | local functions = {...} 143 | return function(...) 144 | local returns = table.pack(a(...)) 145 | for i, fun in ipairs(functions) do 146 | if not (returns[1] ~= nil or returns.n > 1) then 147 | break 148 | end 149 | returns = table.pack(fun(table.unpack(returns, returns.n))) 150 | end 151 | return table.unpack(returns, returns.n) 152 | end 153 | end 154 | 155 | return { 156 | prototype=prototype, 157 | ipairs = ipairs, 158 | len=len, 159 | dotmap=dotmap, 160 | inserts=inserts, 161 | map=map, 162 | filter=filter, 163 | torepresentation=torepresentation, 164 | keys=keys, 165 | copy=copy, 166 | contains=contains, 167 | reverse=reverse, 168 | each=each, 169 | escape=escape, 170 | hasmetatable=hasmetatable, 171 | compose=compose 172 | } 173 | -------------------------------------------------------------------------------- /prototype.lua: -------------------------------------------------------------------------------- 1 | local grammar = require("leftry").grammar 2 | local factor = grammar.factor 3 | local span = grammar.span 4 | 5 | local set = require("leftry.immutable.set") 6 | local set_insert, set_empty = set.insert, set.empty 7 | 8 | -- prototype of an example of a statically compiled leftry grammar. 9 | 10 | local A = factor("A", function(A) return 11 | span(A, "1"), "1" 12 | end) 13 | local B = factor("B", function(B) return 14 | span(B, "2"), A 15 | end) 16 | 17 | -- P == B 18 | 19 | local one = "1" 20 | local two = "2" 21 | 22 | local function P(invariant, position, peek) 23 | if position > #invariant then 24 | return 25 | end 26 | 27 | local rest 28 | 29 | if not rest then 30 | if invariant:sub(position, position + #(one) - 1) == one then 31 | rest = position + #(one) 32 | end 33 | end 34 | if not rest then 35 | return 36 | end 37 | 38 | local done = rest 39 | 40 | while rest and #invariant >= rest do 41 | local index = rest 42 | -- if not met[t] then 43 | if index <= #invariant then 44 | -- span 45 | if invariant:sub(index, index + #(one) - 1) == one then 46 | rest = index + #(one) 47 | done = rest 48 | else 49 | rest = nil 50 | end 51 | end 52 | -- end 53 | end 54 | rest = done 55 | 56 | local done = rest 57 | 58 | while rest and #invariant >= rest do 59 | local index = rest 60 | -- if not met[t] then 61 | if index <= #invariant then 62 | -- span 63 | if invariant:sub(index, index + #(two) - 1) == two then 64 | rest = index + #(two) 65 | done = rest 66 | else 67 | rest = nil 68 | end 69 | end 70 | -- end 71 | end 72 | rest = done 73 | 74 | return rest 75 | end 76 | 77 | print(P(("11112222"), 1)) 78 | 79 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | local leftry = require("leftry") 2 | local lua = require("leftry.language.lua") 3 | local traits = require("leftry.elements.traits") 4 | local reducers = require("leftry.reducers") 5 | 6 | local utils = leftry.utils 7 | local span = leftry.span 8 | local opt = leftry.opt 9 | local rep = leftry.rep 10 | local any = leftry.any 11 | local term = leftry.term 12 | local factor = leftry.factor 13 | local first = reducers.first 14 | 15 | local tests = {} 16 | 17 | --[[ 18 | 19 | Unit Tests. 20 | 21 | ]]-- 22 | 23 | function tests.dotmap() 24 | return {utils.dotmap(function(x) return x + 1 end, 1, 3, 5)}, {2, 4, 6} 25 | end 26 | 27 | function tests.map() 28 | return utils.map(function(x) return x * 2 end, {1, 3, 5}), {2, 6, 10} 29 | end 30 | 31 | function tests.torepresentation() 32 | return {utils.torepresentation("dotmap", {1, 2, 3})}, {"dotmap(1,2,3)"} 33 | end 34 | 35 | function tests.factor_peek_parse_success() 36 | local A = factor("A", function(A) return 37 | span(A, "1"), "1" 38 | end) 39 | local src = ("1"):rep(2) 40 | return {A(src, 1, true)}, {#src + 1} 41 | end 42 | 43 | function tests.factor_parse_success() 44 | local A = factor("A", function(A) return 45 | span(A, "1") % function(initial, value) 46 | return (initial or "") .. value 47 | end, "1" 48 | end) 49 | local src = "111" 50 | return {A(src, 1)}, {#src + 1, "111"} 51 | end 52 | 53 | function tests.factor_first_third_parse_success() 54 | local A = factor("A", function(A) return 55 | span(A, "2", A, "3") % function(initial, value) 56 | return (initial or "") .. value 57 | end, "1" 58 | end) 59 | local src = "1213" 60 | return {A(src, 1)}, {#src + 1, "1213"} 61 | end 62 | 63 | function tests.factor_first_second_parse_success() 64 | local A = factor("A", function(A) return 65 | span(A, A, "3") % function(initial, value) 66 | return (initial or "") .. value 67 | end, "1" 68 | end) 69 | local src = "113" 70 | return {A(src, 1)}, {#src + 1, "113"} 71 | end 72 | 73 | function tests.factor_nested_parse_success() 74 | local A = factor("A", function(A) return 75 | span(A, "1"), "1" 76 | end) 77 | local B = factor("B", function(B) return 78 | span(B, "2"), A 79 | end) 80 | local src = "11112222" 81 | return {B(src, 1, true)}, {#src + 1} 82 | end 83 | 84 | function tests.span_opt_parse_success() 85 | local src = "13" 86 | local rest, values = span("1", opt("2"), "3")(src, 1) 87 | return {rest, values[1], values[3]}, {#src + 1, "1", "3"} 88 | end 89 | 90 | function tests.span_rep_parse_success() 91 | local src = "13" 92 | local rest, values = span("1", rep("2"), "3")(src, 1) 93 | return {rest, values[1], values[3]}, {#src + 1, "1", "3"} 94 | end 95 | 96 | function tests.span_rep1_parse_success() 97 | local src = "122223" 98 | local rest, values = 99 | span( 100 | "1", 101 | rep("2", function(a, b) return (a or "")..b end), 102 | "3")(src, 1) 103 | return {rest, unpack(values or {})}, {#src + 1, "1", "2222", "3"} 104 | end 105 | 106 | function tests.span_parse_success() 107 | local src = "123" 108 | local rest, values = span("1", "2", "3")(src, 1) 109 | return {rest, unpack(values or {})}, {#src + 1, "1", "2", "3"} 110 | end 111 | 112 | function tests.span_parse_failure() 113 | local src = "1" 114 | local rest, values = span("1", "2", "3")(src, 1) 115 | return {rest, unpack(values or {})}, {nil, nil} 116 | end 117 | 118 | function tests.span_as_iterator() 119 | local actual = {} 120 | for rest, values in span("1", "2", "3"), "123123123", 1 do 121 | table.insert(actual, rest) 122 | end 123 | return actual, {4, 7, 10} 124 | end 125 | 126 | function tests.any_parse_success() 127 | local src = "123" 128 | local rest, value = any("1", "2", "3")(src, 1) 129 | return {rest, value}, {2, "1"} 130 | end 131 | 132 | function tests.any_parse_failure() 133 | local src = "4123" 134 | local rest, value = any("1", "2", "3")(src, 1) 135 | return {rest, value}, {nil, nil} 136 | end 137 | 138 | function tests.any_as_iterator() 139 | local src = "123123123" 140 | local actual = {} 141 | for i, values in any("1", "2", "3"), src, 1 do 142 | table.insert(actual, i) 143 | end 144 | return actual, {2, 3, 4, 5, 6, 7, 8, 9, 10} 145 | end 146 | 147 | function tests.lua_exp_binop() 148 | local src = "zzz(1) % 1" 149 | local rest, values = lua.Exp(src, 1) 150 | return {rest, values}, {#src + 1, src} 151 | end 152 | 153 | function tests.lua_string_parse_success() 154 | local src = '"hello world!"' 155 | local rest, values = lua.LiteralString(src, 1) 156 | return {rest, unpack(values or {})}, {#src + 1, "hello world!"} 157 | end 158 | 159 | function tests.lua_exp_parse_success() 160 | local src = '"hello world!"' 161 | local rest, values = lua.Exp(src, 1) 162 | return {rest, unpack(values or {})}, {#src + 1, "hello world!"} 163 | end 164 | 165 | function tests.lua_exp1_parse_success() 166 | local src = '1+1' 167 | local rest, values = lua.Exp(src, 1) 168 | return {rest, unpack(values or {})}, {#src + 1, 1, " + ", 1} 169 | end 170 | 171 | function tests.lua_var_parse_success() 172 | local src = 'print' 173 | local rest, values = lua.Var(src, 1) 174 | return {rest, values}, {#src + 1, "print"} 175 | end 176 | 177 | function tests.lua_prefixexp_parse_success() 178 | local src = 'print' 179 | local rest, values = lua.PrefixExp(src, 1) 180 | return {rest, values}, {#src + 1, "print"} 181 | end 182 | 183 | function tests.lua_args_parse_success() 184 | local src = '(1)' 185 | local rest, values = lua.PrefixExp(src, 1) 186 | return {rest, values}, {#src + 1, src} 187 | end 188 | 189 | function tests.lua_args2_parse_success() 190 | local src = '()' 191 | local rest, values = lua.Args(src, 1, nil) 192 | return {rest}, {#src + 1} 193 | end 194 | 195 | function tests.lua_functioncall0_parse_success() 196 | local src = 'print(1)' 197 | local rest, values = lua.FunctionCall( 198 | src, 1, true) 199 | return {rest}, {#src + 1} 200 | end 201 | 202 | function tests.lua_functioncall_parse_success() 203 | local src = 'print(1)' 204 | local rest, values = lua.FunctionCall(src, 1) 205 | return {rest, values}, 206 | {#src + 1, src} 207 | end 208 | 209 | function tests.lua_functioncall2_parse_success() 210 | local src = 'a()()' 211 | local rest, values = lua.FunctionCall(src, 1) 212 | return {rest}, {#src+1} 213 | -- return {rest, values[1][1], values[1][2][1], values[1][2][3], values[2][1], 214 | -- values[2][3]}, {#src + 1, "a", "(", ")", "(", ")"} 215 | end 216 | 217 | function tests.lua_retstat_parse_success() 218 | local src = 'return (1+1)' 219 | local rest, values = lua.RetStat(src, 1, true) 220 | return {rest, unpack(values or {})}, {#src + 1} 221 | end 222 | 223 | function tests.lua_retstat_parse_failure() 224 | local src = 'returntrue' 225 | return {lua.RetStat(src, 1, true)}, {nil} 226 | end 227 | 228 | function tests.lua_block_parse_success() 229 | local src = 'print(1);return true' 230 | lua.Block:setup() 231 | local rest, values = lua.Block(src, 1, true) 232 | return {rest, unpack(values or {})}, {#src + 1} 233 | end 234 | 235 | function tests.lua_var_parse_failure() 236 | local src = 'a()' 237 | local rest, values = lua.Var(src, 1) 238 | return {rest}, {2} 239 | end 240 | 241 | function tests.lua_search_nonterminal_binop() 242 | lua.BinOp:setup() 243 | return {traits.search_left_nonterminal(lua.BinOp.canonize(), 244 | lua.BinOp)}, {false} 245 | end 246 | 247 | function tests.lua_exp() 248 | local src = 'b()' 249 | local rest, values = lua.PrefixExp(src, 1, true) 250 | return {rest}, {#src + 1} 251 | end 252 | 253 | function tests.lua_stat() 254 | local src = 'local utils = require("leftry").utils' 255 | local rest, values = lua.Stat(src, 1) 256 | return {rest}, {#src + 1} 257 | end 258 | 259 | function tests.lua_block() 260 | local src = [[ 261 | local utils = require("leftry").utils 262 | local grammar = require("leftry").grammar 263 | ]] 264 | local rest, values = lua.Block(src, 1) 265 | return {rest}, {#src + 1} 266 | end 267 | 268 | function tests.lua_table() 269 | local src = 'local test = {}' 270 | local rest, values = lua.Stat(src, 1) 271 | return {rest}, {#src + 1} 272 | end 273 | 274 | function tests.lua_function() 275 | local src =[[function(x) return x end]] 276 | local rest, values = lua.Exp(src, 1) 277 | return {rest}, {#src + 1} 278 | end 279 | 280 | function tests.lua_function1() 281 | local src =[[return {function(x) return x + 1 end}]] 282 | local rest, values = lua.RetStat(src, 1) 283 | return {rest}, {#src + 1} 284 | end 285 | 286 | function tests.lua_function2() 287 | local src =[[a.b()]] 288 | local rest, values = lua.Exp(src, 1) 289 | return {rest}, {#src + 1} 290 | end 291 | 292 | function tests.lua_length() 293 | local src =[[#src]] 294 | local rest, values = lua.Exp(src, 1) 295 | return {rest}, {#src + 1} 296 | end 297 | 298 | function tests.lua_big_for() 299 | local src = [[ 300 | for _, name in ipairs(utils.keys(tests)) do 301 | if not arg[1] or arg[1] == name then 302 | local test = tests[name] 303 | io.write(("test (%s)"):format(name)) 304 | local passed, n = compare(test()) 305 | if passed then 306 | io.write(("...%s ok\n"):format(n)) 307 | passes = passes + 1 308 | else 309 | io.write(("...%s total\n"):format(n)) 310 | fails = fails + 1 311 | end 312 | end 313 | end 314 | ]] 315 | local rest, values = lua.Stat(src, 1) 316 | return {rest}, {#src + 1} 317 | end 318 | 319 | function tests.lua_big_function() 320 | local src = [[ 321 | Comment = factor("Comment", function() return 322 | grammar.span("--", function(invariant, position) 323 | while invariant:sub(position, position) ~= "\n" do 324 | position = position + 1 325 | end 326 | return position 327 | end) end) 328 | ]] 329 | local rest, values = lua.Stat(src, 1) 330 | return {rest}, {#src + 1} 331 | end 332 | 333 | function tests.lua_function3() 334 | local src = [[a.b.c()]] 335 | local rest, values = lua.PrefixExp(src, 1) 336 | return {rest}, {#src + 1} 337 | end 338 | 339 | 340 | function tests.lua_var() 341 | local src = [[a.b.c]] 342 | local rest, values = lua.Var(src, 1, #src + 1) 343 | return {rest}, {#src + 1} 344 | end 345 | 346 | function tests.lua_var0() 347 | local src = [[a.b.c]] 348 | lua.PrefixExp:setup() 349 | local rest, values = lua.PrefixExp.canon(src, 1, true, 2) 350 | return {rest}, {2} 351 | end 352 | 353 | function tests.lua_big_parse() 354 | local f = io.open("test.lua") 355 | local invariant = f:read("*all") 356 | f.close() 357 | local rest = lua.Chunk(invariant, 1) 358 | return {rest}, {#invariant + 1} 359 | end 360 | 361 | function tests.lua_big_parse2() 362 | local f = io.open("leftry/language/lua.lua") 363 | local invariant = f:read("*all") 364 | f.close() 365 | local rest = lua.Chunk(invariant, 1) 366 | return {rest}, {#invariant + 1} 367 | end 368 | 369 | function tests.lua_big_parse3() 370 | local f = io.open("leftry/elements/factor.lua") 371 | local invariant = f:read("*all") 372 | f.close() 373 | 374 | local rest = lua.Chunk(invariant, 1) 375 | 376 | return {rest}, {#invariant + 1} 377 | end 378 | 379 | local function remainder(invariant) 380 | return #invariant.source 381 | end 382 | 383 | function tests.factor_match() 384 | local f = io.open("test.lua") 385 | local text = f:read("*a") 386 | f:close() 387 | return { 388 | -- Uses exact match, uses pattern to extract value. 389 | lua.Chunk:match(text, lua.FunctionCall, term("table")), 390 | -- Uses prefix match. returns entire range of nonterminal. 391 | text:sub(lua.Chunk:find(text, lua.FunctionCall, term("table"))) 392 | }, {"table", "table.insert(actual, rest)\n "} 393 | end 394 | 395 | function tests.gfind() 396 | local f = io.open("test.lua") 397 | local text = f:read("*a") 398 | f:close() 399 | local matches = {} 400 | -- Uses prefix match. returns entire range of nonterminal. 401 | for index, to in lua.Chunk:gfind(text, lua.FunctionCall, term("table")) do 402 | table.insert(matches, text:sub(index, to)) 403 | end 404 | return { 405 | "table.insert(actual, rest)\n ", 406 | "table.insert(actual, i)\n ", 407 | "table.insert(matches, text:sub(index, to))\n ", 408 | "table.insert(matches, value)\n "}, matches 409 | end 410 | 411 | function tests.gmatch() 412 | local f = io.open("test.lua") 413 | local text = f:read("*a") 414 | f:close() 415 | local matches = {} 416 | -- Uses exact match, uses pattern to extract value. 417 | for value in lua.Chunk:gmatch(text, lua.Stat, 418 | lua.span("local", lua.NameList, "=", "require", lua.Args) 419 | % reducers.second) do 420 | table.insert(matches, value) 421 | end 422 | return {"leftry", "lua", "traits", "reducers"}, matches 423 | end 424 | 425 | local function compare(actual, expected) 426 | assert(actual) assert(expected) 427 | local passed = true 428 | for i=1, math.max(table.maxn(actual), table.maxn(expected)) do 429 | local value = actual[i] 430 | if tostring(value) == tostring(expected[i]) then 431 | -- print((" passed %s == %s"):format( 432 | -- tostring(value), 433 | -- tostring(expected[i]))) 434 | else 435 | print(("\n failed %s == %s"):format( 436 | tostring(value), 437 | tostring(expected[i]))) 438 | passed = false 439 | end 440 | end 441 | return passed, #expected 442 | end 443 | 444 | local passed = true 445 | local passes, fails = 0, 0 446 | for _, name in ipairs(utils.keys(tests)) do 447 | if not arg[1] or arg[1] == name then 448 | local test = tests[name] 449 | io.write(("test (%s)"):format(name)) 450 | local passed, n = compare(test()) 451 | if passed then 452 | io.write(("...%s ok\n"):format(n)) 453 | passes = passes + 1 454 | else 455 | io.write(("...%s total\n"):format(n)) 456 | fails = fails + 1 457 | end 458 | end 459 | end 460 | 461 | print(("\n%s tests, %s passes, %s fails"):format(passes+fails, passes,fails)) 462 | --------------------------------------------------------------------------------