Created
June 21, 2015 20:53
-
-
Save randrews/23c68a2a542a20a86fdf to your computer and use it in GitHub Desktop.
Toy calculator in Lua, version 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
setmetatable(_ENV, { __index=lpeg }) | |
Scopes = { {} } | |
function eval_expr(expr) | |
local accum = eval(expr[2]) -- because 1 is "expr" | |
for i = 3, #expr, 2 do | |
local operator = expr[i] | |
local num2 = eval(expr[i+1]) | |
if operator == '+' then | |
accum = accum + num2 | |
elseif operator == '-' then | |
accum = accum - num2 | |
elseif operator == '*' then | |
accum = accum * num2 | |
elseif operator == '/' then | |
accum = accum / num2 | |
end | |
end | |
return accum | |
end | |
function eval_bool(expr) | |
local num1 = eval(expr[2]) | |
local operator = expr[3] | |
local num2 = eval(expr[4]) | |
if operator == '<' then | |
return num1 < num2 | |
elseif operator == '<=' then | |
return num1 <= num2 | |
elseif operator == '>' then | |
return num1 > num2 | |
elseif operator == '>=' then | |
return num1 >= num2 | |
elseif operator == '==' then | |
return num1 == num2 | |
elseif operator == '!=' then | |
return num1 ~= num2 | |
end | |
end | |
function eval(ast) | |
if type(ast) == 'number' then | |
return ast | |
elseif ast[1] == 'expr' or ast[1] == 'term' then | |
return eval_expr(ast) | |
elseif ast[1] == 'array' then | |
local new = {} | |
for _, el in ipairs(ast[2]) do | |
table.insert(new, eval(el)) | |
end | |
return new | |
elseif ast[1] == 'ref' then | |
return lookup(ast) | |
elseif ast[1] == 'assign' then | |
return assign(ast[2], eval(ast[3])) | |
elseif ast[1] == 'list' then | |
local last = nil | |
for i = 2, #ast do | |
last = eval(ast[i]) | |
end | |
return last | |
elseif ast[1] == 'if' then | |
if eval_bool(ast[2]) then | |
return eval(ast[3]) | |
end | |
elseif ast[1] == 'while' then | |
while eval_bool(ast[2]) do | |
eval(ast[3]) | |
end | |
elseif ast[1] == 'function' then | |
return { args=ast[2], | |
code=ast[3], | |
scope=Scopes[#Scopes] } | |
elseif ast[1] == 'call' then | |
local fn = eval(ast[2]) | |
local scope = setmetatable({}, {__index=fn.scope}) | |
for i, name in ipairs(fn.args) do | |
scope[name] = eval(ast[3][i]) | |
end | |
table.insert(Scopes, scope) | |
local result = eval(fn.code) | |
table.remove(Scopes) | |
return result | |
end | |
end | |
function assign(ref, value) | |
local current = Scopes[#Scopes] | |
for i = 2, #ref do | |
local next_index = ref[i] | |
if type(next_index) == 'table' then | |
next_index = eval(next_index) | |
end | |
if i == #ref then -- last one, set the value | |
-- Special case, assign to something in an outer scope | |
while current[next_index] and not rawget(current, next_index) do | |
-- Walk the scope chain back until we see it | |
current = getmetatable(current).__index | |
end | |
current[next_index] = value | |
return value | |
else -- not the last, keep following the chain | |
current = current[next_index] | |
end | |
end | |
end | |
function lookup(ref) | |
local current = Scopes[#Scopes] | |
for i = 2, #ref do | |
local next_index = ref[i] | |
if type(next_index) == 'table' then | |
next_index = eval(next_index) | |
end | |
current = current[next_index] | |
end | |
return current | |
end | |
spc = S(" \t\n")^0 | |
digit = R('09') | |
number = C( (P("-") + digit) * | |
digit^0 * | |
( P('.') * digit^0 )^-1 ) / tonumber * spc | |
lparen = "(" * spc | |
rparen = ")" * spc | |
lbrack = "[" * spc | |
rbrack = "]" * spc | |
lcurly = "{" * spc | |
rcurly = "}" * spc | |
comma = "," * spc | |
expr_op = C( S('+-') ) * spc | |
term_op = C( S('*/') ) * spc | |
letter = R('AZ','az') | |
name = C( letter * (digit+letter+"_")^0 ) * spc | |
keywords = (P("if") + P("while") + P("function")) * spc | |
name = name - keywords | |
boolean = C( S("<>") + "<=" + ">=" + "!=" + "==" ) * spc | |
stmt = spc * P{ | |
"LIST"; | |
LIST = | |
V("STMT") + | |
Ct( Cc("list") * | |
lcurly * | |
(V("STMT") * P(";")^0 * spc)^0 * | |
rcurly ), | |
STMT = | |
Ct( Cc("assign") * V("REF") * "=" * spc * V("VAL") ) + | |
V("IF") + | |
V("WHILE") + | |
V("CALL") + | |
V("FUNCTION") + | |
V("EXPR"), | |
EXPR = Ct( Cc("expr") * V("TERM") * ( expr_op * V("TERM") )^0 ), | |
TERM = Ct( Cc("term") * V("FACT") * ( term_op * V("FACT") )^0 ), | |
REF = Ct( Cc("ref") * name * (lbrack * V("EXPR") * rbrack)^0 ), | |
FACT = | |
number + | |
lparen * V("EXPR") * rparen + | |
V("REF"), | |
ARRAY = Ct( Cc("array") * lbrack * V("VAL_LIST") * rbrack ), | |
VAL_LIST = Ct( (V("VAL") * comma^-1)^0 ), | |
VAL = V("CALL") + V("ARRAY") + V("FUNCTION") + V("EXPR"), | |
BOOL = Ct( Cc("bool") * V("EXPR") * boolean * V("EXPR") ), | |
IF = Ct( C("if") * spc * lparen * V("BOOL") * rparen * V("LIST") ), | |
WHILE = Ct( C("while") * spc * lparen * V("BOOL") * rparen * V("LIST") ), | |
CALL = Ct( Cc("call") * V("REF") * spc * lparen * V("VAL_LIST") * rparen ), | |
FUNCTION = Ct( C("function") * spc * lparen * V("NAME_LIST") * rparen * V("LIST") ), | |
NAME_LIST = Ct( (name * comma^0)^0 ) | |
} | |
function test(stmt) | |
local Global = Scopes[1] | |
stmt = stmt / eval | |
assert(stmt:match(" 1 + 2 ") == 3) | |
assert(stmt:match("1+2+3+4+5") == 15) | |
assert(stmt:match("2*3*4 + 5*6*7") == 234) | |
assert(stmt:match(" 1 * 2 + 3") == 5) | |
assert(stmt:match("( 2 +2) *6") == 24) | |
stmt:match("a=3"); assert(Global.a == 3) | |
assert(stmt:match("a") == 3) | |
assert(stmt:match("a * 5") == 15); Global.a=nil | |
stmt:match("a = [ 4, 5, 6 ]"); | |
assert(Global.a[1] == 4) | |
assert(Global.a[2] == 5) | |
assert(Global.a[3] == 6) | |
Global.a=nil | |
stmt:match("b = [ ]"); | |
assert(Global.b[1] == nil) | |
Global.b=nil | |
stmt:match("c = [[1,2], [3,4]]") | |
assert(Global.c[1][1] == 1) | |
assert(Global.c[1][2] == 2) | |
assert(Global.c[2][1] == 3) | |
assert(Global.c[2][2] == 4) | |
assert(stmt:match("c[4/2][1]") == 3) | |
stmt:match("c[3] = 5") | |
assert(Global.c[3] == 5) | |
Global.c=nil | |
stmt:match("if(1 < 0) b = 5"); assert(Global.b ~= 5) | |
Global.n=0; Global.x=1 | |
stmt:match("while(n < 8) { x = x * 2; n = n + 1 }") | |
assert(Global.x == 256) | |
Global.n=nil; Global.x=nil | |
stmt:match("f = function(a) a*2+3") | |
assert(Global.f ~= nil) | |
assert(Global.f.scope == Scopes[1]) | |
stmt:match("res = f(5)") | |
assert(Global.res == 13) | |
assert(Global.a == nil) | |
assert(#Scopes == 1) | |
Scopes[1] = {}; Global = Scopes[1] | |
stmt:match("make_acc = function(){ a = 0; function(n) a = a+n; }") | |
assert(Global.make_acc) | |
stmt:match("acc = make_acc()") | |
assert(stmt:match("acc(1)") == 1) | |
assert(stmt:match("acc(1)") == 2) | |
end | |
function repl(file) | |
file = file or io.input() | |
parser = stmt | |
for line in file:lines() do | |
print(parser:match(line)) | |
end | |
end |
To anybody reading lpeg.Cmt
is a better /f
way to call function.
If you are seriously creating a programming language then use lpeg.Cmt
instead of /
for attaching functions to your parser.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@randrews
Do you know why somebody would use
lpeg.Cmt
?In moonscript it seems to be used :
leafo/moonscript@2f09192#diff-272b1f163aec9b0bd2b9b5dab4a9688dR117
How is it different than normal capture ? In your complete programming language where would somebody use it ?
Thanks !