r_calc 0.0.1
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/.autotest +10 -0
- data/.gitignore +21 -0
- data/.rspec +0 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +74 -0
- data/Rakefile +17 -0
- data/lib/r_calc/arithmetic_operations.rb +34 -0
- data/lib/r_calc/assignment_operator.rb +11 -0
- data/lib/r_calc/binary_operator.rb +10 -0
- data/lib/r_calc/binary_or_unary_operator.rb +8 -0
- data/lib/r_calc/boolean_operators.rb +36 -0
- data/lib/r_calc/formula_expression_exception.rb +5 -0
- data/lib/r_calc/formula_processor.rb +195 -0
- data/lib/r_calc/function.rb +11 -0
- data/lib/r_calc/function_operators.rb +33 -0
- data/lib/r_calc/identifier.rb +21 -0
- data/lib/r_calc/last_result_operator.rb +9 -0
- data/lib/r_calc/literal.rb +25 -0
- data/lib/r_calc/operator.rb +32 -0
- data/lib/r_calc/operator_list.rb +8 -0
- data/lib/r_calc/parenthetical_grouping.rb +20 -0
- data/lib/r_calc/parse_token.rb +18 -0
- data/lib/r_calc/popper.rb +10 -0
- data/lib/r_calc/pusher.rb +17 -0
- data/lib/r_calc/separator.rb +10 -0
- data/lib/r_calc/unary_operator.rb +11 -0
- data/lib/r_calc/version.rb +3 -0
- data/lib/r_calc.rb +12 -0
- data/r_calc.gemspec +26 -0
- data/spec/r_calc_spec.rb +91 -0
- data/spec/spec_helper.rb +42 -0
- metadata +213 -0
data/.autotest
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# ./.autotest
|
2
|
+
Autotest.add_hook(:initialize) {|at|
|
3
|
+
at.add_exception %r{^\.git} # ignore Version Control System
|
4
|
+
at.add_exception %r{^./tmp} # ignore temp files, lest autotest will run again, and again...
|
5
|
+
# at.clear_mappings # take out the default (test/test*rb)
|
6
|
+
at.add_mapping(%r{^lib/.*\.rb$}) {|f, _|
|
7
|
+
Dir['spec/**/*_spec.rb']
|
8
|
+
}
|
9
|
+
nil
|
10
|
+
}
|
data/.gitignore
ADDED
data/.rspec
ADDED
File without changes
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Milan Jaric
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# RCalc
|
2
|
+
|
3
|
+
RCalc is gem which can help you adding calculator like behaviour to your code. It is sandboxed and there is no posibility that end user can execute some code against your system.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'r_calc'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install r_calc
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
To use this gem, after including this could be minimum of required code to run calculation:
|
22
|
+
|
23
|
+
`
|
24
|
+
require 'r_calc'
|
25
|
+
#require 'supermodel'
|
26
|
+
#require 'minitest/spec'
|
27
|
+
#require 'minitest/autorun'
|
28
|
+
|
29
|
+
module RCalcHelpers
|
30
|
+
class Calculator < RCalc::FormulaProcessor
|
31
|
+
# Let's just add all of the usual operators
|
32
|
+
|
33
|
+
include ::RCalc::ParentheticalGrouping # ()
|
34
|
+
include ::RCalc::AssignmentOperator # =
|
35
|
+
include ::RCalc::LastResultOperator # $
|
36
|
+
include ::RCalc::ArithmeticOperators # +-*/%^
|
37
|
+
include ::RCalc::BooleanOperators # true false and or eq ne gt lt ge le not
|
38
|
+
include ::RCalc::FunctionOperators # sum(), max(), min()
|
39
|
+
# example of adding custom operators
|
40
|
+
|
41
|
+
def AddOperators_custom
|
42
|
+
|
43
|
+
calc = self
|
44
|
+
|
45
|
+
|
46
|
+
# Now let's show how you can use a function to access a constant, like PI
|
47
|
+
|
48
|
+
@operators << ::RCalc::BinaryOperator.new("[", 3) {|x, y| x.value + y.value}
|
49
|
+
@operators << ::RCalc::UnaryOperator.new("]", 4) { |x| x.value }
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def calculator
|
55
|
+
|
56
|
+
|
57
|
+
values = Hash.new
|
58
|
+
calc = Calculator.new do |key, val|
|
59
|
+
values[key] = val if val
|
60
|
+
values[key]
|
61
|
+
end
|
62
|
+
[calc, values]
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
`
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
|
70
|
+
1. Fork it
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'bundler'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
task default: :spec
|
9
|
+
|
10
|
+
#require 'rake/testtask'
|
11
|
+
#
|
12
|
+
#Rake::TestTask.new do |t|
|
13
|
+
# t.libs.push "lib"
|
14
|
+
# t.test_files = FileList['specs/**/*_spec.rb']
|
15
|
+
# t.verbose = true
|
16
|
+
#end
|
17
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'r_calc/binary_operator'
|
2
|
+
require 'r_calc/binary_or_unary_operator'
|
3
|
+
|
4
|
+
# Arithmetic operators + - * / % ^
|
5
|
+
module RCalc
|
6
|
+
module ArithmeticOperators
|
7
|
+
def AddOperators_Arithmetic
|
8
|
+
|
9
|
+
# Plus and minus are binary operators if they are preceeded by an operand.
|
10
|
+
# Otherwise they are unary, and have higher priority.
|
11
|
+
|
12
|
+
@operators << (plus = (BinOrUnOperator.new("+", nil) { |x, y| y ? x.value + y.value : x.value }))
|
13
|
+
@operators << (minus = (BinOrUnOperator.new("-", nil) { |x, y| y ? x.value - y.value : -x.value }))
|
14
|
+
|
15
|
+
# If a + or - has an operand preceding, the priority is lower than * and /,
|
16
|
+
# but higher than =. If no operand precedes, it is higher than * or /.
|
17
|
+
# So, we create a code block to make this determination.
|
18
|
+
|
19
|
+
pmPriority = Proc.new { |operandPreceding| operandPreceding ? 2 : 4 }
|
20
|
+
|
21
|
+
# and assign it as the priorityProc for the + and - operators
|
22
|
+
|
23
|
+
plus.priorityProc = pmPriority
|
24
|
+
minus.priorityProc = pmPriority
|
25
|
+
|
26
|
+
# Now we add some easy binary operators...
|
27
|
+
|
28
|
+
@operators << (BinaryOperator.new("*", 3) { |x, y| x.value * y.value })
|
29
|
+
@operators << (BinaryOperator.new("/", 3) { |x, y| x.value / y.value })
|
30
|
+
@operators << (BinaryOperator.new("%", 3) { |x, y| x.value % y.value })
|
31
|
+
@operators << (BinaryOperator.new("^", 3) { |x, y| x.value ** y.value })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'r_calc/function'
|
2
|
+
require 'r_calc/binary_operator'
|
3
|
+
require 'r_calc/unary_operator'
|
4
|
+
|
5
|
+
module RCalc
|
6
|
+
module BooleanOperators
|
7
|
+
def AddOperators_Boolean
|
8
|
+
|
9
|
+
# Functions for true and false
|
10
|
+
|
11
|
+
@operators << ::RCalc::Function.new("true", 0, 0) { true }
|
12
|
+
@operators << ::RCalc::Function.new("false", 0, 0) { false }
|
13
|
+
|
14
|
+
# Equality and its clan (note we cannot use '==' or other two-character
|
15
|
+
# non-word operators, because of the way we parse the string. Non-word
|
16
|
+
# characters come in one at a time.
|
17
|
+
|
18
|
+
@operators << ::RCalc::BinaryOperator.new("eq", 3) { |x, y| x.value == y.value }
|
19
|
+
@operators << ::RCalc::BinaryOperator.new("ne", 3) { |x, y| x.value != y.value }
|
20
|
+
@operators << ::RCalc::BinaryOperator.new("gt", 3) { |x, y| x.value > y.value }
|
21
|
+
@operators << ::RCalc::BinaryOperator.new("lt", 3) { |x, y| x.value < y.value }
|
22
|
+
@operators << ::RCalc::BinaryOperator.new("ge", 3) { |x, y| x.value >= y.value }
|
23
|
+
@operators << ::RCalc::BinaryOperator.new("le", 3) { |x, y| x.value <= y.value }
|
24
|
+
|
25
|
+
# Boolean and/or
|
26
|
+
|
27
|
+
@operators << ::RCalc::BinaryOperator.new("and", 2) { |x, y| x.value && y.value }
|
28
|
+
@operators << ::RCalc::BinaryOperator.new("or", 2) { |x, y| x.value || y.value }
|
29
|
+
|
30
|
+
# Logical not
|
31
|
+
|
32
|
+
@operators << ::RCalc::UnaryOperator.new("not", 4) { |x| !x.value }
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'r_calc/parse_token'
|
2
|
+
require 'r_calc/operator'
|
3
|
+
require 'r_calc/unary_operator'
|
4
|
+
require 'r_calc/binary_operator'
|
5
|
+
require 'r_calc/binary_or_unary_operator'
|
6
|
+
require 'r_calc/function'
|
7
|
+
require 'r_calc/pusher'
|
8
|
+
require 'r_calc/popper'
|
9
|
+
require 'r_calc/separator'
|
10
|
+
require 'r_calc/identifier'
|
11
|
+
require 'r_calc/literal'
|
12
|
+
require 'r_calc/operator_list'
|
13
|
+
require 'r_calc/parse_token'
|
14
|
+
|
15
|
+
|
16
|
+
module RCalc
|
17
|
+
# An expression processor. This class can be derived to process many types of
|
18
|
+
# expressions. The modules defined below can be included in a derived class to
|
19
|
+
# support popular sets of operations, or you can define your own.
|
20
|
+
# The code block passed to the new method for this class is used for evaluating
|
21
|
+
# identifiers in the expression. This allows the client code to supply/store
|
22
|
+
# variables in any way it wishes.
|
23
|
+
class FormulaProcessor
|
24
|
+
attr_reader :operators, :last_result, :receipt_components
|
25
|
+
private
|
26
|
+
def initialize(&proc)
|
27
|
+
@proc = proc # code block for accessing values for identifiers
|
28
|
+
@operators = OperatorList.new # hash for operators
|
29
|
+
@operators << Separator.new(',') # comma separates by default
|
30
|
+
@curr = nil # current operand stack
|
31
|
+
@currop = nil # current operator
|
32
|
+
@opstack = nil # stack of operators
|
33
|
+
@last_result = nil # result of last expression
|
34
|
+
@receipt_components =[] # the list of local identifiers which was used to store
|
35
|
+
# values carried to next calculation
|
36
|
+
|
37
|
+
# Here's a nifty little trick. We'll inspect our methods and call any method
|
38
|
+
# whose name starts with "AddOperator_". That allows a derived class to include
|
39
|
+
# any of the operator modules defined below, and automatically get support
|
40
|
+
# for the operators included in each module, without having to invoke the
|
41
|
+
# methods explicitly.
|
42
|
+
|
43
|
+
methods.each do |name|
|
44
|
+
self.send(name) if (name =~ /^AddOperators_/)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Resolve the expression composed of the current operator (@currop) and the
|
49
|
+
# current operand stack (@curr)
|
50
|
+
|
51
|
+
def resolve
|
52
|
+
if (@currop) then # Do we have an operator?
|
53
|
+
args = @curr[1..-1] # omit the "pusher" (curr[0] is always a Pusher)
|
54
|
+
|
55
|
+
# check argument count requirements
|
56
|
+
|
57
|
+
if (@currop.minarg && (args.length < @currop.minarg))
|
58
|
+
raise(FormulaExpressionException, "Not enough arguments for operation '#{@currop.symbol}'\nMinimum: #{@currop.minarg}, encountered #{args.length}")
|
59
|
+
end
|
60
|
+
if (@currop.maxarg && (args.length > @currop.maxarg))
|
61
|
+
raise(FormulaExpressionException, "Too many arguments for operation '#{@currop.symbol}'\nMaximum: #{@currop.maxarg}, encountered #{args.length}")
|
62
|
+
end
|
63
|
+
|
64
|
+
# Call the operator's code block and store the result as element 1, trimming the other operands
|
65
|
+
|
66
|
+
@curr[1..-1] = Literal.new(@currop.proc.call(*args))
|
67
|
+
@currop = nil # No more current operator
|
68
|
+
end
|
69
|
+
if (@curr[0].implied) # pop off any implied Pusher
|
70
|
+
val = @curr[1] # grab the value
|
71
|
+
@curr = @curr[0].parent # pop back up to the parent operand stack
|
72
|
+
@curr[-1] = val # store the value over the reference to the stack we just popped
|
73
|
+
@currop = @opstack.pop # pop off the operator from the previous level
|
74
|
+
end
|
75
|
+
return @curr[-1] # In either case, return the last value on the current operand stack
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
public
|
80
|
+
# Evaluate an expression
|
81
|
+
def eval(input=$_) # default to current string if not passed
|
82
|
+
stack = [Pusher.new(nil)] # operand array (a stack of operands and/or operand stacks)
|
83
|
+
@opstack = [] # operator stack
|
84
|
+
@curr = stack # current operand stack
|
85
|
+
@currop = nil # operator at current stack level
|
86
|
+
|
87
|
+
# Tokenize as numeric or identifier or non-space/comma (presumably an operator or parentheses)
|
88
|
+
input.scan(/[0-9.]+|\w+|,|[^\s\t\r\n\f]/) do |token|
|
89
|
+
t = nil
|
90
|
+
if (token =~ /^[0-9]+$/) then # integer literal
|
91
|
+
t = Literal.new(token.to_i)
|
92
|
+
elsif (token =~ /^[0-9.]+$/) then # floating point literal
|
93
|
+
t = Literal.new(token.to_f)
|
94
|
+
elsif (t = @operators[token]) then # a defined operator
|
95
|
+
elsif (token =~ /^\w+$/) # an identifier
|
96
|
+
t = Identifier.new(token, @proc)
|
97
|
+
receipt_components << token unless receipt_components.include? token
|
98
|
+
else
|
99
|
+
raise(FormulaExpressionException, "Unrecognized character in expression: #{token}")
|
100
|
+
end
|
101
|
+
|
102
|
+
if (t) # Did we create something?
|
103
|
+
case t.parseAction # What are we to do with it?
|
104
|
+
when ParseToken::PARSE_VALUE # Literals and identifiers
|
105
|
+
@curr.push(t) # get pushed as operands
|
106
|
+
|
107
|
+
when ParseToken::PARSE_OP # Operators and functions
|
108
|
+
t = t.clone # Must copy to establish priority independently
|
109
|
+
|
110
|
+
# Get the priority (note our only context-based syntax element, a leading operand)
|
111
|
+
prior = t.priority(opPreceding = @currop ? (@curr.length > 2) : (@curr.length > 1))
|
112
|
+
|
113
|
+
# If we already have an operator with at least equal priority, resolve it
|
114
|
+
while (@currop && (prior <= @currop.priority))
|
115
|
+
resolve
|
116
|
+
end
|
117
|
+
if (@currop) then # We found an operator of lower priority
|
118
|
+
pusher = Pusher.new(nil) # Group our new operation (implied parenthesis)
|
119
|
+
pusher.implied = true
|
120
|
+
pusher.parent = @curr # back-link the stack
|
121
|
+
if (opPreceding) then # operand preceding?
|
122
|
+
new = [pusher, @curr[-1]] # move last operand to new operation
|
123
|
+
@curr[-1] = new # link new stack to end of old one
|
124
|
+
else
|
125
|
+
new = [pusher]
|
126
|
+
@curr.push(new) # just push and go
|
127
|
+
end
|
128
|
+
@curr = new # save reference to new operand stack
|
129
|
+
@opstack.push(@currop) # push the previous operator
|
130
|
+
end
|
131
|
+
@currop = t # now we have a new operator
|
132
|
+
|
133
|
+
# Resolve the operation if we already reached the maximum argument count
|
134
|
+
resolve if (@currop.maxarg && (@curr.length > @currop.maxarg))
|
135
|
+
|
136
|
+
when ParseToken::PARSE_PUSH # Grouping (parenthesis equivalent)
|
137
|
+
t = t.clone # Clone so we have a unique parent property
|
138
|
+
t.parent = @curr # Back-link to previous operand stack
|
139
|
+
@curr.push(new = [t]) # Forward-link to new operand stack
|
140
|
+
@curr = new # Save reference to new stack
|
141
|
+
@opstack.push(@currop) # Push the current operator, if any
|
142
|
+
@currop = nil # No current operator yet within this grouping
|
143
|
+
|
144
|
+
when ParseToken::PARSE_POP # End grouping (close parenthesis)
|
145
|
+
resolve while (@curr[0].implied) # Resolve all implied pushes first
|
146
|
+
if (@currop) then # Do we have an operator?
|
147
|
+
val = resolve # Resolve the contained operation
|
148
|
+
|
149
|
+
# If we're already unwound, this is an extra one
|
150
|
+
raise(FormulaExpressionException, "Unmatched #{t.symbol}") if (@curr == stack)
|
151
|
+
@curr = @curr[0].parent # Pop back to parent stack
|
152
|
+
@curr[-1] = val # Store the value over the forward reference (losing the reference)
|
153
|
+
else # No current operator, just move the operands back
|
154
|
+
val = @curr[1..-1] # Gather the operands
|
155
|
+
@curr = @curr[0].parent # Pop back to parent stack
|
156
|
+
@curr[-1, val.length] = val # Extend the operand stack with the new operands
|
157
|
+
end
|
158
|
+
@currop = @opstack.pop # Pop back to previous operator
|
159
|
+
|
160
|
+
when ParseToken::PARSE_SEPARATE # Separator, like comma
|
161
|
+
resolve if (@currop || @curr[0].implied) # Resolve any pending operation
|
162
|
+
pusher = Pusher.new(nil) # Implied push to prevent folding operators across separator
|
163
|
+
pusher.implied = true
|
164
|
+
pusher.parent = @curr
|
165
|
+
new = [pusher]
|
166
|
+
@curr.push(new)
|
167
|
+
@curr = new
|
168
|
+
@opstack.push(@currop)
|
169
|
+
@currop = nil
|
170
|
+
end # case t.parseAction
|
171
|
+
end # if (t)
|
172
|
+
end # input.scan
|
173
|
+
|
174
|
+
# Pop off any implied pushes, resolving the values
|
175
|
+
resolve while (@curr[0].implied)
|
176
|
+
|
177
|
+
# Make sure we unwound completely
|
178
|
+
raise(FormulaExpressionException, "Unmatched #{@curr[0].symbol}") if (@curr != stack)
|
179
|
+
|
180
|
+
# Resolve any final operator, and save the value
|
181
|
+
@last_result = resolve.value
|
182
|
+
|
183
|
+
# Make sure we ended up with just one value
|
184
|
+
raise(FormulaExpressionException, "Missing operator") if (@curr.length > 2)
|
185
|
+
|
186
|
+
# Return that value
|
187
|
+
@last_result
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'r_calc/operator'
|
2
|
+
|
3
|
+
# A function has the lowest priority, because usually the arguments are
|
4
|
+
# enclosed in a push/pop sequence (e.g., parentheses)
|
5
|
+
|
6
|
+
class RCalc::Function < ::RCalc::Operator
|
7
|
+
private
|
8
|
+
def initialize(name, minarg, maxarg)
|
9
|
+
super(name, minarg, maxarg, 5)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'r_calc/function'
|
2
|
+
require 'r_calc/binary_operator'
|
3
|
+
require 'r_calc/unary_operator'
|
4
|
+
|
5
|
+
module RCalc
|
6
|
+
module FunctionOperators
|
7
|
+
def AddOperators_ArithmeticFunctions
|
8
|
+
@operators << ::RCalc::Function.new("sum", 1, nil) do |*args|
|
9
|
+
args.inject(0) { |sum, arg| sum + arg.value }
|
10
|
+
end
|
11
|
+
|
12
|
+
@operators << ::RCalc::Function.new("max", 1, nil) do |*args|
|
13
|
+
max = nil
|
14
|
+
args.each { |arg| max = arg.value if !max || arg.value > max }
|
15
|
+
max
|
16
|
+
end
|
17
|
+
|
18
|
+
@operators << ::RCalc::Function.new("min", 1, nil) do |*args|
|
19
|
+
min = nil
|
20
|
+
args.each { |arg| min = arg.value if !min || arg.value < min }
|
21
|
+
min
|
22
|
+
end
|
23
|
+
|
24
|
+
@operators << ::RCalc::Function.new("PI", 0, 0) { Math::PI }
|
25
|
+
end
|
26
|
+
|
27
|
+
def AddOperators_BooleanFunctions
|
28
|
+
@operators << ::RCalc::Function.new("if", 3, 3) do |cond, x, y|
|
29
|
+
cond.value ? x.value : (y.value || 0)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'r_calc/parse_token'
|
2
|
+
|
3
|
+
# A token that presumably references a variable of some sort
|
4
|
+
# This class demonstrates calling a code block from a saved reference to it.
|
5
|
+
|
6
|
+
class RCalc::Identifier < RCalc::ParseToken
|
7
|
+
private
|
8
|
+
def initialize(name, proc)
|
9
|
+
super(name, ::RCalc::ParseToken::PARSE_VALUE) # Note we use the name as the symbol
|
10
|
+
@proc = proc # proc is a code block for evaluating the symbol
|
11
|
+
end
|
12
|
+
|
13
|
+
public
|
14
|
+
def value=(newvalue) # Assign to value
|
15
|
+
@proc.call(self.symbol, newvalue) # We pass newvalue to denote assignment
|
16
|
+
end
|
17
|
+
|
18
|
+
def value # Get value
|
19
|
+
@proc.call(self.symbol) # No newvalue passed here
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'r_calc/formula_expression_exception'
|
2
|
+
require 'r_calc/parse_token'
|
3
|
+
# A literal value
|
4
|
+
|
5
|
+
class RCalc::Literal < RCalc::ParseToken
|
6
|
+
attr_reader :value # Just like an Identifier, we can get the
|
7
|
+
# value using the 'value' property, even
|
8
|
+
# though the implementation has nothing in common.
|
9
|
+
# The nice thing about Ruby is that these
|
10
|
+
# two classes do not need to be derived
|
11
|
+
# from an abstract parent class (less typing,
|
12
|
+
# in every sense of the word).
|
13
|
+
private
|
14
|
+
def initialize(value)
|
15
|
+
super(value, ::RCalc::ParseToken::PARSE_VALUE) # Note we use the value itself as the symbol
|
16
|
+
@value = value
|
17
|
+
end
|
18
|
+
|
19
|
+
public
|
20
|
+
# We could have left out the following method, but our version gives a better error
|
21
|
+
# messsage than the default "method missing value=".
|
22
|
+
def value=(newvalue) # Assign a value
|
23
|
+
raise(::RCalc::FormulaExpressionException, "Writing into a literal") # Not!
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'r_calc/parse_token'
|
2
|
+
|
3
|
+
|
4
|
+
=begin
|
5
|
+
An operator (or function identifier)
|
6
|
+
=end
|
7
|
+
class RCalc::Operator < RCalc::ParseToken
|
8
|
+
attr_reader :proc, :minarg, :maxarg, :priorityProc
|
9
|
+
attr_writer :priorityProc
|
10
|
+
|
11
|
+
private
|
12
|
+
def initialize(symbol, minarg, maxarg, priority, &proc)
|
13
|
+
super(symbol, RCalc::ParseToken::PARSE_OP)
|
14
|
+
@minarg = minarg # minimum number of operands
|
15
|
+
@maxarg = maxarg # maximum number of operands
|
16
|
+
@priority = priority # operator precedence (0 = low .. 5 = high)
|
17
|
+
@proc = proc # code block for performing the operation
|
18
|
+
@priorityProc = nil # procedure for determining priority
|
19
|
+
# (we need this for operators that determine
|
20
|
+
# priority based on context, like + and -
|
21
|
+
end
|
22
|
+
|
23
|
+
public
|
24
|
+
# Get priority of the operator
|
25
|
+
def priority(operandPreceding = nil)
|
26
|
+
# only determine priority once, possibly based on context
|
27
|
+
if (!(@priority) && @priorityProc) # do we have a proc for processing priority?
|
28
|
+
@priority = @priorityProc.call(operandPreceding) # call it
|
29
|
+
end
|
30
|
+
@priority # return it
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# A special Hash for the list of supported operators
|
2
|
+
# The user can append new operators, without worrying about how we need to handle them.
|
3
|
+
|
4
|
+
class RCalc::OperatorList < Hash
|
5
|
+
def <<(oper) # No need for the user to know about the keys
|
6
|
+
self[oper.symbol] = oper # We use the operator's symbol for the key, BTW
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'r_calc/pusher'
|
2
|
+
require 'r_calc/popper'
|
3
|
+
|
4
|
+
module RCalc
|
5
|
+
|
6
|
+
# Mix-ins for the usual operations. See ExpressionProcessor#initialize for how
|
7
|
+
# these "AddOperator_" methods get invoked.
|
8
|
+
|
9
|
+
# First, parenthetical grouping
|
10
|
+
module ParentheticalGrouping
|
11
|
+
def AddOperators_ParentheticalGrouping
|
12
|
+
|
13
|
+
# Define "(" and ")" as our grouping operators
|
14
|
+
|
15
|
+
@operators << ::RCalc::Pusher.new("(")
|
16
|
+
@operators << ::RCalc::Popper.new(")")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
=begin
|
2
|
+
Any token we might encounter in the expression
|
3
|
+
=end
|
4
|
+
class RCalc::ParseToken
|
5
|
+
public
|
6
|
+
# values for parseAction (what to do when we encounter this token):
|
7
|
+
PARSE_VALUE = 0 # A literal or an identifier (stack it)
|
8
|
+
PARSE_OP = 1 # An operator
|
9
|
+
PARSE_PUSH = 2 # Open parenthesis, for example
|
10
|
+
PARSE_POP = 3 # Close parenthesis, for example
|
11
|
+
PARSE_SEPARATE = 4 # Comma, for example
|
12
|
+
attr_reader :symbol, :parseAction
|
13
|
+
private
|
14
|
+
def initialize(symbol, parseAction)
|
15
|
+
@symbol = symbol # symbol used for parsing
|
16
|
+
@parseAction = parseAction # action when parsing
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'r_calc/formula_expression_exception'
|
2
|
+
require 'r_calc/parse_token'
|
3
|
+
|
4
|
+
class RCalc::Pusher < RCalc::ParseToken
|
5
|
+
attr_reader :parent, :implied # parent operand array
|
6
|
+
attr_writer :parent, :implied # implied means we created it via operator precedence
|
7
|
+
private
|
8
|
+
def initialize(symbol)
|
9
|
+
super(symbol, RCalc::ParseToken::PARSE_PUSH)
|
10
|
+
@implied = false # by default it is a literal token
|
11
|
+
end
|
12
|
+
|
13
|
+
public
|
14
|
+
def value # Tried to evaluate one of these
|
15
|
+
raise(RCalc::FormulaExpressionException, "Unrecognized expression")
|
16
|
+
end
|
17
|
+
end
|
data/lib/r_calc.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "r_calc/version"
|
2
|
+
require 'r_calc/formula_expression_exception'
|
3
|
+
require 'r_calc/formula_processor'
|
4
|
+
require 'r_calc/parenthetical_grouping'
|
5
|
+
require 'r_calc/assignment_operator'
|
6
|
+
require 'r_calc/last_result_operator'
|
7
|
+
require 'r_calc/arithmetic_operations'
|
8
|
+
require 'r_calc/boolean_operators'
|
9
|
+
require 'r_calc/function_operators'
|
10
|
+
module RCalc
|
11
|
+
# Your code goes here...
|
12
|
+
end
|
data/r_calc.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/r_calc/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Milan Jaric"]
|
6
|
+
gem.email = ["milan.jaric@gmail.com"]
|
7
|
+
gem.description = %q{Gem defines basic grammar for formula builder and ability to execute formulas to get it results or sum them.}
|
8
|
+
gem.summary = %q{Sandboxed formula builder and evaluator}
|
9
|
+
gem.homepage = "https://github.com/mjaric/formulator"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "r_calc"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = RCalc::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency "rake"
|
19
|
+
gem.add_development_dependency "rspec"
|
20
|
+
gem.add_development_dependency "ZenTest"
|
21
|
+
gem.add_development_dependency "autotest-fsevent"
|
22
|
+
gem.add_development_dependency "autotest-growl"
|
23
|
+
gem.add_development_dependency "minitest"
|
24
|
+
gem.add_development_dependency "minitest-spec"
|
25
|
+
#gem.add_development_dependency "supermodel"
|
26
|
+
end
|
data/spec/r_calc_spec.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec/spec_helper'
|
3
|
+
|
4
|
+
describe RCalc::FormulaProcessor do
|
5
|
+
|
6
|
+
RSpec.configure do |c|
|
7
|
+
c.include RCalcHelpers
|
8
|
+
end
|
9
|
+
|
10
|
+
let :c do
|
11
|
+
a, b= calculator
|
12
|
+
a
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#eval" do
|
16
|
+
it "can add two numbers" do
|
17
|
+
c.eval("2+2").should eq(4)
|
18
|
+
end
|
19
|
+
it "can multiple two numbers" do
|
20
|
+
c.eval("2*2").should eq(4)
|
21
|
+
end
|
22
|
+
it "can divide two numbers" do
|
23
|
+
c.eval("2/2").should eq(1)
|
24
|
+
end
|
25
|
+
it "can mod (%) two numbers" do
|
26
|
+
c.eval("3%2").should eq(1)
|
27
|
+
end
|
28
|
+
it "can pow (^) two numbers" do
|
29
|
+
c.eval("3^2").should eq(9)
|
30
|
+
end
|
31
|
+
it "can sum more than 2 numbers" do
|
32
|
+
c.eval("sum(1,2,3,4,5)").should eq(15)
|
33
|
+
end
|
34
|
+
it "can get minimum of number sequence" do
|
35
|
+
c.eval("min(1,2,3,4,5)").should eq(1)
|
36
|
+
end
|
37
|
+
it "can get maximum of number sequence" do
|
38
|
+
c.eval("max(1,2,3,4,5)").should eq(5)
|
39
|
+
end
|
40
|
+
it "can compare numbers" do
|
41
|
+
c.eval("4 lt 2").should eq(false)
|
42
|
+
c.eval("4 lt 4").should eq(false)
|
43
|
+
c.eval("4 lt 5").should eq(true)
|
44
|
+
|
45
|
+
c.eval("4 le 2").should eq(false)
|
46
|
+
c.eval("4 le 4").should eq(true)
|
47
|
+
c.eval("4 le 5").should eq(true)
|
48
|
+
|
49
|
+
c.eval("4 gt 2").should eq(true)
|
50
|
+
c.eval("4 gt 4").should eq(false)
|
51
|
+
c.eval("4 gt 5").should eq(false)
|
52
|
+
|
53
|
+
c.eval("4 ge 2").should eq(true)
|
54
|
+
c.eval("4 ge 4").should eq(true)
|
55
|
+
c.eval("4 ge 5").should eq(false)
|
56
|
+
|
57
|
+
c.eval("4 eq 2").should eq(false)
|
58
|
+
c.eval("4 eq 4").should eq(true)
|
59
|
+
c.eval("4 eq 5").should eq(false)
|
60
|
+
|
61
|
+
c.eval("4 ne 2").should eq(true)
|
62
|
+
c.eval("4 ne 4").should eq(false)
|
63
|
+
c.eval("4 ne 5").should eq(true)
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
it "can compare numbers using if statement with two branches" do
|
68
|
+
c.eval("if(4 lt 2, 1, -1 )").should eq(-1)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "memoizable #eval" do
|
74
|
+
it "can memoize variables into the hash" do
|
75
|
+
calc, memo = calculator
|
76
|
+
calc.eval("number=12*3").should eq(36)
|
77
|
+
memo["number"].should eq(36)
|
78
|
+
end
|
79
|
+
it "can memoize variables and it can be reused later in next step formula" do
|
80
|
+
calc, memo = calculator
|
81
|
+
calc.eval("number=12*3")
|
82
|
+
calc.eval("number * 10").should eq(360)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "can memoize last value sing $" do
|
86
|
+
calc, memo = calculator
|
87
|
+
val = calc.eval("12*3")
|
88
|
+
calc.eval("$").should eq(36)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'r_calc'
|
2
|
+
#require 'supermodel'
|
3
|
+
#require 'minitest/spec'
|
4
|
+
#require 'minitest/autorun'
|
5
|
+
|
6
|
+
module RCalcHelpers
|
7
|
+
class Calculator < RCalc::FormulaProcessor
|
8
|
+
# Let's just add all of the usual operators
|
9
|
+
|
10
|
+
include ::RCalc::ParentheticalGrouping # ()
|
11
|
+
include ::RCalc::AssignmentOperator # =
|
12
|
+
include ::RCalc::LastResultOperator # $
|
13
|
+
include ::RCalc::ArithmeticOperators # +-*/%^
|
14
|
+
include ::RCalc::BooleanOperators # true false and or eq ne gt lt ge le not
|
15
|
+
include ::RCalc::FunctionOperators # sum(), max(), min()
|
16
|
+
# example of adding custom operators
|
17
|
+
|
18
|
+
def AddOperators_custom
|
19
|
+
|
20
|
+
calc = self
|
21
|
+
|
22
|
+
|
23
|
+
# Now let's show how you can use a function to access a constant, like PI
|
24
|
+
|
25
|
+
@operators << ::RCalc::BinaryOperator.new("[", 3) {|x, y| x.value + y.value}
|
26
|
+
@operators << ::RCalc::UnaryOperator.new("]", 4) { |x| x.value }
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def calculator
|
32
|
+
|
33
|
+
|
34
|
+
values = Hash.new
|
35
|
+
calc = Calculator.new do |key, val|
|
36
|
+
values[key] = val if val
|
37
|
+
values[key]
|
38
|
+
end
|
39
|
+
[calc, values]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: r_calc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Milan Jaric
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: !binary |-
|
21
|
+
MA==
|
22
|
+
none: false
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: !binary |-
|
28
|
+
MA==
|
29
|
+
none: false
|
30
|
+
prerelease: false
|
31
|
+
type: :development
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rspec
|
34
|
+
version_requirements: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: !binary |-
|
39
|
+
MA==
|
40
|
+
none: false
|
41
|
+
requirement: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: !binary |-
|
46
|
+
MA==
|
47
|
+
none: false
|
48
|
+
prerelease: false
|
49
|
+
type: :development
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: ZenTest
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: !binary |-
|
57
|
+
MA==
|
58
|
+
none: false
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: !binary |-
|
64
|
+
MA==
|
65
|
+
none: false
|
66
|
+
prerelease: false
|
67
|
+
type: :development
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: autotest-fsevent
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: !binary |-
|
75
|
+
MA==
|
76
|
+
none: false
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: !binary |-
|
82
|
+
MA==
|
83
|
+
none: false
|
84
|
+
prerelease: false
|
85
|
+
type: :development
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: autotest-growl
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: !binary |-
|
93
|
+
MA==
|
94
|
+
none: false
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: !binary |-
|
100
|
+
MA==
|
101
|
+
none: false
|
102
|
+
prerelease: false
|
103
|
+
type: :development
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: minitest
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: !binary |-
|
111
|
+
MA==
|
112
|
+
none: false
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: !binary |-
|
118
|
+
MA==
|
119
|
+
none: false
|
120
|
+
prerelease: false
|
121
|
+
type: :development
|
122
|
+
- !ruby/object:Gem::Dependency
|
123
|
+
name: minitest-spec
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: !binary |-
|
129
|
+
MA==
|
130
|
+
none: false
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: !binary |-
|
136
|
+
MA==
|
137
|
+
none: false
|
138
|
+
prerelease: false
|
139
|
+
type: :development
|
140
|
+
description: Gem defines basic grammar for formula builder and ability to execute formulas to get it results or sum them.
|
141
|
+
email:
|
142
|
+
- milan.jaric@gmail.com
|
143
|
+
executables: []
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".autotest"
|
148
|
+
- ".gitignore"
|
149
|
+
- ".rspec"
|
150
|
+
- Gemfile
|
151
|
+
- LICENSE
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- lib/r_calc.rb
|
155
|
+
- lib/r_calc/arithmetic_operations.rb
|
156
|
+
- lib/r_calc/assignment_operator.rb
|
157
|
+
- lib/r_calc/binary_operator.rb
|
158
|
+
- lib/r_calc/binary_or_unary_operator.rb
|
159
|
+
- lib/r_calc/boolean_operators.rb
|
160
|
+
- lib/r_calc/formula_expression_exception.rb
|
161
|
+
- lib/r_calc/formula_processor.rb
|
162
|
+
- lib/r_calc/function.rb
|
163
|
+
- lib/r_calc/function_operators.rb
|
164
|
+
- lib/r_calc/identifier.rb
|
165
|
+
- lib/r_calc/last_result_operator.rb
|
166
|
+
- lib/r_calc/literal.rb
|
167
|
+
- lib/r_calc/operator.rb
|
168
|
+
- lib/r_calc/operator_list.rb
|
169
|
+
- lib/r_calc/parenthetical_grouping.rb
|
170
|
+
- lib/r_calc/parse_token.rb
|
171
|
+
- lib/r_calc/popper.rb
|
172
|
+
- lib/r_calc/pusher.rb
|
173
|
+
- lib/r_calc/separator.rb
|
174
|
+
- lib/r_calc/unary_operator.rb
|
175
|
+
- lib/r_calc/version.rb
|
176
|
+
- r_calc.gemspec
|
177
|
+
- spec/r_calc_spec.rb
|
178
|
+
- spec/spec_helper.rb
|
179
|
+
homepage: https://github.com/mjaric/formulator
|
180
|
+
licenses: []
|
181
|
+
post_install_message:
|
182
|
+
rdoc_options: []
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
segments:
|
190
|
+
- 0
|
191
|
+
hash: 2
|
192
|
+
version: !binary |-
|
193
|
+
MA==
|
194
|
+
none: false
|
195
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
segments:
|
200
|
+
- 0
|
201
|
+
hash: 2
|
202
|
+
version: !binary |-
|
203
|
+
MA==
|
204
|
+
none: false
|
205
|
+
requirements: []
|
206
|
+
rubyforge_project:
|
207
|
+
rubygems_version: 1.8.25
|
208
|
+
signing_key:
|
209
|
+
specification_version: 3
|
210
|
+
summary: Sandboxed formula builder and evaluator
|
211
|
+
test_files:
|
212
|
+
- spec/r_calc_spec.rb
|
213
|
+
- spec/spec_helper.rb
|