expression_parser 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +14 -0
- data/Rakefile +32 -0
- data/lib/expression_parser.rb +4 -0
- data/lib/expression_parser/lexer.rb +77 -0
- data/lib/expression_parser/parser.rb +97 -0
- data/lib/expression_parser/token.rb +38 -0
- data/parser_spec.rb +109 -0
- metadata +73 -0
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
|
data/Rakefile
ADDED
@@ -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
|
data/parser_spec.rb
ADDED
@@ -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 & 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
|
+
|