Permalink
Browse files

User-proof parser

  • Loading branch information...
1 parent 5771dd2 commit d40e5d014db8a6531085151845c4edd8a5e45640 @txus committed Jan 8, 2011
Showing with 117 additions and 6 deletions.
  1. +15 −0 Guardfile
  2. +3 −3 lib/rpncalc/calculator.rb
  3. +36 −0 lib/rpncalc/parser.rb
  4. +3 −3 spec/rpncalc/calculator_spec.rb
  5. +60 −0 spec/rpncalc/parser_spec.rb
View
@@ -0,0 +1,15 @@
+# A sample Guardfile
+# More info at http://github.com/guard/guard#readme
+
+guard 'rspec', :version => 2 do
+ watch('^spec/(.*)_spec.rb')
+ watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch('^spec/spec_helper.rb') { "spec" }
+
+ # Rails example
+ watch('^app/(.*)\.rb') { |m| "spec/#{m[1]}_spec.rb" }
+ # watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch('^config/routes.rb') { "spec/routing" }
+ watch('^app/controllers/application_controller.rb') { "spec/controllers" }
+ watch('^spec/factories.rb') { "spec/models" }
+end
@@ -2,10 +2,10 @@ module Rpncalc
class Calculator
attr_reader :parser, :stack
def initialize options = {}
- arity, separator = options[:arity] || 2,
- options[:separator] || ' '
+ arity, delimiter = options[:arity] || 2,
+ options[:delimiter] || ' '
@stack = Stack.new(arity)
- @parser = Parser.new(separator)
+ @parser = Parser.new(delimiter)
end
def solve string
stack.handle parser.parse(string)
View
@@ -1,4 +1,40 @@
module Rpncalc
class Parser
+ class MalformedStringError < StandardError; end;
+ class InvalidDelimiterError < StandardError; end;
+
+ TOKENS = %w{. + - * / ^ ln log10}
+
+ attr_reader :delimiter
+
+ def initialize delimiter
+ @delimiter = validate(delimiter)
+ end
+
+ def parse string
+ string.gsub(/#{delimiter}+/, delimiter)
+ .split(delimiter).map(&:strip).map do |element|
+ if element.to_i.zero? && element != '0'
+ if TOKENS.include?(element)
+ element.to_sym
+ else
+ raise MalformedStringError.new("Offending token: #{element}")
+ end
+ else
+ element =~ /\./ ? element.to_f
+ : element.to_i
+ end
+ end
+ end
+
+ private
+
+ def validate delimiter
+ if TOKENS.include?(delimiter.strip) ||
+ delimiter =~ /[0-9]+/
+ raise InvalidDelimiterError.new
+ end
+ delimiter
+ end
end
end
@@ -8,7 +8,7 @@ module Rpncalc
Stack.should_receive(:new).with(2)
Calculator.new
end
- it 'sets default separator' do
+ it 'sets default delimiter' do
Parser.should_receive(:new).with(' ')
Calculator.new
end
@@ -17,9 +17,9 @@ module Rpncalc
Stack.should_receive(:new).with(3)
Calculator.new :arity => 3
end
- it 'accepts a custom separator' do
+ it 'accepts a custom delimiter' do
Parser.should_receive(:new).with(',')
- Calculator.new :separator => ','
+ Calculator.new :delimiter => ','
end
it 'creates an accessible stack' do
calculator = Calculator.new
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+module Rpncalc
+ describe Parser do
+ describe "#initialize" do
+ it 'sets the delimiter' do
+ parser = Parser.new ' '
+ parser.delimiter.should == ' '
+ end
+ describe "makes the parser raise an InvalidDelimiterError" do
+ %w{. + - * / ^ ln log10 19 425 0}.each do |invalid_delimiter|
+ it "when using #{invalid_delimiter} as a delimiter" do
+ expect {
+ Parser.new invalid_delimiter
+ }.to raise_error(Parser::InvalidDelimiterError)
+ end
+ end
+ end
+ end
+
+ describe "#parse" do
+ context "with a space delimiter" do
+ subject { Parser.new ' ' }
+
+ it 'returns a tokenized array' do
+ subject.parse("1 4 5 + -").should == [1, 4, 5, :+, :-]
+ subject.parse("1 2 3 4 5 6 7 8 + - * / ^ ln log10").should == [1, 2, 3, 4, 5, 6, 7, 8, :+, :-, :*, :/, :^, :ln, :log10]
+ end
+ describe "edge cases" do
+ it 'strips any extra spaces' do
+ subject.parse("1 4 5 + -").should == [1, 4, 5, :+, :-]
+ end
+ end
+ end
+ context "with a comma delimiter" do
+ subject { Parser.new ',' }
+
+ it 'returns a tokenized array' do
+ subject.parse("1,4,5.3,+,-").should == [1, 4, 5.3, :+, :-]
+ subject.parse("1,2,3,4,5,6,7,8,+,-,*,/,^,ln,log10").should == [1,2,3,4,5,6,7,8,:+,:-,:*,:/,:^,:ln,:log10]
+ end
+ describe "edge cases" do
+ it 'strips any extra spaces and delimiter' do
+ subject.parse("1,, 4 , 5.3 ,,,+, -").should == [1, 4, 5.3, :+, :-]
+ end
+ end
+ end
+ context "with inconsistent or malformed strings" do
+ subject { Parser.new ',' }
+
+ it 'raises a MalformedStringError' do
+ expect {
+ subject.parse "1,4.5,&+,-"
+ }.to raise_error(Parser::MalformedStringError, "Offending token: &+")
+ end
+ end
+ end
+
+ end
+end

0 comments on commit d40e5d0

Please sign in to comment.