Skip to content

Instantly share code, notes, and snippets.

@seanchas116
Last active December 21, 2015 13:58
Show Gist options
  • Save seanchas116/6315859 to your computer and use it in GitHub Desktop.
Save seanchas116/6315859 to your computer and use it in GitHub Desktop.
parse and calculate arithmetic expressions with Parslet
require 'parslet'
require 'pp'
class Parser < Parslet::Parser
rule(:lparen) { str('(') >> space? }
rule(:rparen) { str(')') >> space? }
rule(:space) { match('\s').repeat(1) }
rule(:space?) { space.maybe }
rule(:numeral) { match('[0-9]').repeat(1) }
rule(:number) { ( numeral >> ( str('.') >> numeral ).maybe ).as(:number) >> space? }
rule(:factor) { number | lparen >> expression.as(:expression) >> rparen }
rule(:expression) { factor >> ( match['+-/*%^'].as(:operator) >> space? >> factor).repeat(0) }
root :expression
end
class NumberLiteral < Struct.new(:value)
def eval
value.to_f
end
end
class BinaryOperation < Struct.new(:left, :operator, :right)
def eval
case operator
when '+'
left.eval + right.eval
when '-'
left.eval - right.eval
when '*'
left.eval * right.eval
when '/'
left.eval / right.eval
when '%'
left.eval % right.eval
when '^'
left.eval ** right.eval
end
end
end
OP_PRECEDENCE = {
'+' => 10,
'-' => 10,
'*' => 20,
'/' => 20,
'%' => 20,
'^' => 30
}
# return: root AST, remaining expression
def construct_ast_recursive(expression, precedence_limit)
if expression.length == 0
return nil, nil
end
first = expression[0]
case
when first.has_key?(:number)
lhs = NumberLiteral.new(first[:number])
when first.has_key?(:expression)
lhs = construct_ast(first[:expression])
else
raise "no expression or integer literal in expression item"
end
expression = expression[1..-1]
if expression.length == 0
expression = nil
end
while expression
op = expression[0][:operator].to_s
precedence = OP_PRECEDENCE[op]
if precedence <= precedence_limit
return lhs, expression
end
rhs, expression = construct_ast_recursive(expression, precedence)
lhs = BinaryOperation.new(lhs, op, rhs)
end
return lhs, nil
end
def construct_ast(expression)
construct_ast_recursive(expression, -1)[0]
end
def parse(str)
parsed = Parser.new.parse(str)
pp parsed
ast = construct_ast(parsed)
pp ast
pp ast.eval
rescue Parslet::ParseFailed => failure
puts failure.cause.ascii_tree
end
parse("3.4 * 2 + 5 * 3")
parse("(1 + 2) * 3.5 + 4 * (2 / 4) ^ 2")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment