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 +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
|
+
|