rpn 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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