math_engine 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # MathEngine
2
+
3
+ MathEngine is a lightweight mathematical expression parser and evaluator. It currently handles addition, subtraction, multiplication and division, but support is planned for other operators and variables, etc.
4
+
5
+ Install with
6
+
7
+ gem install math_engine
8
+
9
+ ## An example: Expressions
10
+
11
+ require 'rubygems'
12
+ require 'math_engine'
13
+
14
+ puts "#{MathEngine.new.evaluate("10 * (3 * 2) + (55 - 5) / (2.5 * (3 + 1))")}"
15
+
16
+ results in an output of
17
+
18
+ 65.0
19
+
20
+ if you missed a closing parenthesis, had an operator where it wasn't meant to be, you might get something like this:
21
+
22
+ Unexpected multiplication(*), expected: number or open_parenthesis
23
+
24
+ and that is pretty much every feature so far. Please let me know of any bugs or additions that you'd like to see!
@@ -0,0 +1,5 @@
1
+ class MathEngine
2
+ def evaluate(expression)
3
+ MathParser.new(MathLexer.new(expression)).parse.evaluate
4
+ end
5
+ end
data/lib/math_lexer.rb ADDED
@@ -0,0 +1,10 @@
1
+ MathLexer = Lexr.that {
2
+ ignores /\s/ => :whitespace
3
+ matches /[-+]?[0-9]*\.?[0-9]+/ => :number, :convert_with => lambda { |v| Float(v) }
4
+ matches '+' => :addition
5
+ matches '-' => :subtraction
6
+ matches '*' => :multiplication
7
+ matches '/' => :division
8
+ matches '(' => :open_parenthesis
9
+ matches ')' => :close_parenthesis
10
+ }
@@ -0,0 +1,130 @@
1
+ class MathParser
2
+ def initialize(lexer)
3
+ @lexer = lexer
4
+ end
5
+
6
+ def parse
7
+ #statement = expression <end>
8
+ #expression = term { ( <addition> | <subtraction> ) term }
9
+ #term = factor { ( <multiplication> | <division> ) factor }
10
+ #factor = <number> | <open_parenthesis> expression <close_parenthesis>
11
+ statement
12
+ end
13
+
14
+ private
15
+
16
+ def statement
17
+ next!
18
+ result = expression
19
+ next!
20
+ expect_current :end
21
+ result
22
+ end
23
+
24
+ def expression
25
+ left = term
26
+ result = nil
27
+ while [:addition, :subtraction].include? current.type
28
+ node_type = current.type == :addition ? AdditionNode : SubtractionNode
29
+ next!
30
+ left = node_type.new(left, term)
31
+ end
32
+ ExpressionNode.new(result || left)
33
+ end
34
+
35
+ def term
36
+ left = factor
37
+ result = nil
38
+ while [:multiplication, :division].include? current.type
39
+ node_type = current.type == :multiplication ? MultiplicationNode : DivisionNode
40
+ next!
41
+ left = node_type.new(left, factor)
42
+ end
43
+ result || left
44
+ end
45
+
46
+ def factor
47
+ if current.type == :number
48
+ result = LiteralNumberNode.new(current.value)
49
+ next!
50
+ return result
51
+ end
52
+ expect_current :open_parenthesis, "number or open_parenthesis"
53
+ next!
54
+ result = expression
55
+ expect_current :close_parenthesis
56
+ next!
57
+ result
58
+ end
59
+
60
+ private
61
+
62
+ def current
63
+ @lexer.current
64
+ end
65
+
66
+ def next!
67
+ @lexer.next
68
+ end
69
+
70
+ def expect_current(type, friendly = nil)
71
+ raise ParseError.new("Unexpected #{current}, expected: #{friendly ? friendly : type}") unless current.type == type
72
+ end
73
+
74
+ class Node
75
+ attr_reader :left, :right
76
+ alias :value :left
77
+
78
+ def initialize(left, right = nil)
79
+ @left, @right = left, right
80
+ end
81
+
82
+ def evaluate
83
+ raise "Evaluate not overridden in #{self.class.name}"
84
+ end
85
+ end
86
+
87
+ class LiteralNumberNode < Node
88
+ def evaluate
89
+ value
90
+ end
91
+ end
92
+
93
+ class ExpressionNode < Node
94
+ def evaluate
95
+ left.evaluate
96
+ end
97
+ end
98
+
99
+ class AdditionNode < Node
100
+ def evaluate
101
+ left.evaluate + right.evaluate
102
+ end
103
+ end
104
+
105
+ class SubtractionNode < Node
106
+ def evaluate
107
+ left.evaluate - right.evaluate
108
+ end
109
+ end
110
+ class MultiplicationNode < Node
111
+ def evaluate
112
+ left.evaluate * right.evaluate
113
+ end
114
+ end
115
+ class DivisionNode < Node
116
+ def evaluate
117
+ left.evaluate / right.evaluate
118
+ end
119
+ end
120
+
121
+ class ParseError < StandardError
122
+ def initialize(message)
123
+ @message = message
124
+ end
125
+
126
+ def to_s
127
+ @message
128
+ end
129
+ end
130
+ end
data/math_engine.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'lexr'
3
+
4
+ require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'math_lexer'))
5
+ require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'math_parser'))
6
+ require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'math_engine'))
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: math_engine
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Michael Baldry
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-24 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description:
36
+ email: michael.baldry@uswitch.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.md
43
+ files:
44
+ - math_engine.rb
45
+ - README.md
46
+ - lib/math_engine.rb
47
+ - lib/math_lexer.rb
48
+ - lib/math_parser.rb
49
+ has_rdoc: true
50
+ homepage: http://www.forwardtechnology.co.uk
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --main
56
+ - README.md
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.7
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Evaluates simple mathematical expressions
84
+ test_files: []
85
+