diff --git a/impl/velo.lua/src/velo.lua b/impl/velo.lua/src/velo.lua index 537f9b5..33b6f56 100755 --- a/impl/velo.lua/src/velo.lua +++ b/impl/velo.lua/src/velo.lua @@ -3,7 +3,7 @@ --[[ ========== DEBUG ========= ]]-- local debug = function(s) - print("--> " .. s) + print("--> (" .. s .. ")") end --[[ ========== EXCEPTIONS ========= ]]-- @@ -32,13 +32,13 @@ Script = {} Script.new = function(exprs) - methods = {} + local methods = {} methods.eval = function(obj, args) debug "eval #{self} on #{obj} with #{args}" local e = nil for i,expr in ipairs(exprs) do - e = expr:eval(obj, args) + e = expr.eval(obj, args) end return e end @@ -56,14 +56,14 @@ Assignment = {} Assignment.new = function(object, field, expr) - methods = {} + local methods = {} methods.eval = function(obj, args) debug "eval #{self} on #{obj} with #{args}" - local val = expr:eval(obj, args) - local receiver = object:eval(obj, args) + local val = expr.eval(obj, args) + local receiver = object.eval(obj, args) debug "setting #{@field} on #{receiver}" - receiver:set(field, val) + receiver.set(field, val) return val end @@ -81,7 +81,7 @@ Self = {} Self.new = function() - methods = {} + local methods = {} methods.eval = function(obj, args) debug "eval #{self} on #{obj} with #{args}" @@ -97,7 +97,11 @@ Lookup = {} Lookup.new = function(_receiver, _ident) - methods = {} + local methods = {} + methods.class = "Lookup" + + debug(tostring(_receiver)) + _receiver.foo = "hi" methods.receiver = function() return _receiver @@ -109,34 +113,33 @@ methods.eval = function(obj, args) debug "eval #{self} on #{obj} with #{args}" - local receiver = _receiver:eval(obj, args) - return receiver:lookup(_ident) + local receiver = _receiver.eval(obj, args) + return receiver.lookup(_ident) end methods.to_s = function() return "Lookup(" .. _receiver.to_s() .. "," .. _ident .. ")" end - + return methods end MethodCall = {} MethodCall.new = function(method_expr, exprs) - methods = {} + local methods = {} methods.eval = function(obj, args) debug "eval #{self} on #{obj} with #{args}" local new_args = {} for i,expr in ipairs(exprs) do - new_args.push(expr:eval(obj, args)) - end - local method = method_expr:eval(obj, args) + new_args.push(expr.eval(obj, args)) + end + local method = method_expr.eval(obj, args) debug "arguments evaluated, now calling #{@method_expr} -> #{method}" - -- OOOH - if method.is_a_VeloMethod ~= nil then + if method.class == "VeloMethod" then --# xxx show receiver (method's bound object) in debug debug "running real method #{method} w/args #{args}" - return method:run(new_args) + return method.run(new_args) else debug "just returning non-method (#{method}) on call" return method @@ -157,7 +160,7 @@ Argument = {} Argument.new = function(num) num = num - 1 - methods = {} + local methods = {} methods.eval = function(obj, args) debug "eval #{self} on #{obj} with #{args}" @@ -173,7 +176,7 @@ StringLiteral = {} StringLiteral.new = function(text) - methods = {} + local methods = {} methods.eval = function(obj, args) debug "eval #{self} on #{obj} with #{args}" @@ -190,7 +193,7 @@ -- SANITY TEST local m = MethodCall.new(Self.new(), {Argument.new(1), StringLiteral.new("jonkers")}) local a = Assignment.new(m, "bar", Lookup.new(Self.new(), "foo")) -s = Script.new({a, Self.new()}); print(s:to_s()) +s = Script.new({a, Self.new()}); print(s.to_s()) function isdigit(s) return string.find("0123456789", s, 1, true) ~= nil @@ -210,6 +213,10 @@ function isalnum(s) return isalpha(s) or isdigit(s) +end + +function issep(s) + return string.find("(),.;=", s, 1, true) ~= nil end --[[ ========== SCANNER ========= ]]-- @@ -220,7 +227,7 @@ local _text = nil local _type = nil - methods = {} + local methods = {} methods.text = function() return _text end methods.type = function() return _type end @@ -267,8 +274,7 @@ -- check for any single character tokens local c = string:sub(1,1) - local set = "(),.;=" - if set:find(c, 1, true) ~= nil then + if issep(c) then string = string:sub(2) methods.set_token(c, "seperator") return @@ -277,7 +283,7 @@ -- check for arguments if string:sub(1,1) == "#" then local len = 0 - while isdigit(string:sub(2+len,2+len)) do + while isdigit(string:sub(2+len,2+len)) and len <= string:len() do len = len + 1 end if len > 0 then @@ -291,7 +297,7 @@ -- check for strings of "word" characters if isalnum(string:sub(1,1)) then local len = 0 - while isalnum(string:sub(1+len,1+len)) do + while isalnum(string:sub(1+len,1+len)) and len <= string:len() do len = len + 1 end local word = string:sub(1, 1+len-1) @@ -327,7 +333,7 @@ methods.set_token('UNKNOWN', 'UNKNOWN') end - + methods.consume = function(s) if _text == s then methods.scan() @@ -364,7 +370,12 @@ end end if not good then - raise_VeloSyntaxError("expected '#{t}', found '#{@text}' (#{@type})") + local tstring = "" + for i,v in ipairs(types) do + tstring = tstring .. v .. "," + end + raise_VeloSyntaxError("expected '" .. tstring .. "', found '" .. + _text .. "' (" .. _type .. ")") end end @@ -375,11 +386,13 @@ end -- SANITY TEST +--[[ x = Scanner.new(" \n (.#53) jonkers,031jon {sk}{str{ing}ity}w ") while not x.is_eof() do print(x.text() .. ":" .. x.type()) x.scan() end +]]-- --[[ ========== PARSER ========== ]]-- @@ -406,125 +419,122 @@ # | "(" [EOL] Expr ")" # . -class Parser - def initialize s - @scanner = Scanner.new(s) - end - - def script - debug "parsing Script production" - exprs = [] - @scanner.consume_type "EOL" - e = expr - while not e.nil? - @scanner.expect_types ["EOL", "EOF"] - exprs.push(e) - @scanner.consume_type "EOL" - e = expr - end - Script.new(exprs) - end - - def expr - debug "parsing Expr production" - if (['EOL', 'EOF'].include? @scanner.type or [')', ','].include? @scanner.text) - return nil - end - receiver = base # could be Expr, StringLit, Arg - if (['EOL', 'EOF'].include? @scanner.type or [')', ','].include? @scanner.text) - return MethodCall.new(receiver, []) - end - while @scanner.consume '.' - @scanner.consume_type 'EOL' - debug "parsing .ident" - ident = @scanner.text - @scanner.scan - receiver = Lookup.new(MethodCall.new(receiver, []), ident) - end - if @scanner.consume '=' - # this is an assignment, so we must resolve the reciever chain - # as follows: a.b.c = foo becomes - # lookup(a, b).set(c, foo) - debug "unlookuping" - ident = nil - if receiver.is_a? Lookup - ident = receiver.ident - receiver = receiver.receiver - else - raise VeloSyntaxError, "assignment requires lvalue, but we have '#{@receiver}'" - end - debug "parsing assignment" - @scanner.consume_type 'EOL' - e = expr - return Assignment.new(receiver, ident, e) - elsif @scanner.type == 'EOF' or @scanner.type == 'EOL' - # this is a plain value, so we must resolve the reciever chain - # as follows: a.b.c becomes - # lookup(lookup(a, b), c) - debug "not a method call" - return MethodCall.new(receiver, []) - else - # this is a method call, so we must resolve the reciever chain - # as follows: a.b.c args becomes - # methodcall(lookup(lookup(a, b), c), args) - debug "parsing method call args" - args = [] - e = expr - args.push(e) unless e.nil? - while @scanner.consume "," - @scanner.consume_type 'EOL' - e = expr - args.push(e) unless e.nil? - end - MethodCall.new(receiver, args) - end - end - - def base - debug "parsing Base production" - if @scanner.consume "(" - debug "parsing parens" - @scanner.consume_type 'EOL' - e = expr - @scanner.expect ")" - return e - elsif @scanner.type == 'strlit' - debug "parsing strlit" - s = @scanner.text - @scanner.scan - return StringLiteral.new(s) - elsif @scanner.type == 'arg' - debug "parsing arg" - num = @scanner.text.to_i - @scanner.scan - return Argument.new(num) - elsif @scanner.type == 'ident' - debug "parsing ident" - ident = @scanner.text - @scanner.scan - return Lookup.new(Self.new, ident) - else - raise VeloSyntaxError, "unexpected '#{@scanner.text}'" - end - end -end - -if $0 == __FILE__ - #$debug = true - p = Parser.new(ARGV[0]) - s = p.script - puts s - - if $debug - s1 = Parser.new('m a, m b, c').script - s2 = Parser.new('m a, (m b, c)').script - s3 = Parser.new('m a, (m b), c').script - puts s1 - puts s2 - puts s3 - end -end ]]-- + +Parser = {} +Parser.new = function(s) + local scanner = Scanner.new(s) + + local methods = {} + + methods.script = function() + debug "parsing Script production" + local exprs = {} + scanner.consume_type "EOL" + local e = methods.expr() + while e ~= nil do + scanner.expect_types {"EOL", "EOF"} + exprs[#exprs+1] = e + scanner.consume_type "EOL" + e = methods.expr() + end + return Script.new(exprs) + end + + methods.expr = function() + debug "parsing Expr production" + if (scanner.type() == "EOL" or scanner.type() == "EOF" or + scanner.text() == ")" or scanner.text() == ",") then + return nil + end + local receiver = methods.base() --# could be Expr, StringLit, Arg + if (scanner.type() == "EOL" or scanner.type() == "EOF" or + scanner.text() == ")" or scanner.text() == ",") then + return MethodCall.new(receiver, {}) + end + while scanner.consume '.' do + scanner.consume_type 'EOL' + debug "parsing .ident" + ident = scanner.text() + scanner.scan() + receiver = Lookup.new(MethodCall.new(receiver, {}), ident) + end + if scanner.consume '=' then + -- this is an assignment, so we must resolve the reciever chain + -- as follows: a.b.c = foo becomes lookup(a, b).set(c, foo) + debug "unlookuping" + local ident = nil + if receiver.class == "Lookup" ~= nil then + ident = receiver.ident() + receiver = receiver.receiver() + else + raise_VeloSyntaxError("assignment requires lvalue, but we have '#{@receiver}'") + end + debug "parsing assignment" + scanner.consume_type 'EOL' + e = methods.expr() + return Assignment.new(receiver, ident, e) + elseif scanner.type() == 'EOF' or scanner.type() == 'EOL' then + -- this is a plain value, so we must resolve the reciever chain + -- as follows: a.b.c becomes lookup(lookup(a, b), c) + debug "not a method call" + return MethodCall.new(receiver, {}) + else + -- this is a method call, so we must resolve the reciever chain + -- as follows: a.b.c args becomes + -- methodcall(lookup(lookup(a, b), c), args) + debug "parsing method call args" + local args = {} + local e = methods.expr() + if e ~= nil then + args[#args+1] = e + end + while scanner.consume "," do + scanner.consume_type 'EOL' + e = methods.expr() + if e ~= nil then + args[#args+1] = e + end + end + return MethodCall.new(receiver, args) + end + end + + methods.base = function() + debug "parsing Base production" + if scanner.consume "(" then + debug "parsing parens" + scanner.consume_type "EOL" + e = methods.expr() + scanner.expect ")" + return e + elseif scanner.type() == "strlit" then + debug "parsing strlit" + s = scanner.text() + scanner.scan() + return StringLiteral.new(s) + elseif scanner.type() == "arg" then + debug "parsing arg" + num = scanner.text().to_i() + scanner.scan() + return Argument.new(num) + elseif scanner.type() == "ident" then + debug "parsing ident" + ident = scanner.text() + scanner.scan() + return Lookup.new(Self.new(), ident) + else + raise_VeloSyntaxError("unexpected '#{@scanner.text}'") + end + end + + return methods +end + +-- SANITY TEST +print(Parser.new('m a, m b, c').script().to_s()) +print(Parser.new('m a, (m b, c)').script().to_s()) +print(Parser.new('m a, (m b), c').script().to_s()) --[[ ========== RUNTIME ========= ]]--