expression_parser 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+