math_engine 0.1.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.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
+