expression_parser 0.9.0

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/README ADDED
@@ -0,0 +1,14 @@
1
+ # Taken from http://lukaszwrobel.pl/blog/math-parser-part-3-implementation
2
+ require 'rubygems'
3
+ require 'expression_parser'
4
+
5
+ parser = ExpressionParser::Parser.new
6
+
7
+ loop do
8
+ begin
9
+ print '>> '
10
+ puts parser.parse(gets)
11
+ rescue RuntimeError
12
+ puts 'Error occured: ' + $!
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ desc 'Generate documentation for the expression_parser plugin.'
7
+ Rake::RDocTask.new(:rdoc) do |rdoc|
8
+ rdoc.rdoc_dir = 'rdoc'
9
+ rdoc.title = 'expression_parser'
10
+ rdoc.options << '--line-numbers' << '--inline-source'
11
+ rdoc.rdoc_files.include('README')
12
+ rdoc.rdoc_files.include('lib/**/*.rb')
13
+ end
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = "expression_parser"
17
+ s.version = "0.9.0"
18
+ s.author = "Lukasz Wrobel"
19
+ s.homepage = "http://lukaszwrobel.pl/blog/math-parser-part-3-implementation"
20
+ s.platform = Gem::Platform::RUBY
21
+ s.summary = "A math parser"
22
+ s.files = FileList["{lib}/**/*"].to_a +
23
+ ["Rakefile","parser_spec.rb"]
24
+ s.require_path = "lib"
25
+ s.description = File.read("README")
26
+ s.has_rdoc = false
27
+ s.extra_rdoc_files = ["README"]
28
+ s.description = %q{math parser}
29
+ end
30
+ Rake::GemPackageTask.new(spec) do |pkg|
31
+ pkg.need_tar = true
32
+ end
@@ -0,0 +1,4 @@
1
+ # Taken from http://lukaszwrobel.pl/blog/math-parser-part-3-implementation
2
+ require File.join(File.dirname(__FILE__),'expression_parser/token')
3
+ require File.join(File.dirname(__FILE__),'expression_parser/lexer')
4
+ require File.join(File.dirname(__FILE__),'expression_parser/parser')
@@ -0,0 +1,77 @@
1
+ # Taken from http://lukaszwrobel.pl/blog/math-parser-part-3-implementation
2
+ module ExpressionParser
3
+
4
+ class Lexer
5
+
6
+ def initialize(input)
7
+ @input = input
8
+ @return_previous_token = false
9
+ end
10
+
11
+ def get_next_token
12
+ if @return_previous_token
13
+ @return_previous_token = false
14
+ return @previous_token
15
+ end
16
+
17
+ token = Token.new
18
+
19
+ @input.lstrip!
20
+
21
+ case @input
22
+ when /\A\+/ then
23
+ token.kind = Token::Plus
24
+ when /\A-/ then
25
+ token.kind = Token::Minus
26
+ when /\A\*/ then
27
+ token.kind = Token::Multiply
28
+ when /\Adiv/ then
29
+ token.kind = Token::Divide
30
+ when /\A\// then
31
+ token.kind = Token::Divide
32
+ when /\A\d+(\.\d+)?/
33
+ token.kind = Token::Number
34
+ token.value = $&.to_f
35
+ when /\A\(/
36
+ token.kind = Token::LParen
37
+ when /\A\)/
38
+ token.kind = Token::RParen
39
+ when ''
40
+ token.kind = Token::End
41
+ when /\Ae/
42
+ token.kind = Token::Number
43
+ token.value = 2.718281828459
44
+ when /\Api/
45
+ token.kind = Token::Number
46
+ token.value = 3.1415926535898
47
+ when /\Amod/
48
+ token.kind = Token::MOD
49
+ when /\A!=/
50
+ token.kind = Token::NotEqual
51
+ when /\A<>/
52
+ token.kind = Token::NotEqual
53
+ when /\A>=/
54
+ token.kind = Token::GThanE
55
+ when /\A>/
56
+ token.kind = Token::GThan
57
+ when /\A<=/
58
+ token.kind = Token::LThanE
59
+ when /\A</
60
+ token.kind = Token::LThan
61
+ when /\A=/
62
+ token.kind = Token::Equal
63
+ end
64
+
65
+ raise "Unknown token #{@input}" if token.unknown?
66
+ @input = $'
67
+
68
+ @previous_token = token
69
+ token
70
+ end
71
+
72
+ def revert
73
+ @return_previous_token = true
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,97 @@
1
+ # Taken from http://lukaszwrobel.pl/blog/math-parser-part-3-implementation
2
+ module ExpressionParser
3
+
4
+ class Parser
5
+ def parse(input)
6
+ @lexer = Lexer.new(input)
7
+
8
+ expression_value = expression
9
+ token = @lexer.get_next_token
10
+ if token.kind == Token::End
11
+ expression_value
12
+ else
13
+ case token.kind
14
+ when Token::GThan
15
+ expression_value > expression ? 1 : 0
16
+ when Token::LThan
17
+ expression_value < expression ? 1 : 0
18
+ when Token::Equal
19
+ expression_value == expression ? 1 : 0
20
+ when Token::NotEqual
21
+ expression_value != expression ? 1 : 0
22
+ when Token::GThanE
23
+ expression_value >= expression ? 1 : 0
24
+ when Token::LThanE
25
+ expression_value <= expression ? 1 : 0
26
+ else
27
+ raise 'End expected'
28
+ end
29
+ end
30
+ end
31
+
32
+ protected
33
+ def expression
34
+ component1 = factor
35
+
36
+ additive_operators = [Token::Plus, Token::Minus]
37
+
38
+ token = @lexer.get_next_token
39
+ while additive_operators.include?(token.kind)
40
+ component2 = factor
41
+
42
+ if token.kind == Token::Plus
43
+ component1 += component2
44
+ else
45
+ component1 -= component2
46
+ end
47
+
48
+ token = @lexer.get_next_token
49
+ end
50
+ @lexer.revert
51
+
52
+ component1
53
+ end
54
+
55
+ def factor
56
+ factor1 = number
57
+
58
+ multiplicative_operators = [Token::Multiply, Token::Divide, Token::MOD]
59
+
60
+ token = @lexer.get_next_token
61
+ while multiplicative_operators.include?(token.kind)
62
+ factor2 = number
63
+
64
+ if token.kind == Token::Multiply
65
+ factor1 *= factor2
66
+ elsif token.kind == Token::MOD
67
+ factor1 %= factor2
68
+ else
69
+ factor1 /= factor2
70
+ end
71
+
72
+ token = @lexer.get_next_token
73
+ end
74
+ @lexer.revert
75
+
76
+ factor1
77
+ end
78
+
79
+ def number
80
+ token = @lexer.get_next_token
81
+
82
+ if token.kind == Token::LParen
83
+ value = expression
84
+
85
+ expected_rparen = @lexer.get_next_token
86
+ raise 'Unbalanced parenthesis' unless expected_rparen.kind == Token::RParen
87
+ elsif token.kind == Token::Number
88
+ value = token.value
89
+ else
90
+ raise 'Not a number'
91
+ end
92
+
93
+ value
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,38 @@
1
+ # Taken from http://lukaszwrobel.pl/blog/math-parser-part-3-implementation
2
+ module ExpressionParser
3
+
4
+ class Token
5
+ Plus = 0
6
+ Minus = 1
7
+ Multiply = 2
8
+ Divide = 3
9
+
10
+ Number = 4
11
+
12
+ LParen = 5
13
+ RParen = 6
14
+
15
+ MOD = 7
16
+ GThan = 8
17
+ LThan = 9
18
+ Equal = 10
19
+ NotEqual = 11
20
+ GThanE = 12
21
+ LThanE = 13
22
+
23
+ End = 14
24
+
25
+ attr_accessor :kind
26
+ attr_accessor :value
27
+
28
+ def initialize
29
+ @kind = nil
30
+ @value = nil
31
+ end
32
+
33
+ def unknown?
34
+ @kind.nil?
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,109 @@
1
+ require File.join(File.dirname(__FILE__),'lib/expression_parser')
2
+ include ExpressionParser
3
+
4
+ describe Parser do
5
+ before(:each) do
6
+ @parser = Parser.new
7
+ end
8
+
9
+ it 'should compute 5 when given 2 + 3' do
10
+ @parser.parse('2 + 3').should == 5
11
+ end
12
+
13
+ it 'should compute 6 when given 2 * 3' do
14
+ @parser.parse('2 * 3').should == 6
15
+ end
16
+
17
+ it 'should compute 89 when given 89' do
18
+ @parser.parse('89').should == 89
19
+ end
20
+
21
+ it 'should raise an error when input is empty' do
22
+ lambda {@parser.parse('')}.should raise_error()
23
+ end
24
+
25
+ it 'should omit white spaces' do
26
+ @parser.parse(' 12 - 8 ').should == 4
27
+ @parser.parse('142 -9 ').should == 133
28
+ @parser.parse('72+ 15').should == 87
29
+ @parser.parse(' 12* 4').should == 48
30
+ @parser.parse(' 50/10').should == 5
31
+ end
32
+
33
+ it 'should treat dot separated floating point numbers as a valid input' do
34
+ @parser.parse('2.5').should == 2.5
35
+ @parser.parse('4*2.5 + 8.5+1.5 / 3.0').should == 19
36
+ @parser.parse('5.0005 + 0.0095').should be_close(5.01, 0.01)
37
+ end
38
+
39
+ it 'should handle tight expressions' do
40
+ @parser.parse('67+2').should == 69
41
+ @parser.parse(' 2-7').should == -5
42
+ @parser.parse('5*7 ').should == 35
43
+ @parser.parse('8/4').should == 2
44
+ end
45
+
46
+ it 'should calculate long additive expressions from left to right' do
47
+ @parser.parse('2 -4 +6 -1 -1- 0 +8').should == 10
48
+ @parser.parse('1 -1 + 2 - 2 + 4 - 4 + 6').should == 6
49
+ end
50
+
51
+ it 'should calculate long multiplicative expressions from left to right' do
52
+ @parser.parse('2 -4 +6 -1 -1- 0 +8').should == 10
53
+ @parser.parse('1 -1 + 2 - 2 + 4 - 4 + 6').should == 6
54
+ end
55
+
56
+ it 'should calculate long, mixed additive and multiplicative expressions from left to right' do
57
+ @parser.parse(' 2*3 - 4*5 + 6/3 ').should == -12
58
+ @parser.parse('2*3*4/8 - 5/2*4 + 6 + 0/3 ').should == -1
59
+ end
60
+
61
+ it 'should return float pointing numbers when division result is not an integer' do
62
+ @parser.parse('10/4').should == 2.5
63
+ @parser.parse('5/3').should be_close(1.66, 0.01)
64
+ @parser.parse('3 + 8/5 -1 -2*5').should be_close(-6.4, 0.01)
65
+ end
66
+
67
+ it 'should raise an error on wrong token' do
68
+ lambda {@parser.parse(' 6 + c')}.should raise_error()
69
+ lambda {@parser.parse(' 7 &amp; 2')}.should raise_error()
70
+ lambda {@parser.parse(' %')}.should raise_error()
71
+ end
72
+
73
+ it 'should raise an error on syntax error' do
74
+ lambda {@parser.parse(' 5 + + 6')}.should raise_error()
75
+ lambda {@parser.parse(' -5 + 2')}.should raise_error()
76
+ end
77
+
78
+ it 'should return Infinity when attempt to divide by zero occurs' do
79
+ @parser.parse('5/0').should be_infinite
80
+ @parser.parse(' 2 - 1 + 14/0 + 7').should be_infinite
81
+ end
82
+
83
+ it 'should compute 2 when given (2)' do
84
+ @parser.parse('(2)').should == 2
85
+ end
86
+
87
+ it 'should compute complex expressions enclosed in parenthesis' do
88
+ @parser.parse('(5 + 2*3 - 1 + 7 * 8)').should == 66
89
+ @parser.parse('(67 + 2 * 3 - 67 + 2/1 - 7)').should == 1
90
+ end
91
+
92
+ it 'should compute expressions with many subexpressions enclosed in parenthesis' do
93
+ @parser.parse('(2) + (17*2-30) * (5)+2 - (8/2)*4').should == 8
94
+ @parser.parse('(5*7/5) + (23) - 5 * (98-4)/(6*7-42)').should be_infinite
95
+ end
96
+
97
+ it 'should handle nested parenthesis' do
98
+ @parser.parse('(((((5)))))').should == 5
99
+ @parser.parse('(( ((2)) + 4))*((5))').should == 30
100
+ end
101
+
102
+ it 'should raise an error on unbalanced parenthesis' do
103
+ lambda {@parser.parse('2 + (5 * 2')}.should raise_error()
104
+ lambda {@parser.parse('(((((4))))')}.should raise_error()
105
+ lambda {@parser.parse('((2)) * ((3')}.should raise_error()
106
+ lambda {@parser.parse('((9)) * ((1)')}.should raise_error()
107
+ end
108
+ end
109
+
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: expression_parser
3
+ version: !ruby/object:Gem::Version
4
+ hash: 59
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
+ platform: ruby
12
+ authors:
13
+ - Lukasz Wrobel
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-20 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: math parser
23
+ email:
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README
30
+ files:
31
+ - lib/expression_parser.rb
32
+ - lib/expression_parser/token.rb
33
+ - lib/expression_parser/lexer.rb
34
+ - lib/expression_parser/parser.rb
35
+ - Rakefile
36
+ - parser_spec.rb
37
+ - README
38
+ has_rdoc: true
39
+ homepage: http://lukaszwrobel.pl/blog/math-parser-part-3-implementation
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ hash: 3
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.7
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: A math parser
72
+ test_files: []
73
+