math_engine 0.2.0 → 0.6.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 +28 -2
- data/lib/context.rb +63 -0
- data/lib/errors.rb +16 -1
- data/lib/evaluators/calculate.rb +60 -0
- data/lib/evaluators/finders.rb +9 -0
- data/lib/lexer.rb +23 -17
- data/lib/math_engine.rb +21 -47
- data/lib/nodes.rb +27 -70
- data/lib/parser.rb +97 -97
- metadata +57 -68
data/README.md
CHANGED
@@ -6,7 +6,7 @@ Install with
|
|
6
6
|
|
7
7
|
gem install math_engine
|
8
8
|
|
9
|
-
The only dependency is
|
9
|
+
The only dependency is [lexr](http://github.com/michaelbaldry/lexr), which is really lightweight and has no external dependencies.
|
10
10
|
|
11
11
|
## An example: Expressions
|
12
12
|
|
@@ -22,14 +22,40 @@ results in an output of
|
|
22
22
|
|
23
23
|
65.0
|
24
24
|
70.0
|
25
|
+
|
26
|
+
extending is easy using functions, you can add single functions using MathEngine.define
|
27
|
+
|
28
|
+
engine.context.define :add_em do |x, y|
|
29
|
+
x + y
|
30
|
+
end
|
31
|
+
|
32
|
+
or you can write all your functions in a class and add the class
|
33
|
+
|
34
|
+
class SomeFunctions
|
35
|
+
def add_em(x, y)
|
36
|
+
x + y
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
engine.context.include_library SomeFunctions.new
|
25
41
|
|
42
|
+
and calling them with
|
43
|
+
|
44
|
+
engine.evaluate("1 + 1 + add_em(2, 2)")
|
45
|
+
|
46
|
+
All functions are pulled in from the built in Math class by default, so all the standard ruby math functions are available (cos, sin, tan etc)
|
47
|
+
|
26
48
|
if you missed a closing parenthesis, had an operator where it wasn't meant to be, you might get something like this:
|
27
49
|
|
28
50
|
Unexpected multiplication(*), expected: number, variable name or open_parenthesis
|
29
51
|
|
30
52
|
and that is pretty much every feature so far. Please let me know of any bugs or additions that you'd like to see!
|
31
53
|
|
54
|
+
## Contributors
|
55
|
+
|
56
|
+
Mario de la Ossa (mdelaossa): Handling of complex numbers and upgraded to work with 1.9.X
|
57
|
+
|
32
58
|
## License
|
33
59
|
|
34
60
|
See the LICENSE file included with the distribution for licensing and
|
35
|
-
copyright details.
|
61
|
+
copyright details.
|
data/lib/context.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
class MathEngine
|
2
|
+
class Context
|
3
|
+
DEFAULT_OPTIONS = {case_sensitive: true}
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
@opts = DEFAULT_OPTIONS.merge(opts)
|
7
|
+
@variables = {}
|
8
|
+
@dynamic_library = Class.new.new
|
9
|
+
@libraries = [@dynamic_library, Math]
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(variable_name)
|
13
|
+
@variables[key variable_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(variable_name, value)
|
17
|
+
raise MathEngine::UnableToModifyConstantError.new(key variable_name) if constant?(key variable_name) &&
|
18
|
+
get(variable_name)
|
19
|
+
|
20
|
+
@variables[key variable_name] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def define(function_name, func = nil, &block)
|
24
|
+
@dynamic_library.class.send :define_method, function_name.to_sym do |*args|
|
25
|
+
func ? func.call(*args) : block.call(*args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(function_name, *args)
|
30
|
+
library = library_for_function(function_name)
|
31
|
+
raise UnknownFunctionError.new(function_name) unless library
|
32
|
+
library.send(function_name, *args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def include_library(library)
|
36
|
+
@libraries << library
|
37
|
+
end
|
38
|
+
|
39
|
+
def constants
|
40
|
+
@variables.keys.select { |variable_name| constant?(variable_name) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def variables
|
44
|
+
@variables.keys - constants
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def constant?(variable_name)
|
50
|
+
variable_name.upcase == variable_name
|
51
|
+
end
|
52
|
+
|
53
|
+
def key(variable_name)
|
54
|
+
return variable_name if @opts[:case_sensitive]
|
55
|
+
result = @variables.keys.select { |key| key.downcase == variable_name.downcase }.first
|
56
|
+
result || variable_name
|
57
|
+
end
|
58
|
+
|
59
|
+
def library_for_function(function_name)
|
60
|
+
@libraries.detect { |l| l.methods.include? function_name.to_sym }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/errors.rb
CHANGED
@@ -29,7 +29,7 @@ class MathEngine
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
class
|
32
|
+
class UnableToModifyConstantError < StandardError
|
33
33
|
def initialize(constant_name)
|
34
34
|
@constant_name = constant_name
|
35
35
|
end
|
@@ -38,4 +38,19 @@ class MathEngine
|
|
38
38
|
"Unable to modify value of constant '#{@constant_name}'"
|
39
39
|
end
|
40
40
|
end
|
41
|
+
|
42
|
+
class UnknownEvaluatorError < StandardError
|
43
|
+
def initialize(evaluator_name, expected_const)
|
44
|
+
@evaluator_name = evaluator_name
|
45
|
+
@expected_const = expected_const
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"Unable to find an evaluator called #{@evaluator_name}(#{@expected_const})"
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
self.to_s == other.to_s
|
54
|
+
end
|
55
|
+
end
|
41
56
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class MathEngine
|
2
|
+
module Evaluators
|
3
|
+
class Calculate
|
4
|
+
def initialize(context)
|
5
|
+
@context = context
|
6
|
+
end
|
7
|
+
|
8
|
+
def literal_number(node)
|
9
|
+
node.value
|
10
|
+
end
|
11
|
+
|
12
|
+
def expression(node)
|
13
|
+
node.left.evaluate(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def identifier(node)
|
17
|
+
@context.get node.value
|
18
|
+
end
|
19
|
+
|
20
|
+
def assignment(node)
|
21
|
+
result = node.right.evaluate(self)
|
22
|
+
@context.set(node.left.value, result)
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def addition(node)
|
27
|
+
node.left.evaluate(self) + node.right.evaluate(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def subtraction(node)
|
31
|
+
node.left.evaluate(self) - node.right.evaluate(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def multiplication(node)
|
35
|
+
node.left.evaluate(self) * node.right.evaluate(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def division(node)
|
39
|
+
node.left.evaluate(self) / node.right.evaluate(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def exponent(node)
|
43
|
+
node.left.evaluate(self) ** node.right.evaluate(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def modulus(node)
|
47
|
+
node.left.evaluate(self) % node.right.evaluate(self)
|
48
|
+
end
|
49
|
+
|
50
|
+
def function_call(node)
|
51
|
+
parameters = node.right ? node.right.to_a.collect { |p| p.evaluate(self) } : []
|
52
|
+
@context.call(node.left, *parameters)
|
53
|
+
end
|
54
|
+
|
55
|
+
def parameters(node)
|
56
|
+
node.left.evaluate(self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class MathEngine
|
2
|
+
module Evaluators
|
3
|
+
def self.find_by_name(name)
|
4
|
+
class_name = name.to_s.sub(%r{^[a-z\d]}) { $&.upcase }
|
5
|
+
class_name.gsub!(%r{(?:_|(\/))([a-z\d]*)}) { "#{$1}#{$2.capitalize}" }
|
6
|
+
MathEngine::Evaluators.const_get(class_name) rescue raise MathEngine::UnknownEvaluatorError.new(name, "MathEngine::Evaluators::#{class_name}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/lib/lexer.rb
CHANGED
@@ -1,20 +1,26 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'lexr'
|
3
|
-
|
4
1
|
class MathEngine
|
5
2
|
Lexer = Lexr.that {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
3
|
+
ignores /\s/ => :whitespace
|
4
|
+
|
5
|
+
legal_place_for_binary_operator = lambda { |prev| [:addition,
|
6
|
+
:subtraction,
|
7
|
+
:multiplication,
|
8
|
+
:division,
|
9
|
+
:open_parenthesis,
|
10
|
+
:start].include? prev.type }
|
11
|
+
|
12
|
+
matches ',' => :comma
|
13
|
+
matches '=' => :assignment
|
14
|
+
matches '+' => :addition, :unless => legal_place_for_binary_operator
|
15
|
+
matches '-' => :subtraction, :unless => legal_place_for_binary_operator
|
16
|
+
matches '*' => :multiplication, :unless => legal_place_for_binary_operator
|
17
|
+
matches '/' => :division, :unless => legal_place_for_binary_operator
|
18
|
+
matches '^' => :exponent, :unless => legal_place_for_binary_operator
|
19
|
+
matches '%' => :modulus, :unless => legal_place_for_binary_operator
|
20
|
+
matches '(' => :open_parenthesis
|
21
|
+
matches ')' => :close_parenthesis
|
22
|
+
|
23
|
+
matches /([-+]?(\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?[r]?|[-+]?((\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?)?[i]|[-+]?(\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?[r]?[-+]((\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-2]?\d{1,2})?)?[i])/ => :number, :convert_with => lambda { |v| BigDecimal(v) }
|
24
|
+
matches /[a-z][a-z0-9_]*/i => :identifier, :convert_with => lambda { |v| v.to_sym }
|
19
25
|
}
|
20
|
-
end
|
26
|
+
end
|
data/lib/math_engine.rb
CHANGED
@@ -1,55 +1,29 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
1
|
+
#require "mathn"
|
2
|
+
require "bigdecimal"
|
3
|
+
|
4
|
+
require_relative "errors"
|
5
|
+
require_relative "lexer"
|
6
|
+
require_relative "parser"
|
7
|
+
require_relative "context"
|
8
|
+
|
9
|
+
require_relative "evaluators/finders"
|
10
|
+
require_relative "evaluators/calculate"
|
4
11
|
|
5
12
|
class MathEngine
|
6
|
-
|
7
|
-
|
8
|
-
@dyn_library = Class.new.new
|
9
|
-
@libraries = [@dyn_library, Math]
|
10
|
-
end
|
11
|
-
|
12
|
-
def evaluate(expression)
|
13
|
-
Parser.new(Lexer.new(expression)).parse.evaluate(self)
|
14
|
-
end
|
15
|
-
|
16
|
-
def set(variable_name, value)
|
17
|
-
raise UnableToModifyConstant.new(variable_name) if @variables.keys.include? variable_name and variable_name.to_s == variable_name.to_s.upcase
|
18
|
-
@variables[variable_name] = value
|
19
|
-
end
|
20
|
-
|
21
|
-
def get(variable_name)
|
22
|
-
raise UnknownVariableError.new(variable_name) unless @variables.keys.include? variable_name
|
23
|
-
@variables[variable_name]
|
24
|
-
end
|
25
|
-
|
26
|
-
def variables
|
27
|
-
@variables.keys.collect { |k| k.to_s }.reject { |v| v.downcase != v }.sort.collect { |k| k.to_sym }
|
28
|
-
end
|
29
|
-
|
30
|
-
def constants
|
31
|
-
@variables.keys.collect { |k| k.to_s }.reject { |v| v.upcase != v }.sort.collect { |k| k.to_sym }
|
32
|
-
end
|
13
|
+
DEFAULT_OPTIONS = {evaluator: :calculate,
|
14
|
+
case_sensitive: true}
|
33
15
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
cls.send name, *parameters
|
16
|
+
def initialize(opts = {})
|
17
|
+
@opts = DEFAULT_OPTIONS.merge(opts)
|
18
|
+
@opts[:context] = Context.new(@opts) unless @opts[:context]
|
38
19
|
end
|
39
20
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def include_library(library)
|
47
|
-
@libraries << library
|
21
|
+
def evaluate(expression)
|
22
|
+
evaluator = MathEngine::Evaluators.find_by_name(@opts[:evaluator]).new(context)
|
23
|
+
Parser.new(Lexer.new(expression)).parse.evaluate(evaluator)
|
48
24
|
end
|
49
25
|
|
50
|
-
|
51
|
-
|
52
|
-
def class_for_function(name)
|
53
|
-
@libraries.detect { |l| l.methods.include? name.to_s }
|
26
|
+
def context
|
27
|
+
@opts[:context]
|
54
28
|
end
|
55
|
-
end
|
29
|
+
end
|
data/lib/nodes.rb
CHANGED
@@ -1,83 +1,40 @@
|
|
1
1
|
class MathEngine
|
2
2
|
class Node
|
3
|
-
|
4
|
-
|
3
|
+
attr_reader :left, :right
|
4
|
+
alias :value :left
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class IdentifierNode < Node
|
24
|
-
def evaluate(engine)
|
25
|
-
engine.get value
|
6
|
+
def initialize(left, right = nil)
|
7
|
+
@left, @right = left, right
|
8
|
+
end
|
9
|
+
|
10
|
+
def evaluate(evaluator)
|
11
|
+
evaluator.send(method_name, self)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def method_name
|
17
|
+
class_name = self.class.name[0..-5]
|
18
|
+
class_name = class_name[class_name.rindex("::")+2..-1] if class_name.rindex("::")
|
19
|
+
method_name = class_name.gsub(%r{([A-Z\d]+)([A-Z][a-z])},'\1_\2').gsub(%r{([a-z\d])([A-Z])},'\1_\2').downcase
|
26
20
|
end
|
27
21
|
end
|
28
22
|
|
29
|
-
class
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
result
|
34
|
-
end
|
35
|
-
end
|
23
|
+
class LiteralNumberNode < Node ; end
|
24
|
+
class ExpressionNode < Node ; end
|
25
|
+
class IdentifierNode < Node ; end
|
26
|
+
class AssignmentNode < Node ; end
|
36
27
|
|
37
|
-
class AdditionNode < Node
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
28
|
+
class AdditionNode < Node ; end
|
29
|
+
class SubtractionNode < Node ; end
|
30
|
+
class MultiplicationNode < Node ; end
|
31
|
+
class DivisionNode < Node ; end
|
32
|
+
class ExponentNode < Node ; end
|
33
|
+
class ModulusNode < Node ; end
|
42
34
|
|
43
|
-
class
|
44
|
-
def evaluate(engine)
|
45
|
-
left.evaluate(engine) - right.evaluate(engine)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
class MultiplicationNode < Node
|
49
|
-
def evaluate(engine)
|
50
|
-
left.evaluate(engine) * right.evaluate(engine)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
class DivisionNode < Node
|
54
|
-
def evaluate(engine)
|
55
|
-
left.evaluate(engine) / right.evaluate(engine)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
class ExponentNode < Node
|
59
|
-
def evaluate(engine)
|
60
|
-
left.evaluate(engine) ** right.evaluate(engine)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
class ModulusNode < Node
|
64
|
-
def evaluate(engine)
|
65
|
-
left.evaluate(engine) % right.evaluate(engine)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
class FunctionCallNode < Node
|
70
|
-
def evaluate(engine)
|
71
|
-
parameters = right ? right.to_a.collect { |p| p.evaluate(engine) } : []
|
72
|
-
engine.call(left, *parameters)
|
73
|
-
end
|
74
|
-
end
|
35
|
+
class FunctionCallNode < Node ; end
|
75
36
|
|
76
37
|
class ParametersNode < Node
|
77
|
-
def evaluate(engine)
|
78
|
-
left.evaluate(engine)
|
79
|
-
end
|
80
|
-
|
81
38
|
def to_a
|
82
39
|
[left] + (right ? right.to_a : [])
|
83
40
|
end
|
data/lib/parser.rb
CHANGED
@@ -2,101 +2,101 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'nodes'))
|
|
2
2
|
|
3
3
|
class MathEngine
|
4
4
|
class Parser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
def initialize(lexer)
|
6
|
+
@lexer = lexer
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
#statement = { <identifier> <assignment> } expression <end>
|
11
|
+
#expression = term { ( <addition> | <subtraction> ) term }
|
12
|
+
#term = exp { ( <multiplication> | <division> ) exp }
|
13
|
+
#exp = factor { ( <exponent> | <modulus> ) factor }
|
14
|
+
#factor = <call> | <identifier> | <number> | ( <open_parenthesis> expression <close_parenthesis> )
|
15
15
|
#call = <identifier> <open_parenthesis> { call_parameter } <close_parenthesis>
|
16
16
|
#call_parameter = <expression> { <comma> call_parameter }
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
statement
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def statement
|
23
|
+
next!
|
24
|
+
if current.type == :identifier && peek.type == :assignment
|
25
|
+
variable_name = current.value
|
26
|
+
next!
|
27
|
+
expect_current :assignment
|
28
|
+
next!
|
29
|
+
result = MathEngine::AssignmentNode.new(MathEngine::IdentifierNode.new(variable_name), expression)
|
30
|
+
else
|
31
|
+
result = expression
|
32
|
+
end
|
33
|
+
next!
|
34
|
+
expect_current :end
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def expression
|
39
|
+
left = term
|
40
|
+
result = nil
|
41
|
+
while [:addition, :subtraction].include? current.type
|
42
|
+
node_type = current.type == :addition ? MathEngine::AdditionNode : MathEngine::SubtractionNode
|
43
|
+
next!
|
44
|
+
left = node_type.new(left, term)
|
45
|
+
end
|
46
|
+
result = MathEngine::ExpressionNode.new(result || left)
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def term
|
51
|
+
left = exp
|
52
|
+
result = nil
|
53
|
+
while [:multiplication, :division].include? current.type
|
54
|
+
node_type = current.type == :multiplication ? MathEngine::MultiplicationNode : MathEngine::DivisionNode
|
55
|
+
next!
|
56
|
+
left = node_type.new(left, exp)
|
57
|
+
end
|
58
|
+
result || left
|
59
|
+
end
|
21
60
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
result = expression
|
32
|
-
end
|
33
|
-
next!
|
34
|
-
expect_current :end
|
35
|
-
result
|
36
|
-
end
|
37
|
-
|
38
|
-
def expression
|
39
|
-
left = term
|
40
|
-
result = nil
|
41
|
-
while [:addition, :subtraction].include? current.type
|
42
|
-
node_type = current.type == :addition ? MathEngine::AdditionNode : MathEngine::SubtractionNode
|
43
|
-
next!
|
44
|
-
left = node_type.new(left, term)
|
45
|
-
end
|
46
|
-
result = MathEngine::ExpressionNode.new(result || left)
|
47
|
-
result
|
48
|
-
end
|
49
|
-
|
50
|
-
def term
|
51
|
-
left = exp
|
52
|
-
result = nil
|
53
|
-
while [:multiplication, :division].include? current.type
|
54
|
-
node_type = current.type == :multiplication ? MathEngine::MultiplicationNode : MathEngine::DivisionNode
|
55
|
-
next!
|
56
|
-
left = node_type.new(left, exp)
|
57
|
-
end
|
58
|
-
result || left
|
59
|
-
end
|
60
|
-
|
61
|
-
def exp
|
62
|
-
left = factor
|
63
|
-
result = nil
|
64
|
-
while [:exponent, :modulus].include? current.type
|
65
|
-
node_type = current.type == :exponent ? MathEngine::ExponentNode : MathEngine::ModulusNode
|
66
|
-
next!
|
67
|
-
left = node_type.new(left, factor)
|
68
|
-
end
|
69
|
-
result || left
|
61
|
+
def exp
|
62
|
+
left = factor
|
63
|
+
result = nil
|
64
|
+
while [:exponent, :modulus].include? current.type
|
65
|
+
node_type = current.type == :exponent ? MathEngine::ExponentNode : MathEngine::ModulusNode
|
66
|
+
next!
|
67
|
+
left = node_type.new(left, factor)
|
68
|
+
end
|
69
|
+
result || left
|
70
70
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
71
|
+
|
72
|
+
def factor
|
73
|
+
if current.type == :number
|
74
|
+
result = MathEngine::LiteralNumberNode.new(current.value)
|
75
|
+
next!
|
76
|
+
return result
|
77
|
+
elsif current.type == :identifier
|
78
|
+
result = peek.type == :open_parenthesis ? call : MathEngine::IdentifierNode.new(current.value)
|
79
79
|
next!
|
80
80
|
return result
|
81
81
|
end
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
83
|
+
expect_current :open_parenthesis, "number, variable or open_parenthesis"
|
84
|
+
next!
|
85
|
+
result = expression
|
86
|
+
expect_current :close_parenthesis
|
87
|
+
next!
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
def call
|
92
|
+
expect_current :identifier
|
93
|
+
function_name = current.value
|
94
|
+
next!
|
95
|
+
expect_current :open_parenthesis
|
96
|
+
next!
|
97
|
+
result = MathEngine::FunctionCallNode.new(function_name, current.type == :close_parenthesis ? nil : call_parameter)
|
98
|
+
expect_current :close_parenthesis
|
99
|
+
result
|
100
100
|
end
|
101
101
|
|
102
102
|
def call_parameter
|
@@ -108,21 +108,21 @@ class MathEngine
|
|
108
108
|
end
|
109
109
|
MathEngine::ParametersNode.new(left, right)
|
110
110
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
|
112
|
+
def current
|
113
|
+
@lexer.current
|
114
114
|
end
|
115
115
|
|
116
116
|
def peek
|
117
117
|
@lexer.peek
|
118
118
|
end
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
119
|
+
|
120
|
+
def next!
|
121
|
+
@lexer.next
|
122
|
+
end
|
123
|
+
|
124
|
+
def expect_current(type, friendly = nil)
|
125
|
+
raise MathEngine::ParseError.new("Unexpected #{current}, expected: #{friendly ? friendly : type}") unless current.type == type
|
126
126
|
end
|
127
127
|
end
|
128
128
|
end
|
metadata
CHANGED
@@ -1,102 +1,91 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: math_engine
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 0
|
10
|
-
version: 0.2.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Michael Baldry
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-09-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: lexr
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
- 2
|
33
|
-
- 2
|
34
|
-
version: 0.2.2
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.3.0
|
35
22
|
type: :runtime
|
36
|
-
version_requirements: *id001
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
|
-
name: rspec
|
39
23
|
prerelease: false
|
40
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.3.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
41
33
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
segments:
|
47
|
-
- 0
|
48
|
-
version: "0"
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
49
38
|
type: :development
|
50
|
-
|
51
|
-
|
52
|
-
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Lightweight matematical expression parser that is easy to extend
|
47
|
+
email: michael@brightbits.co.uk
|
53
48
|
executables: []
|
54
|
-
|
55
49
|
extensions: []
|
56
|
-
|
57
|
-
extra_rdoc_files:
|
50
|
+
extra_rdoc_files:
|
58
51
|
- README.md
|
59
|
-
files:
|
52
|
+
files:
|
60
53
|
- README.md
|
54
|
+
- lib/context.rb
|
61
55
|
- lib/errors.rb
|
56
|
+
- lib/evaluators/calculate.rb
|
57
|
+
- lib/evaluators/finders.rb
|
62
58
|
- lib/lexer.rb
|
63
59
|
- lib/math_engine.rb
|
64
60
|
- lib/nodes.rb
|
65
61
|
- lib/parser.rb
|
66
|
-
|
67
|
-
homepage: http://www.forwardtechnology.co.uk
|
62
|
+
homepage: http://www.brightbits.co.uk
|
68
63
|
licenses: []
|
69
|
-
|
70
64
|
post_install_message:
|
71
|
-
rdoc_options:
|
65
|
+
rdoc_options:
|
72
66
|
- --main
|
73
67
|
- README.md
|
74
|
-
require_paths:
|
68
|
+
require_paths:
|
75
69
|
- lib
|
76
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
71
|
none: false
|
78
|
-
requirements:
|
79
|
-
- -
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
|
82
|
-
segments:
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
segments:
|
83
77
|
- 0
|
84
|
-
|
85
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
hash: 4003661917995291831
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
80
|
none: false
|
87
|
-
requirements:
|
88
|
-
- -
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
|
91
|
-
segments:
|
92
|
-
- 0
|
93
|
-
version: "0"
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
94
85
|
requirements: []
|
95
|
-
|
96
86
|
rubyforge_project:
|
97
|
-
rubygems_version: 1.
|
87
|
+
rubygems_version: 1.8.24
|
98
88
|
signing_key:
|
99
89
|
specification_version: 3
|
100
|
-
summary:
|
90
|
+
summary: Lightweight mathematical expression parser
|
101
91
|
test_files: []
|
102
|
-
|