Parser has been translated.
Cat's Eye Technologies
11 years ago
2 | 2 | --[[ ========== DEBUG ========= ]]-- |
3 | 3 | |
4 | 4 | local debug = function(s) |
5 | print("--> " .. s) | |
5 | print("--> (" .. s .. ")") | |
6 | 6 | end |
7 | 7 | |
8 | 8 | --[[ ========== EXCEPTIONS ========= ]]-- |
31 | 31 | |
32 | 32 | Script = {} |
33 | 33 | Script.new = function(exprs) |
34 | methods = {} | |
34 | local methods = {} | |
35 | 35 | |
36 | 36 | methods.eval = function(obj, args) |
37 | 37 | debug "eval #{self} on #{obj} with #{args}" |
38 | 38 | local e = nil |
39 | 39 | for i,expr in ipairs(exprs) do |
40 | e = expr:eval(obj, args) | |
40 | e = expr.eval(obj, args) | |
41 | 41 | end |
42 | 42 | return e |
43 | 43 | end |
55 | 55 | |
56 | 56 | Assignment = {} |
57 | 57 | Assignment.new = function(object, field, expr) |
58 | methods = {} | |
58 | local methods = {} | |
59 | 59 | |
60 | 60 | methods.eval = function(obj, args) |
61 | 61 | debug "eval #{self} on #{obj} with #{args}" |
62 | local val = expr:eval(obj, args) | |
63 | local receiver = object:eval(obj, args) | |
62 | local val = expr.eval(obj, args) | |
63 | local receiver = object.eval(obj, args) | |
64 | 64 | debug "setting #{@field} on #{receiver}" |
65 | receiver:set(field, val) | |
65 | receiver.set(field, val) | |
66 | 66 | return val |
67 | 67 | end |
68 | 68 | |
80 | 80 | |
81 | 81 | Self = {} |
82 | 82 | Self.new = function() |
83 | methods = {} | |
83 | local methods = {} | |
84 | 84 | |
85 | 85 | methods.eval = function(obj, args) |
86 | 86 | debug "eval #{self} on #{obj} with #{args}" |
96 | 96 | |
97 | 97 | Lookup = {} |
98 | 98 | Lookup.new = function(_receiver, _ident) |
99 | methods = {} | |
99 | local methods = {} | |
100 | methods.class = "Lookup" | |
101 | ||
102 | debug(tostring(_receiver)) | |
103 | _receiver.foo = "hi" | |
100 | 104 | |
101 | 105 | methods.receiver = function() |
102 | 106 | return _receiver |
108 | 112 | |
109 | 113 | methods.eval = function(obj, args) |
110 | 114 | debug "eval #{self} on #{obj} with #{args}" |
111 | local receiver = _receiver:eval(obj, args) | |
112 | return receiver:lookup(_ident) | |
115 | local receiver = _receiver.eval(obj, args) | |
116 | return receiver.lookup(_ident) | |
113 | 117 | end |
114 | 118 | |
115 | 119 | methods.to_s = function() |
116 | 120 | return "Lookup(" .. _receiver.to_s() .. "," .. _ident .. ")" |
117 | 121 | end |
118 | ||
122 | ||
119 | 123 | return methods |
120 | 124 | end |
121 | 125 | |
122 | 126 | MethodCall = {} |
123 | 127 | MethodCall.new = function(method_expr, exprs) |
124 | methods = {} | |
128 | local methods = {} | |
125 | 129 | |
126 | 130 | methods.eval = function(obj, args) |
127 | 131 | debug "eval #{self} on #{obj} with #{args}" |
128 | 132 | local new_args = {} |
129 | 133 | for i,expr in ipairs(exprs) do |
130 | new_args.push(expr:eval(obj, args)) | |
131 | end | |
132 | local method = method_expr:eval(obj, args) | |
134 | new_args.push(expr.eval(obj, args)) | |
135 | end | |
136 | local method = method_expr.eval(obj, args) | |
133 | 137 | debug "arguments evaluated, now calling #{@method_expr} -> #{method}" |
134 | -- OOOH | |
135 | if method.is_a_VeloMethod ~= nil then | |
138 | if method.class == "VeloMethod" then | |
136 | 139 | --# xxx show receiver (method's bound object) in debug |
137 | 140 | debug "running real method #{method} w/args #{args}" |
138 | return method:run(new_args) | |
141 | return method.run(new_args) | |
139 | 142 | else |
140 | 143 | debug "just returning non-method (#{method}) on call" |
141 | 144 | return method |
156 | 159 | Argument = {} |
157 | 160 | Argument.new = function(num) |
158 | 161 | num = num - 1 |
159 | methods = {} | |
162 | local methods = {} | |
160 | 163 | |
161 | 164 | methods.eval = function(obj, args) |
162 | 165 | debug "eval #{self} on #{obj} with #{args}" |
172 | 175 | |
173 | 176 | StringLiteral = {} |
174 | 177 | StringLiteral.new = function(text) |
175 | methods = {} | |
178 | local methods = {} | |
176 | 179 | |
177 | 180 | methods.eval = function(obj, args) |
178 | 181 | debug "eval #{self} on #{obj} with #{args}" |
189 | 192 | -- SANITY TEST |
190 | 193 | local m = MethodCall.new(Self.new(), {Argument.new(1), StringLiteral.new("jonkers")}) |
191 | 194 | local a = Assignment.new(m, "bar", Lookup.new(Self.new(), "foo")) |
192 | s = Script.new({a, Self.new()}); print(s:to_s()) | |
195 | s = Script.new({a, Self.new()}); print(s.to_s()) | |
193 | 196 | |
194 | 197 | function isdigit(s) |
195 | 198 | return string.find("0123456789", s, 1, true) ~= nil |
209 | 212 | |
210 | 213 | function isalnum(s) |
211 | 214 | return isalpha(s) or isdigit(s) |
215 | end | |
216 | ||
217 | function issep(s) | |
218 | return string.find("(),.;=", s, 1, true) ~= nil | |
212 | 219 | end |
213 | 220 | |
214 | 221 | --[[ ========== SCANNER ========= ]]-- |
219 | 226 | local _text = nil |
220 | 227 | local _type = nil |
221 | 228 | |
222 | methods = {} | |
229 | local methods = {} | |
223 | 230 | |
224 | 231 | methods.text = function() return _text end |
225 | 232 | methods.type = function() return _type end |
266 | 273 | |
267 | 274 | -- check for any single character tokens |
268 | 275 | local c = string:sub(1,1) |
269 | local set = "(),.;=" | |
270 | if set:find(c, 1, true) ~= nil then | |
276 | if issep(c) then | |
271 | 277 | string = string:sub(2) |
272 | 278 | methods.set_token(c, "seperator") |
273 | 279 | return |
276 | 282 | -- check for arguments |
277 | 283 | if string:sub(1,1) == "#" then |
278 | 284 | local len = 0 |
279 | while isdigit(string:sub(2+len,2+len)) do | |
285 | while isdigit(string:sub(2+len,2+len)) and len <= string:len() do | |
280 | 286 | len = len + 1 |
281 | 287 | end |
282 | 288 | if len > 0 then |
290 | 296 | -- check for strings of "word" characters |
291 | 297 | if isalnum(string:sub(1,1)) then |
292 | 298 | local len = 0 |
293 | while isalnum(string:sub(1+len,1+len)) do | |
299 | while isalnum(string:sub(1+len,1+len)) and len <= string:len() do | |
294 | 300 | len = len + 1 |
295 | 301 | end |
296 | 302 | local word = string:sub(1, 1+len-1) |
326 | 332 | |
327 | 333 | methods.set_token('UNKNOWN', 'UNKNOWN') |
328 | 334 | end |
329 | ||
335 | ||
330 | 336 | methods.consume = function(s) |
331 | 337 | if _text == s then |
332 | 338 | methods.scan() |
363 | 369 | end |
364 | 370 | end |
365 | 371 | if not good then |
366 | raise_VeloSyntaxError("expected '#{t}', found '#{@text}' (#{@type})") | |
372 | local tstring = "" | |
373 | for i,v in ipairs(types) do | |
374 | tstring = tstring .. v .. "," | |
375 | end | |
376 | raise_VeloSyntaxError("expected '" .. tstring .. "', found '" .. | |
377 | _text .. "' (" .. _type .. ")") | |
367 | 378 | end |
368 | 379 | end |
369 | 380 | |
374 | 385 | end |
375 | 386 | |
376 | 387 | -- SANITY TEST |
388 | --[[ | |
377 | 389 | x = Scanner.new(" \n (.#53) jonkers,031jon {sk}{str{ing}ity}w ") |
378 | 390 | while not x.is_eof() do |
379 | 391 | print(x.text() .. ":" .. x.type()) |
380 | 392 | x.scan() |
381 | 393 | end |
394 | ]]-- | |
382 | 395 | |
383 | 396 | --[[ ========== PARSER ========== ]]-- |
384 | 397 | |
405 | 418 | # | "(" [EOL] Expr ")" |
406 | 419 | # . |
407 | 420 | |
408 | class Parser | |
409 | def initialize s | |
410 | @scanner = Scanner.new(s) | |
411 | end | |
412 | ||
413 | def script | |
414 | debug "parsing Script production" | |
415 | exprs = [] | |
416 | @scanner.consume_type "EOL" | |
417 | e = expr | |
418 | while not e.nil? | |
419 | @scanner.expect_types ["EOL", "EOF"] | |
420 | exprs.push(e) | |
421 | @scanner.consume_type "EOL" | |
422 | e = expr | |
423 | end | |
424 | Script.new(exprs) | |
425 | end | |
426 | ||
427 | def expr | |
428 | debug "parsing Expr production" | |
429 | if (['EOL', 'EOF'].include? @scanner.type or [')', ','].include? @scanner.text) | |
430 | return nil | |
431 | end | |
432 | receiver = base # could be Expr, StringLit, Arg | |
433 | if (['EOL', 'EOF'].include? @scanner.type or [')', ','].include? @scanner.text) | |
434 | return MethodCall.new(receiver, []) | |
435 | end | |
436 | while @scanner.consume '.' | |
437 | @scanner.consume_type 'EOL' | |
438 | debug "parsing .ident" | |
439 | ident = @scanner.text | |
440 | @scanner.scan | |
441 | receiver = Lookup.new(MethodCall.new(receiver, []), ident) | |
442 | end | |
443 | if @scanner.consume '=' | |
444 | # this is an assignment, so we must resolve the reciever chain | |
445 | # as follows: a.b.c = foo becomes | |
446 | # lookup(a, b).set(c, foo) | |
447 | debug "unlookuping" | |
448 | ident = nil | |
449 | if receiver.is_a? Lookup | |
450 | ident = receiver.ident | |
451 | receiver = receiver.receiver | |
452 | else | |
453 | raise VeloSyntaxError, "assignment requires lvalue, but we have '#{@receiver}'" | |
454 | end | |
455 | debug "parsing assignment" | |
456 | @scanner.consume_type 'EOL' | |
457 | e = expr | |
458 | return Assignment.new(receiver, ident, e) | |
459 | elsif @scanner.type == 'EOF' or @scanner.type == 'EOL' | |
460 | # this is a plain value, so we must resolve the reciever chain | |
461 | # as follows: a.b.c becomes | |
462 | # lookup(lookup(a, b), c) | |
463 | debug "not a method call" | |
464 | return MethodCall.new(receiver, []) | |
465 | else | |
466 | # this is a method call, so we must resolve the reciever chain | |
467 | # as follows: a.b.c args becomes | |
468 | # methodcall(lookup(lookup(a, b), c), args) | |
469 | debug "parsing method call args" | |
470 | args = [] | |
471 | e = expr | |
472 | args.push(e) unless e.nil? | |
473 | while @scanner.consume "," | |
474 | @scanner.consume_type 'EOL' | |
475 | e = expr | |
476 | args.push(e) unless e.nil? | |
477 | end | |
478 | MethodCall.new(receiver, args) | |
479 | end | |
480 | end | |
481 | ||
482 | def base | |
483 | debug "parsing Base production" | |
484 | if @scanner.consume "(" | |
485 | debug "parsing parens" | |
486 | @scanner.consume_type 'EOL' | |
487 | e = expr | |
488 | @scanner.expect ")" | |
489 | return e | |
490 | elsif @scanner.type == 'strlit' | |
491 | debug "parsing strlit" | |
492 | s = @scanner.text | |
493 | @scanner.scan | |
494 | return StringLiteral.new(s) | |
495 | elsif @scanner.type == 'arg' | |
496 | debug "parsing arg" | |
497 | num = @scanner.text.to_i | |
498 | @scanner.scan | |
499 | return Argument.new(num) | |
500 | elsif @scanner.type == 'ident' | |
501 | debug "parsing ident" | |
502 | ident = @scanner.text | |
503 | @scanner.scan | |
504 | return Lookup.new(Self.new, ident) | |
505 | else | |
506 | raise VeloSyntaxError, "unexpected '#{@scanner.text}'" | |
507 | end | |
508 | end | |
509 | end | |
510 | ||
511 | if $0 == __FILE__ | |
512 | #$debug = true | |
513 | p = Parser.new(ARGV[0]) | |
514 | s = p.script | |
515 | puts s | |
516 | ||
517 | if $debug | |
518 | s1 = Parser.new('m a, m b, c').script | |
519 | s2 = Parser.new('m a, (m b, c)').script | |
520 | s3 = Parser.new('m a, (m b), c').script | |
521 | puts s1 | |
522 | puts s2 | |
523 | puts s3 | |
524 | end | |
525 | end | |
526 | 421 | ]]-- |
422 | ||
423 | Parser = {} | |
424 | Parser.new = function(s) | |
425 | local scanner = Scanner.new(s) | |
426 | ||
427 | local methods = {} | |
428 | ||
429 | methods.script = function() | |
430 | debug "parsing Script production" | |
431 | local exprs = {} | |
432 | scanner.consume_type "EOL" | |
433 | local e = methods.expr() | |
434 | while e ~= nil do | |
435 | scanner.expect_types {"EOL", "EOF"} | |
436 | exprs[#exprs+1] = e | |
437 | scanner.consume_type "EOL" | |
438 | e = methods.expr() | |
439 | end | |
440 | return Script.new(exprs) | |
441 | end | |
442 | ||
443 | methods.expr = function() | |
444 | debug "parsing Expr production" | |
445 | if (scanner.type() == "EOL" or scanner.type() == "EOF" or | |
446 | scanner.text() == ")" or scanner.text() == ",") then | |
447 | return nil | |
448 | end | |
449 | local receiver = methods.base() --# could be Expr, StringLit, Arg | |
450 | if (scanner.type() == "EOL" or scanner.type() == "EOF" or | |
451 | scanner.text() == ")" or scanner.text() == ",") then | |
452 | return MethodCall.new(receiver, {}) | |
453 | end | |
454 | while scanner.consume '.' do | |
455 | scanner.consume_type 'EOL' | |
456 | debug "parsing .ident" | |
457 | ident = scanner.text() | |
458 | scanner.scan() | |
459 | receiver = Lookup.new(MethodCall.new(receiver, {}), ident) | |
460 | end | |
461 | if scanner.consume '=' then | |
462 | -- this is an assignment, so we must resolve the reciever chain | |
463 | -- as follows: a.b.c = foo becomes lookup(a, b).set(c, foo) | |
464 | debug "unlookuping" | |
465 | local ident = nil | |
466 | if receiver.class == "Lookup" ~= nil then | |
467 | ident = receiver.ident() | |
468 | receiver = receiver.receiver() | |
469 | else | |
470 | raise_VeloSyntaxError("assignment requires lvalue, but we have '#{@receiver}'") | |
471 | end | |
472 | debug "parsing assignment" | |
473 | scanner.consume_type 'EOL' | |
474 | e = methods.expr() | |
475 | return Assignment.new(receiver, ident, e) | |
476 | elseif scanner.type() == 'EOF' or scanner.type() == 'EOL' then | |
477 | -- this is a plain value, so we must resolve the reciever chain | |
478 | -- as follows: a.b.c becomes lookup(lookup(a, b), c) | |
479 | debug "not a method call" | |
480 | return MethodCall.new(receiver, {}) | |
481 | else | |
482 | -- this is a method call, so we must resolve the reciever chain | |
483 | -- as follows: a.b.c args becomes | |
484 | -- methodcall(lookup(lookup(a, b), c), args) | |
485 | debug "parsing method call args" | |
486 | local args = {} | |
487 | local e = methods.expr() | |
488 | if e ~= nil then | |
489 | args[#args+1] = e | |
490 | end | |
491 | while scanner.consume "," do | |
492 | scanner.consume_type 'EOL' | |
493 | e = methods.expr() | |
494 | if e ~= nil then | |
495 | args[#args+1] = e | |
496 | end | |
497 | end | |
498 | return MethodCall.new(receiver, args) | |
499 | end | |
500 | end | |
501 | ||
502 | methods.base = function() | |
503 | debug "parsing Base production" | |
504 | if scanner.consume "(" then | |
505 | debug "parsing parens" | |
506 | scanner.consume_type "EOL" | |
507 | e = methods.expr() | |
508 | scanner.expect ")" | |
509 | return e | |
510 | elseif scanner.type() == "strlit" then | |
511 | debug "parsing strlit" | |
512 | s = scanner.text() | |
513 | scanner.scan() | |
514 | return StringLiteral.new(s) | |
515 | elseif scanner.type() == "arg" then | |
516 | debug "parsing arg" | |
517 | num = scanner.text().to_i() | |
518 | scanner.scan() | |
519 | return Argument.new(num) | |
520 | elseif scanner.type() == "ident" then | |
521 | debug "parsing ident" | |
522 | ident = scanner.text() | |
523 | scanner.scan() | |
524 | return Lookup.new(Self.new(), ident) | |
525 | else | |
526 | raise_VeloSyntaxError("unexpected '#{@scanner.text}'") | |
527 | end | |
528 | end | |
529 | ||
530 | return methods | |
531 | end | |
532 | ||
533 | -- SANITY TEST | |
534 | print(Parser.new('m a, m b, c').script().to_s()) | |
535 | print(Parser.new('m a, (m b, c)').script().to_s()) | |
536 | print(Parser.new('m a, (m b), c').script().to_s()) | |
527 | 537 | |
528 | 538 | --[[ ========== RUNTIME ========= ]]-- |
529 | 539 |