Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Toy calculator in Lua, version 4
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
@sourcevault

This comment has been minimized.

Copy link

commented Sep 13, 2018

@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 !

@sourcevault

This comment has been minimized.

Copy link

commented Apr 4, 2019

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
You can’t perform that action at this time.