rpn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create use ruby-1.9.2@rpn
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rpncalc.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rpn (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ configuration (1.2.0)
10
+ diff-lcs (1.1.2)
11
+ guard (0.2.2)
12
+ open_gem (~> 1.4.2)
13
+ thor (~> 0.14.3)
14
+ guard-rspec (0.1.9)
15
+ guard (>= 0.2.2)
16
+ launchy (0.3.7)
17
+ configuration (>= 0.0.5)
18
+ rake (>= 0.8.1)
19
+ open_gem (1.4.2)
20
+ launchy (~> 0.3.5)
21
+ rake (0.8.7)
22
+ rspec (2.4.0)
23
+ rspec-core (~> 2.4.0)
24
+ rspec-expectations (~> 2.4.0)
25
+ rspec-mocks (~> 2.4.0)
26
+ rspec-core (2.4.0)
27
+ rspec-expectations (2.4.0)
28
+ diff-lcs (~> 1.1.2)
29
+ rspec-mocks (2.4.0)
30
+ thor (0.14.6)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ bundler (~> 1.0.7)
37
+ guard
38
+ guard-rspec
39
+ rpn!
40
+ rspec (~> 2.4.0)
data/Guardfile ADDED
@@ -0,0 +1,15 @@
1
+ # A sample Guardfile
2
+ # More info at http://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch('^spec/(.*)_spec.rb')
6
+ watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('^spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch('^app/(.*)\.rb') { |m| "spec/#{m[1]}_spec.rb" }
11
+ # watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
12
+ watch('^config/routes.rb') { "spec/routing" }
13
+ watch('^app/controllers/application_controller.rb') { "spec/controllers" }
14
+ watch('^spec/factories.rb') { "spec/models" }
15
+ end
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ task :default => :spec
data/Readme.md ADDED
@@ -0,0 +1,57 @@
1
+ #Reverse Polish Notation calculator
2
+
3
+ A simple implementation of a RPN (also known as _postfix_ notation) calculator.
4
+
5
+ In a nutshell, this notation consists of operands followed by their operators.
6
+ As opposed to express an operation like this...
7
+
8
+ (3 + 4) * 7 - 1
9
+
10
+ ...in reverse Polish notation it would look like this:
11
+
12
+ 7 3 4 + * 1 -
13
+
14
+ It basically works like a stack in which you push operands until you find an
15
+ operator; given an arity _n_, you evaluate this operator with _n_ numbers from
16
+ the stack, and substitute them for their result. Check out the [Wikipedia
17
+ article](http://en.wikipedia.org/wiki/Reverse_Polish_notation) to learn more about this notation.
18
+
19
+ ##Install
20
+
21
+ gem install rpn
22
+
23
+ ##Usage
24
+
25
+ Just require it in your Gemfile:
26
+
27
+ gem 'rpn'
28
+
29
+ Note: If you are not using Bundler, you should `require 'rpn'` manually.
30
+
31
+ Now you have to initialize a calculator. You can provide a custom _n_ arity
32
+ (the number of operands that will get evaluated by each operator). Default
33
+ arity is 2. You can also provide the delimiter the parser will use (will be ' '
34
+ by default).
35
+
36
+ my_rpn = RPN::Calculator.new # => will use the defaults
37
+ my_rpn.solve "7 3 4 + * 1 -"
38
+ # => 48
39
+
40
+ For now, it supports the following operators:
41
+
42
+ + - * / ^
43
+
44
+ ##Contribute!
45
+
46
+ * Fork the project.
47
+ * Make your feature addition or bug fix.
48
+ * Add specs for it. This is important so I don't break it in a future
49
+ version unintentionally.
50
+ * Commit, do not mess with rakefile, version, or history.
51
+ If you want to have your own version, that is fine but bump version
52
+ in a commit by itself I can ignore when I pull.
53
+ * Send me a pull request. Bonus points for topic branches.
54
+
55
+ ## Copyright
56
+
57
+ Copyright (c) 2011 Josep M. Bach. See LICENSE for details.
@@ -0,0 +1,14 @@
1
+ module RPN
2
+ class Calculator
3
+ attr_reader :parser, :stack
4
+ def initialize options = {}
5
+ arity, delimiter = options[:arity] || 2,
6
+ options[:delimiter] || ' '
7
+ @stack = Stack.new(arity)
8
+ @parser = Parser.new(delimiter)
9
+ end
10
+ def solve string
11
+ stack.solve parser.parse(string)
12
+ end
13
+ end
14
+ end
data/lib/rpn/parser.rb ADDED
@@ -0,0 +1,41 @@
1
+ module RPN
2
+ class Parser
3
+ class MalformedStringError < StandardError; end;
4
+ class InvalidDelimiterError < StandardError; end;
5
+
6
+ TOKENS = %w{. + - * / ^}
7
+ ALIASES = {'^' => '**'}
8
+
9
+ attr_reader :delimiter
10
+
11
+ def initialize delimiter
12
+ @delimiter = validate(delimiter)
13
+ end
14
+
15
+ def parse string
16
+ string.gsub(/#{delimiter}+/, delimiter)\
17
+ .split(delimiter).map(&:strip).map do |element|
18
+ if element.to_i.zero? && element != '0'
19
+ if TOKENS.include?(element)
20
+ (ALIASES[element] || element).to_sym
21
+ else
22
+ raise MalformedStringError.new("Offending token: #{element}")
23
+ end
24
+ else
25
+ element =~ /\./ ? element.to_f\
26
+ : element.to_i
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def validate delimiter
34
+ if TOKENS.include?(delimiter.strip) ||
35
+ delimiter =~ /[0-9]+/
36
+ raise InvalidDelimiterError.new
37
+ end
38
+ delimiter
39
+ end
40
+ end
41
+ end
data/lib/rpn/stack.rb ADDED
@@ -0,0 +1,49 @@
1
+ module RPN
2
+ class Stack
3
+ class InsufficientValuesAvailable < StandardError; end;
4
+ class UnsolvableExpressionError < StandardError; end;
5
+
6
+ attr_reader :arity, :elements
7
+
8
+ def initialize arity
9
+ @arity = arity
10
+ @elements = []
11
+ end
12
+
13
+ def solve tokens
14
+ clear
15
+ tokens.each do |token|
16
+ if Numeric === token
17
+ push token
18
+ next
19
+ end
20
+ raise InsufficientValuesAvailable\
21
+ .new("Cannot apply #{token} to less than #{arity} values!") if size < arity
22
+ result = pop(arity).inject do |acc, e|
23
+ acc.send(token, e)
24
+ end
25
+ push result
26
+ end
27
+ raise UnsolvableExpressionError\
28
+ .new("The final stack contained more than one value: #{elements.inspect}") if size > 1
29
+ elements.first
30
+ end
31
+
32
+ def push token
33
+ elements.push token
34
+ end
35
+
36
+ def pop amount
37
+ elements.pop amount
38
+ end
39
+
40
+ def size
41
+ elements.size
42
+ end
43
+
44
+ def clear
45
+ elements.clear
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module RPN
2
+ VERSION = "0.0.1"
3
+ end
data/lib/rpn.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'rpn/stack.rb'
2
+ require 'rpn/parser.rb'
3
+ require 'rpn/calculator.rb'
4
+
5
+ module RPN
6
+ end
data/rpn.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rpn/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rpn"
7
+ s.version = RPN::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Josep M. Bach"]
10
+ s.email = ["josep.m.bach@gmail.com"]
11
+ s.homepage = "http://github.com/txus/rpn"
12
+ s.summary = %q{A simple Reverse Polish Notation calculator in Ruby}
13
+ s.description = %q{A simple Reverse Polish Notation calculator in Ruby}
14
+
15
+ s.rubyforge_project = "rpn"
16
+
17
+ s.add_development_dependency 'bundler', '~> 1.0.7'
18
+ s.add_development_dependency 'rspec', '~> 2.4.0'
19
+ s.add_development_dependency 'guard'
20
+ s.add_development_dependency 'guard-rspec'
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ module RPN
4
+ describe Calculator do
5
+
6
+ it 'works like a charm' do
7
+ subject.solve("2 3 +").should == 5
8
+ subject.solve("90 3 -").should == 87
9
+ subject.solve("10 4 3 + 2 * -").should == -4
10
+ subject.solve("10 4 3 + 2 * - 2 /").should == -2
11
+ subject.solve("90 34 12 33 55 66 + * - + -").should == 4037
12
+ subject.solve("2 3 ^").should == 8
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module RPN
4
+ describe Calculator do
5
+ describe "#initialize" do
6
+ context "with no arguments" do
7
+ it 'sets default arity' do
8
+ Stack.should_receive(:new).with(2)
9
+ Calculator.new
10
+ end
11
+ it 'sets default delimiter' do
12
+ Parser.should_receive(:new).with(' ')
13
+ Calculator.new
14
+ end
15
+ end
16
+ it 'accepts a custom arity' do
17
+ Stack.should_receive(:new).with(3)
18
+ Calculator.new :arity => 3
19
+ end
20
+ it 'accepts a custom delimiter' do
21
+ Parser.should_receive(:new).with(',')
22
+ Calculator.new :delimiter => ','
23
+ end
24
+ it 'creates an accessible stack' do
25
+ calculator = Calculator.new
26
+ calculator.stack.should be_kind_of(Stack)
27
+ end
28
+ it 'creates an accessible parser' do
29
+ calculator = Calculator.new
30
+ calculator.parser.should be_kind_of(Parser)
31
+ end
32
+ end
33
+
34
+ describe "#solve" do
35
+ it 'passes the string to the parser' do
36
+ subject.stack.stub(:solve)
37
+ subject.parser.should_receive(:parse).with "3 4 +"
38
+ subject.solve "3 4 +"
39
+ end
40
+ it 'delegates solving to the stack' do
41
+ subject.parser.stub(:parse).and_return ["3", "4", "+"]
42
+ subject.stack.should_receive(:solve).with ["3", "4", "+"]
43
+ subject.solve "3 4 +"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ module RPN
4
+ describe Parser do
5
+ describe "#initialize" do
6
+ it 'sets the delimiter' do
7
+ parser = Parser.new ' '
8
+ parser.delimiter.should == ' '
9
+ end
10
+ describe "makes the parser raise an InvalidDelimiterError" do
11
+ %w{. + - * / ^ 19 425 0}.each do |invalid_delimiter|
12
+ it "when using #{invalid_delimiter} as a delimiter" do
13
+ expect {
14
+ Parser.new invalid_delimiter
15
+ }.to raise_error(Parser::InvalidDelimiterError)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#parse" do
22
+ context "with a space delimiter" do
23
+ subject { Parser.new ' ' }
24
+
25
+ it 'returns a tokenized array' do
26
+ subject.parse("1 4 5 + -").should == [1, 4, 5, :+, :-]
27
+ subject.parse("1 2 3 4 5 6 7 8 + - * / ^").should == [1, 2, 3, 4, 5, 6, 7, 8, :+, :-, :*, :/, :**]
28
+ end
29
+ describe "edge cases" do
30
+ it 'strips any extra spaces' do
31
+ subject.parse("1 4 5 + -").should == [1, 4, 5, :+, :-]
32
+ end
33
+ end
34
+ end
35
+ context "with a comma delimiter" do
36
+ subject { Parser.new ',' }
37
+
38
+ it 'returns a tokenized array' do
39
+ subject.parse("1,4,5.3,+,-").should == [1, 4, 5.3, :+, :-]
40
+ subject.parse("1,2,3,4,5,6,7,8,+,-,*,/,^").should == [1,2,3,4,5,6,7,8,:+,:-,:*,:/,:**]
41
+ end
42
+ describe "edge cases" do
43
+ it 'strips any extra spaces and delimiter' do
44
+ subject.parse("1,, 4 , 5.3 ,,,+, -").should == [1, 4, 5.3, :+, :-]
45
+ end
46
+ end
47
+ end
48
+ context "with inconsistent or malformed strings" do
49
+ subject { Parser.new ',' }
50
+
51
+ it 'raises a MalformedStringError' do
52
+ expect {
53
+ subject.parse "1,4.5,&+,-"
54
+ }.to raise_error(Parser::MalformedStringError, "Offending token: &+")
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ module RPN
4
+ describe Stack do
5
+ subject { Stack.new 2 }
6
+
7
+ describe "#initialize" do
8
+ it 'sets the delimiter' do
9
+ stack = Stack.new 3
10
+ stack.arity.should == 3
11
+ end
12
+ end
13
+
14
+ describe "#solve", "iterates the tokens" do
15
+ context 'when the token is a value' do
16
+ it 'pushes it to the stack' do
17
+ subject.should_receive(:push).with 3
18
+ subject.solve [3]
19
+ end
20
+ end
21
+ context 'when the token is an operator' do
22
+ context 'if the size of the stack is below the arity' do
23
+ it 'raises an error' do
24
+ subject.stub(:size).and_return 1
25
+ expect {
26
+ subject.solve [:+]
27
+ }.to raise_error(Stack::InsufficientValuesAvailable)
28
+ end
29
+ end
30
+ context 'otherwise' do
31
+ it 'pops n elements from the stack and pushes the result' do
32
+ subject.stub(:size).and_return 3, 1
33
+ elements = [1,2]
34
+ subject.should_receive(:pop).with(subject.arity).and_return elements
35
+ subject.should_receive(:push).with(3)
36
+
37
+ subject.solve [:+]
38
+ end
39
+ end
40
+ end
41
+ context 'when at the end there is more than one result' do
42
+ it 'raises an error' do
43
+ subject.stub(:size).and_return 2
44
+ expect {
45
+ subject.solve [:+]
46
+ }.to raise_error(Stack::UnsolvableExpressionError)
47
+ end
48
+ end
49
+ context 'when everything is fine' do
50
+ it 'returns the result' do
51
+ subject.stub(:size).and_return 3, 1
52
+ elements = [1,2]
53
+ subject.stub(:pop).with(subject.arity).and_return elements
54
+
55
+ subject.solve([:+]).should == 3
56
+ end
57
+ end
58
+
59
+ it 'clears the stack before each call' do
60
+ subject.solve [3]
61
+ subject.solve [4]
62
+ subject.elements.should == [4]
63
+ end
64
+ end
65
+
66
+ describe "#push" do
67
+ it 'pushes a token to the elements collection' do
68
+ subject.elements.should_receive(:push).with 3
69
+ subject.push 3
70
+ end
71
+ end
72
+ describe "#pop" do
73
+ it 'pushes a token to the elements collection' do
74
+ subject.elements.should_receive(:pop).with 2
75
+ subject.pop 2
76
+ end
77
+ end
78
+ describe "#size" do
79
+ it 'pushes a token to the elements collection' do
80
+ subject.elements.should_receive(:size)
81
+ subject.size
82
+ end
83
+ end
84
+ describe "#clear" do
85
+ it 'clears the stack' do
86
+ subject.elements.should_receive(:clear)
87
+ subject.clear
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,17 @@
1
+ require 'bundler'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts "Run `bundle install` to install missing gems"
7
+ exit e.status_code
8
+ end
9
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
10
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
11
+
12
+ require 'rpn'
13
+ require 'rspec'
14
+
15
+ # Requires supporting files with custom matchers and macros, etc,
16
+ # in ./support/ and its subdirectories.
17
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rpn
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Josep M. Bach
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-09 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 7
32
+ version: 1.0.7
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 2
45
+ - 4
46
+ - 0
47
+ version: 2.4.0
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: guard
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: guard-rspec
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ type: :development
75
+ version_requirements: *id004
76
+ description: A simple Reverse Polish Notation calculator in Ruby
77
+ email:
78
+ - josep.m.bach@gmail.com
79
+ executables: []
80
+
81
+ extensions: []
82
+
83
+ extra_rdoc_files: []
84
+
85
+ files:
86
+ - .gitignore
87
+ - .rspec
88
+ - .rvmrc
89
+ - Gemfile
90
+ - Gemfile.lock
91
+ - Guardfile
92
+ - Rakefile
93
+ - Readme.md
94
+ - lib/rpn.rb
95
+ - lib/rpn/calculator.rb
96
+ - lib/rpn/parser.rb
97
+ - lib/rpn/stack.rb
98
+ - lib/rpn/version.rb
99
+ - rpn.gemspec
100
+ - spec/acceptance/acceptance_spec.rb
101
+ - spec/rpn/calculator_spec.rb
102
+ - spec/rpn/parser_spec.rb
103
+ - spec/rpn/stack_spec.rb
104
+ - spec/spec_helper.rb
105
+ has_rdoc: true
106
+ homepage: http://github.com/txus/rpn
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project: rpn
133
+ rubygems_version: 1.3.7
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: A simple Reverse Polish Notation calculator in Ruby
137
+ test_files:
138
+ - spec/acceptance/acceptance_spec.rb
139
+ - spec/rpn/calculator_spec.rb
140
+ - spec/rpn/parser_spec.rb
141
+ - spec/rpn/stack_spec.rb
142
+ - spec/spec_helper.rb