Thanks to you all for your suggestions. That is certainly the easiest parser I ever wrote. To parse an expression, I can now do this:
using Main.MyEval
m = Dict( :x => 1.0, :y => 2.0, :z = 3.0)
myeval(Meta.parse("x + y/z", m))
which returns 1.6667. Here’s the implementation:
module MyEval
export myeval
"""
myeval(e::Union{Expr,Symbol,Number}, map::Dict{Symbol,Float64})
Evaluate the result produced by Meta.parse, looking up the values of
user-defined variables in "map". Argument "e" is a Union, because
Meta.parse can produce values of type Expr, Symbol, or Number.
"""
function myeval(e::Union{Expr,Symbol,Number}, map::Dict{Symbol,Float64})
try
return f(e, map)
catch ex
println("Can't parse \"$e\"")
rethrow(ex)
end
end
# Look up symbol and return value, or throw.
function f(s::Symbol, map::Dict{Symbol,Float64})
if haskey(map, s)
return map[s]
else
throw(UndefVarError(s))
end
end
# Numbers are converted to type Float64.
function f(x::Number, map::Dict{Symbol,Float64})
return Float64(x)
end
# To parse an expression, convert the head to a singleton
# type, so that Julia can dispatch on that type.
function f(e::Expr, map::Dict{Symbol,Float64})
return f(Val(e.head), e.args, map)
end
# Call the function named in args[1]
function f(::Val{:call}, args, map::Dict{Symbol,Float64})
return f(Val(args[1]), args[2:end], map)
end
# Addition
function f(::Val{:+}, args, map::Dict{Symbol,Float64})
x = 0.0
for arg ∈ args
x += f(arg, map)
end
return x
end
# Subtraction and negation
function f(::Val{:-}, args, map::Dict{Symbol,Float64})
len = length(args)
if len == 1
return -f(args[1], map)
else
return f(args[1], map) - f(args[2], map)
end
end
# Multiplication
function f(::Val{:*}, args, map::Dict{Symbol,Float64})
x = 1.0
for arg ∈ args
x *= f(arg, map)
end
return x
end
# Division
function f(::Val{:/}, args, map::Dict{Symbol,Float64})
return f(args[1], map) / f(args[2], map)
end
end # module MyEval