Skip to content

Instantly share code, notes, and snippets.

@randrews
Created June 21, 2015 20:53
Show Gist options
  • Save randrews/23c68a2a542a20a86fdf to your computer and use it in GitHub Desktop.
Save randrews/23c68a2a542a20a86fdf to your computer and use it in GitHub Desktop.
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
Copy link

sourcevault 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
Copy link

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