Skip to content

Instantly share code, notes, and snippets.

@hmenke
Last active February 29, 2020 21:10
Show Gist options
  • Save hmenke/0a4a1174eaf7250aea39093d4785fcfb to your computer and use it in GitHub Desktop.
Save hmenke/0a4a1174eaf7250aea39093d4785fcfb to your computer and use it in GitHub Desktop.
Simple mathematical expression parser and evaluator in Lua with LPEG
local lpeg = require"lpeg"
local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
local white = S(" \t") ^ 0
local digit = R("09")
local dot = P(".")
local eE = S("eE")
local sign = S("+-")^-1
local mantissa = digit^1 * dot * digit^0 + dot * digit^1 + digit^1
local exponent = (eE * sign * digit^1)^-1
local real = white * mantissa * exponent * white / tonumber
local power = white * C(P("^")) * white
local muldiv = white * C(S("/*%")) * white
local addsub = white * C(S("+-")) * white
local open = white * P("(") * white
local close = white * P(")") * white
local comma = white * P(",") * white
local variable = white * C(R("az","AZ") * (R("az","AZ","09") + P("_"))^0) * white
-- Lookup for functions
local ufunc = {
["+"] = function(x) return x end, ["-"] = function(x) return -x end,
sgn = function(x) return (0<x and 1 or 0) - (x<0 and 1 or 0) end,
isinf = function(x) return (x == math.huge or x == -math.huge) and 1 or 0 end,
isnan = function(x) return x ~= x and 1 or 0 end,
abs = math.abs, acos = math.acos, asin = math.asin, atan = math.atan,
ceil = math.ceil, cos = math.cos, cosh = math.cosh, deg = math.deg,
exp = math.exp, floor = math.floor, log = math.log, log10 = math.log10,
rad = math.rad, sin = math.sin, sinh = math.sinh, sqrt = math.sqrt,
tan = math.tan, tanh = math.tanh
}
local bfunc = {
["+"] = function(a,b) return a + b end,
["-"] = function(a,b) return a - b end,
["*"] = function(a,b) return a * b end,
["/"] = function(a,b) return a / b end,
["%"] = function(a,b) return a % b end,
["^"] = function(a,b) return a ^ b end,
atan2 = math.atan2, max = math.max, min = math.min, pow = math.pow
}
-- Lookup for contants
local const = {
e = math.exp(1), inf = math.huge, nan = 0/0, phi = (1+math.sqrt(5))/2, pi = math.pi
}
-- Generate rules from lookup tables
local function generate(lookup)
local rule
for k,_ in pairs(lookup) do
rule = rule and rule + C(P(k)) or C(P(k))
end
return rule
end
local ufunc_rule = generate(ufunc)
local bfunc_rule = generate(bfunc)
local const_rule = generate(const)
-- Evaluate AST recursively
local function eval(t,s)
if type(t) == "table" then
if t.tag == "u" then
return ufunc[t.op](eval(t.right,s))
elseif t.tag == "b" then
return bfunc[t.op](eval(t.left,s),eval(t.right,s))
else
error("Cannot happen")
end
elseif type(t) == "number" then
return t
elseif const[t] then
return const[t]
elseif s and s[t] then
return s[t]
else
error(string.format("Unknown operand '%s'",t))
end
end
-- Insert binary node into AST
local function binary(rule)
local function recurse(left,op,right,...)
if op then
return recurse({ tag = "b", op = op, left = left, right = right },...)
else
return left
end
end
return rule / recurse
end
-- Insert unary node into AST
local function unary(rule)
return rule / function(op,right)
return { tag = "u", op = op, right = right }
end
end
local grammar = P({
"input",
input = V("expression") * -1,
expression = binary( V("term") * ( addsub * V("term") )^0 ),
term = binary( V("factor") * ( muldiv * V("factor"))^0 ),
factor = binary( V("primary") * ( power * V("factor"))^0 ),
primary = real
+ open * V("expression") * close
+ unary( addsub * V("primary") )
+ unary( ufunc_rule * open * V("expression") * close )
+ binary( bfunc_rule * open * V("expression") * comma * V("expression") * close /
function(op,left,right) return left,op,right end ) -- switch around operands
+ const_rule
+ variable
})
local matheval = {
parse = function(expr,symbols)
return eval(assert(grammar:match(expr)),symbols)
end
}
print(matheval.parse("nan"), 0/0)
print(matheval.parse("inf"), math.huge)
print(matheval.parse("isinf(1e1000)"), ufunc.isinf(1e1000))
print(matheval.parse("isnan(0/0)"), ufunc.isnan(0/0))
print(matheval.parse("sgn(-5)"), ufunc.sgn(-5))
print(matheval.parse("6%4"), 6%4)
print(matheval.parse("3*(5-7^8)"), 3*(5-7^8))
print(matheval.parse("5 + 2*3 - 1 + 7 * 8"), 5 + 2*3 - 1 + 7 * 8)
print(matheval.parse("67 + 2 * 3 - 67 + 2/1 - 7"), 67 + 2 * 3 - 67 + 2/1 - 7)
print(matheval.parse("(5*7/5) + (23) - 5 * (98-4)/(6*7-42)"), (5*7/5) + (23) - 5 * (98-4)/(6*7-42))
print(matheval.parse("(2) + (17*2-30) * (5)+2 - (8/2)*4"), (2) + (17*2-30) * (5)+2 - (8/2)*4)
print(matheval.parse("2*3*4/8 - 5/2*4 + 6 + 0/3"), 2*3*4/8 - 5/2*4 + 6 + 0/3)
print(matheval.parse("2*3 - 4*5 + 6/3"), 2*3 - 4*5 + 6/3)
print(matheval.parse("tan(.7438E+0)"), math.tan(.7438E+0))
print(matheval.parse("sin(pi)"), math.sin(math.pi))
print(matheval.parse("pow(2,3)"), 2^3)
print(matheval.parse("min(2,3)"), math.min(2,3))
print(matheval.parse("max(2,3)"), math.max(2,3))
print(matheval.parse("y0_abc_32_def + 2",{ y0_abc_32_def = 1 }), 3)
local lpeg = require"lpeg"
local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
local white = S(" \t") ^ 0
local integer = white * C(R("09") ^ 1) * white / tonumber
local power = white * C(P("^")) * white
local muldiv = white * C(S("/*%")) * white
local addsub = white * C(S("+-")) * white
local open = white * P("(") * white
local close = white * P(")") * white
local bfunc = {
["+"] = function(a,b) return a + b end,
["-"] = function(a,b) return a - b end,
["*"] = function(a,b) return a * b end,
["/"] = function(a,b) return math.floor(a / b) end,
["%"] = function(a,b) return a % b end,
["^"] = function(a,b) return a ^ b end
}
-- Insert binary node into AST
local function binary(rule)
local function recurse(left,op,right,...)
if op then
return recurse(bfunc[op](left,right),...)
else
return left
end
end
return rule / recurse
end
-- Insert unary node into AST
local function unary(rule)
return rule / function(op,right)
return bfunc[op](0,right)
end
end
local grammar = P({
"input",
input = V("expression") * -1,
expression = binary( V("term") * ( addsub * V("term") )^0 ),
term = binary( V("factor") * ( muldiv * V("factor"))^0 ),
factor = binary( V("primary") * ( power * V("factor"))^0 ),
primary = integer
+ open * V("expression") * close
+ unary( addsub * V("primary") )
})
print(grammar:match("6%4"), 6%4)
print(grammar:match("3*(5-7^8)"), 3*(5-7^8))
print(grammar:match("5 + 2*3 - 1 + 7 * 8"), 5 + 2*3 - 1 + 7 * 8)
print(grammar:match("67 + 2 * 3 - 67 + 2/1 - 7"), 67 + 2 * 3 - 67 + 2/1 - 7)
print(grammar:match("(5*7/5) + (23) - 5 * (98-4)/(6*7-42)"), (5*7/5) + (23) - 5 * (98-4)/(6*7-42))
print(grammar:match("(2) + (17*2-30) * (5)+2 - (8/2)*4"), (2) + (17*2-30) * (5)+2 - (8/2)*4)
print(grammar:match("2*3*4/8 - 5/2*4 + 6 + 0/3"), math.floor(2*3*4/8) - math.floor(5/2)*4 + 6 + 0/3)
print(grammar:match("2*3 - 4*5 + 6/3"), 2*3 - 4*5 + 6/3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment