Permalink
Browse files

Small bugfixes to infix parser

  • Loading branch information...
1 parent 9492f17 commit e3c600adc28e36f478c4cedb65480134ab5457d8 @kschiess committed Jun 20, 2013
@@ -1 +1,5 @@
-{:l=>"1"@0, :o=>"+"@1, :r=>{:l=>{:l=>"2"@2, :o=>"*"@3, :r=>"2"@4}, :o=>"+"@5, :r=>"3"@6}}
+a = 1
+b = 2
+c = 3 * 25
+d = 100 + 3*4
+{:a=>1, :b=>2, :c=>75, :d=>112}
View
@@ -3,14 +3,16 @@
$:.unshift File.dirname(__FILE__) + "/../lib"
+require 'pp'
require 'rspec'
require 'parslet'
require 'parslet/rig/rspec'
+require 'parslet/convenience'
class InfixExpressionParser < Parslet::Parser
- root :expression
+ root :variable_assignment_list
- rule(:space) { match['\s'] }
+ rule(:space) { match[' '] }
def cts atom
atom >> space.repeat
@@ -19,15 +21,51 @@ def infix *args
Infix.new(*args)
end
- rule(:mul_op) { match['*/'] }
- rule(:add_op) { match['+-'] }
+ # This is the heart of the infix expression parser: real simple definitions
+ # for all the pieces we need.
+ rule(:mul_op) { cts match['*/'] }
+ rule(:add_op) { cts match['+-'] }
rule(:digit) { match['0-9'] }
- rule(:integer) { cts digit.repeat(1) }
+ rule(:integer) { cts digit.repeat(1).as(:int) }
rule(:expression) { infix_expression(integer,
[mul_op, 2, :left],
[add_op, 1, :right]) }
+
+ # And now adding variable assignments to that, just to a) demonstrate this
+ # embedded in a bigger parser, and b) make the example interesting.
+ rule(:variable_assignment_list) {
+ variable_assignment.repeat(1) }
+ rule(:variable_assignment) {
+ identifier.as(:ident) >> equal_sign >> expression.as(:exp) >> eol }
+ rule(:identifier) {
+ cts (match['a-z'] >> match['a-zA-Z0-9'].repeat) }
+ rule(:equal_sign) {
+ cts str('=') }
+ rule(:eol) {
+ cts(str("\n")) | any.absent? }
+end
+
+class InfixInterpreter < Parslet::Transform
+ rule(int: simple(:int)) { Integer(int) }
+ rule(ident: simple(:ident), exp: simple(:result)) { |d|
+ d[:doc][d[:ident].to_s.strip.to_sym] = d[:result] }
+
+ rule(l: simple(:l), o: /^\*/, r: simple(:r)) { l * r }
+ rule(l: simple(:l), o: /^\+/, r: simple(:r)) { l + r }
end
-expression = (ARGV.empty? ? '1+2*2+3' : ARGV.join)
-p InfixExpressionParser.new.parse(expression)
+input = <<ASSIGNMENTS
+a = 1
+b = 2
+c = 3 * 25
+d = 100 + 3*4
+ASSIGNMENTS
+
+puts input
+
+int_tree = InfixExpressionParser.new.parse_with_debug(input)
+bindings = {}
+result = InfixInterpreter.new.apply(int_tree, doc: bindings)
+
+pp bindings
@@ -61,10 +61,20 @@ def precedence_climb(source, context, consume_all, current_prec=1, needs_element
result << flatten(value, true)
# Loop until we fail on operator matching or until input runs out.
- while source.chars_left > 0
+ loop do
op_pos = source.pos
op_match, prec, assoc = match_operation(source, context, false)
+ # If no operator could be matched here, one of several cases
+ # applies:
+ #
+ # - end of file
+ # - end of expression
+ # - syntax error
+ #
+ # We abort matching the expression here.
+ break unless op_match
+
if prec >= current_prec
next_prec = (assoc == :left) ? prec+1 : prec
@@ -88,13 +98,13 @@ def match_operation(source, context, consume_all)
errors = []
@operations.each do |op_atom, prec, assoc|
success, value = op_atom.apply(source, context, consume_all)
- return value, prec, assoc if success
+ return flatten(value, true), prec, assoc if success
# assert: this was in fact an error, accumulate
errors << value
end
- abort context.err(self, source, "Expected an operator.", errors)
+ return nil
end
def abort(error)
@@ -55,7 +55,7 @@ def element_match(tree, exp, bindings)
return element_match_ary_single(tree, exp, bindings)
else
# If elements match exactly, then that is good enough in all cases
- return true if tree == exp
+ return true if exp === tree
# If exp is a bind variable: Check if the binding matches
if exp.respond_to?(:can_bind?) && exp.can_bind?(tree)
@@ -11,8 +11,8 @@ def infix *args
Infix.new(*args)
end
- rule(:mul_op) { match['*/'] }
- rule(:add_op) { match['+-'] }
+ rule(:mul_op) { match['*/'] >> str(' ').maybe }
+ rule(:add_op) { match['+-'] >> str(' ').maybe }
rule(:digit) { match['0-9'] }
rule(:integer) { cts digit.repeat(1) }
@@ -41,6 +41,9 @@ def infix *args
it "parses simple multiplication" do
m.should parse('1*2').as(l: '1', o: '*', r: '2')
end
+ it "parses simple multiplication with spaces" do
+ m.should parse('1 * 2').as(l: '1 ', o: '* ', r: '2')
+ end
it "parses division" do
m.should parse('1/2')
end
@@ -100,9 +103,7 @@ def infix *args
mo.parse('1%') }
cause.ascii_tree.to_s.should == <<-ERROR
-Expected an operator. at line 1 char 2.
-|- Failed to match [*/] at line 1 char 2.
-`- Failed to match [+-] at line 1 char 2.
+Don't know what to do with "%" at line 1 char 2.
ERROR
end
end

0 comments on commit e3c600a

Please sign in to comment.