require 'parslet' require 'pp' require 'pry' #ParseDose = Struct.new("ParseDose", :qty, :unit) class MiniP < Parslet::Parser # Single character rules rule(:lparen) { str('(') >> space? } rule(:rparen) { str(')') >> space? } rule(:comma) { str(',') >> space? } rule(:space) { match('\s').repeat(1) } rule(:space?) { space.maybe } # Things rule(:integer) { match('[0-9]').repeat(1).as(:int) >> space? } rule(:identifier) { match['a-z'].repeat(1) } rule(:operator) { match('[+]') >> space? } # Grammar parts rule(:sum) { integer.as(:left) >> operator.as(:op) >> expression.as(:right) } rule(:arglist) { expression >> (comma >> expression).repeat } rule(:funcall) { identifier.as(:funcall) >> lparen >> arglist.as(:arglist) >> rparen } # dose # rule(:qty) { integer } rule(:dose) { integer.as(:qty) >> str('mg').as(:unit) } rule(:expression) { dose } #rule(:expression) { dose | funcall | sum | integer } root :expression end class IntLit < Struct.new(:int) def eval; int.to_i; end end class QtyLit < Struct.new(:qty) def eval; qty.to_i; end end class Addition < Struct.new(:left, :right) def eval; left.eval + right.eval; end end class FunCall < Struct.new(:name, :args); def eval p args.map { |s| s.eval } end end class ParseDose attr_reader :qty, :unit def initialize(qty, unit) @qty = qty.eval @unit = unit.to_s end def eval self end end class MiniT < Parslet::Transform rule(:int => simple(:int)) { IntLit.new(int) } rule( :left => simple(:left), :right => simple(:right), :op => '+') { Addition.new(left, right) } rule( :funcall => 'puts', :arglist => subtree(:arglist)) { FunCall.new('puts', arglist) } rule( :qty => simple(:qty), :unit => simple(:unit)) { ParseDose.new(qty, unit) } end require 'rspec' require 'parslet/rig/rspec' describe MiniP do context "dose" do it "should consume 2 mg" do string = '2 mg' parser = MiniP.new transf = MiniT.new parser.should parse(string) res = parse(string) ast = transf.apply(parser.parse(string)) res2 = ast.eval res2.class.should eq ParseDose res2.qty.should eq 2 res2.unit.should eq 'mg' end end end RSpec::Core::Runner.run([])