eqn 1.6.2 → 1.6.3
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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/eqn.gemspec +1 -1
- data/lib/eqn.rb +4 -1
- data/lib/eqn/calculator.rb +9 -6
- data/lib/eqn/comparation.rb +5 -3
- data/lib/eqn/eqn_node.rb +19 -0
- data/lib/eqn/expression.rb +33 -24
- data/lib/eqn/function.rb +21 -16
- data/lib/eqn/number.rb +21 -19
- data/lib/eqn/parser.rb +6 -12
- data/lib/eqn/terminal.rb +15 -10
- data/lib/eqn/version.rb +1 -1
- metadata +3 -3
- data/lib/eqn/engine.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fce3040108d20ba8a47b915c6878c541f0c845c3
|
4
|
+
data.tar.gz: b612f8b6d206e04a545c1577f8032ace2c6f4b43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9fdd51eb3ffd6b8acee105627279a70c886da6cce6bc1d4813ef78de5143aa81b8748d974230cfb74bba279f0b7e8e460ede46f00b4ce8e8acb8a43c98f5162
|
7
|
+
data.tar.gz: 8e862df9a84a75763e52cebeca227c7bd5221fbd8daaff4b70c5f5b607db663958cd883ff60ac0775f7cffbd7dfec076e184966608f7d84e2a493a7e3844a6ad
|
data/README.md
CHANGED
@@ -128,7 +128,7 @@ Rounds the number down (i.e. floor function). Optionally, pass a number of decim
|
|
128
128
|
|
129
129
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
130
130
|
|
131
|
-
The gem uses rspec for testing
|
131
|
+
The gem uses rspec for testing and appraisal to test against multiple versions of treetop (the only runtime dependency). From the command line, run `bundle exec appraisal install` to install gems and `bundle exec appraisal rspec` to run the complete test suite. (You can still run `bundle exec rspec` to only test against the latest version of treetop while developing.)
|
132
132
|
|
133
133
|
## Authorship
|
134
134
|
|
@@ -136,7 +136,7 @@ Written by Zach Schneider for [Aha!, the world's #1 product roadmap software](ht
|
|
136
136
|
|
137
137
|
## Contributing
|
138
138
|
|
139
|
-
1. Fork it (
|
139
|
+
1. Fork it ([https://github.com/schneidmaster/eqn/fork](https://github.com/schneidmaster/eqn/fork))
|
140
140
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
141
141
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
142
142
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/eqn.gemspec
CHANGED
@@ -4,7 +4,7 @@ lib = File.expand_path('../lib', __FILE__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'eqn/version'
|
6
6
|
|
7
|
-
config_files = %w
|
7
|
+
config_files = %w(.rspec .rubocop.yml .ruby-version Appraisals circle.yml)
|
8
8
|
|
9
9
|
Gem::Specification.new do |spec|
|
10
10
|
spec.name = 'eqn'
|
data/lib/eqn.rb
CHANGED
@@ -3,5 +3,8 @@ require 'treetop'
|
|
3
3
|
# Load the parser from the Treetop grammar.
|
4
4
|
Treetop.load(File.join(__dir__, 'eqn.treetop'))
|
5
5
|
|
6
|
-
# Load eqn
|
6
|
+
# Load custom eqn syntax node.
|
7
|
+
require File.join(__dir__, 'eqn', 'eqn_node')
|
8
|
+
|
9
|
+
# Load other eqn classes.
|
7
10
|
Dir.glob File.join(__dir__, 'eqn', '**', '*.rb'), &method(:require)
|
data/lib/eqn/calculator.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module Eqn
|
2
|
+
# Primary calculator class used for performing calculations or creating eqn instances.
|
2
3
|
class Calculator
|
3
4
|
def initialize(eqn, vars = {})
|
4
5
|
@eqn = eqn
|
@@ -9,16 +10,16 @@ module Eqn
|
|
9
10
|
if key_or_hash.is_a?(Hash)
|
10
11
|
@vars.merge!(key_or_hash)
|
11
12
|
else
|
12
|
-
@vars[key_or_hash.
|
13
|
+
@vars[key_or_hash.to_sym] = value
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
def method_missing(method, *args)
|
17
|
-
if %i
|
17
|
+
def method_missing(method, *args) # rubocop:disable AbcSize
|
18
|
+
if %i(calculate calc valid?).include?(method)
|
18
19
|
self.class.send(method, @eqn, @vars)
|
19
20
|
elsif (match = method.to_s.match(/^[A-Za-z]+=$/))
|
20
|
-
@vars[match.to_s.delete('=').
|
21
|
-
elsif (var = method.to_s.match(/^[A-Za-z]+$/).to_s.
|
21
|
+
@vars[match.to_s.delete('=').to_sym] = args.first
|
22
|
+
elsif (var = method.to_s.match(/^[A-Za-z]+$/).to_s.to_sym) && @vars.key?(var)
|
22
23
|
@vars[var]
|
23
24
|
else
|
24
25
|
super
|
@@ -26,7 +27,9 @@ module Eqn
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def respond_to_missing?(method, _p2 = false)
|
29
|
-
%i
|
30
|
+
%i(calculate calc valid?).include?(method) ||
|
31
|
+
method.to_s.match(/^[A-Za-z]+=$/) ||
|
32
|
+
(var = method.to_s.match(/^[A-Za-z]+$/).to_s.to_sym) && @vars.key?(var)
|
30
33
|
end
|
31
34
|
|
32
35
|
class << self
|
data/lib/eqn/comparation.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
module Eqn
|
2
|
-
class
|
2
|
+
# Node class for a comparison between expressions.
|
3
|
+
class Comparation < EqnNode
|
3
4
|
def value(vars = {})
|
4
5
|
val_one = elements.shift.value(vars)
|
5
|
-
if
|
6
|
+
if term?
|
6
7
|
val_one
|
7
8
|
else
|
8
9
|
val_one.send(*elements.shift.value(vars))
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
|
-
class
|
13
|
+
# Node class for the operator and expression being compared.
|
14
|
+
class CompGroup < EqnNode
|
13
15
|
def value(vars = {})
|
14
16
|
[elements.shift.value(vars), elements.shift.value(vars)]
|
15
17
|
end
|
data/lib/eqn/eqn_node.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Eqn
|
2
|
+
# Eqn parser node with helper methods for cleaning.
|
3
|
+
class EqnNode < Treetop::Runtime::SyntaxNode
|
4
|
+
def term?
|
5
|
+
elements.nil? || elements.empty?
|
6
|
+
end
|
7
|
+
|
8
|
+
def clean_tree!
|
9
|
+
# Return if node is a terminal.
|
10
|
+
return if term?
|
11
|
+
|
12
|
+
# Delete any cruft syntax nodes.
|
13
|
+
elements.delete_if { |node| !node.is_a?(EqnNode) }
|
14
|
+
|
15
|
+
# Recurse over any elements with their own children.
|
16
|
+
elements.each(&:clean_tree!)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/eqn/expression.rb
CHANGED
@@ -1,38 +1,47 @@
|
|
1
1
|
module Eqn
|
2
|
-
class
|
2
|
+
# Node class for an expression.
|
3
|
+
class Expression < EqnNode
|
3
4
|
def left_associative?
|
4
5
|
elements.any? && elements.last.left_associative?
|
5
6
|
end
|
6
7
|
|
7
|
-
def
|
8
|
-
elements.
|
8
|
+
def value(vars = {})
|
9
|
+
base = elements.shift.value(vars)
|
10
|
+
|
11
|
+
# Aggressively consume left associative operators to maintain associativity.
|
12
|
+
base = consume_while_left_associative(base, vars)
|
13
|
+
|
14
|
+
# Apply next right-associative operator (if any) or return.
|
15
|
+
apply_next_operator(base, vars)
|
9
16
|
end
|
10
17
|
|
11
|
-
|
12
|
-
|
13
|
-
|
18
|
+
private
|
19
|
+
|
20
|
+
def consume_while_left_associative(base, vars)
|
21
|
+
return base unless left_associative?
|
22
|
+
base = apply_next_operator(base, vars)
|
23
|
+
consume_while_left_associative(base, vars)
|
24
|
+
end
|
25
|
+
|
26
|
+
def apply_next_operator(base, vars)
|
27
|
+
return base if term?
|
28
|
+
left_associative = elements.last.left_associative?
|
29
|
+
op, num_expr = elements.shift.value(vars)
|
30
|
+
if left_associative
|
31
|
+
apply_left_associative(base, vars, op, num_expr)
|
14
32
|
else
|
15
|
-
base
|
16
|
-
|
17
|
-
# Aggressively consume left associative operators to maintain associativity.
|
18
|
-
while left_associative?
|
19
|
-
op, num_expr = elements.shift.value(vars)
|
20
|
-
num_expr_operand = num_expr.elements.shift
|
21
|
-
base = base.send(op, num_expr_operand.value(vars))
|
22
|
-
elements.push num_expr.elements.shift unless num_expr.term?
|
23
|
-
end
|
24
|
-
|
25
|
-
# Apply next right-associative operator (if any) or return.
|
26
|
-
if term?
|
27
|
-
base
|
28
|
-
else
|
29
|
-
op, num_expr = elements.shift.value(vars)
|
30
|
-
base.send(op, num_expr.value(vars))
|
31
|
-
end
|
33
|
+
base.send(op, num_expr.value(vars))
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
35
|
-
|
37
|
+
def apply_left_associative(base, vars, op, num_expr)
|
38
|
+
num_expr_operand = num_expr.elements.shift
|
39
|
+
elements.push(num_expr.elements.shift) unless num_expr.term?
|
40
|
+
base.send(op, num_expr_operand.value(vars))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Node class for the operator and latter component of an expression.
|
44
|
+
class ExprGroup < EqnNode
|
36
45
|
def left_associative?
|
37
46
|
elements.first.left_associative?
|
38
47
|
end
|
data/lib/eqn/function.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Eqn
|
2
2
|
module Function
|
3
|
-
class
|
3
|
+
# Node class for the if function.
|
4
|
+
class If < EqnNode
|
4
5
|
def value(vars = {})
|
5
6
|
comp_val = elements.shift.value(vars)
|
6
7
|
ls = elements.shift.value(vars)
|
@@ -10,35 +11,39 @@ module Eqn
|
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
|
-
class
|
14
|
-
|
14
|
+
# Base node class for round functions.
|
15
|
+
class RoundBase < EqnNode
|
16
|
+
def value(vars)
|
15
17
|
value = elements.shift.value(vars)
|
16
18
|
raise ZeroDivisionError if value.is_a?(Float) && (value.abs == Float::INFINITY || value.nan?)
|
17
|
-
if
|
18
|
-
value.send(
|
19
|
+
if term?
|
20
|
+
value.send(self.class::ROUND_METHOD)
|
19
21
|
else
|
20
|
-
|
21
|
-
(value * 10**decimals).send(fn).to_f / 10**decimals
|
22
|
+
round_to_precision(vars, value)
|
22
23
|
end
|
23
24
|
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def round_to_precision(vars, value)
|
29
|
+
decimals = elements.shift.value(vars)
|
30
|
+
(value * 10**decimals).send(self.class::ROUND_METHOD).to_f / 10**decimals
|
31
|
+
end
|
24
32
|
end
|
25
33
|
|
34
|
+
# Node class for the round function.
|
26
35
|
class Round < RoundBase
|
27
|
-
|
28
|
-
super(:round, vars)
|
29
|
-
end
|
36
|
+
ROUND_METHOD = :round
|
30
37
|
end
|
31
38
|
|
39
|
+
# Node class for the roundup function.
|
32
40
|
class RoundUp < RoundBase
|
33
|
-
|
34
|
-
super(:ceil, vars)
|
35
|
-
end
|
41
|
+
ROUND_METHOD = :ceil
|
36
42
|
end
|
37
43
|
|
44
|
+
# Node class for the rounddown function.
|
38
45
|
class RoundDown < RoundBase
|
39
|
-
|
40
|
-
super(:floor, vars)
|
41
|
-
end
|
46
|
+
ROUND_METHOD = :floor
|
42
47
|
end
|
43
48
|
end
|
44
49
|
end
|
data/lib/eqn/number.rb
CHANGED
@@ -1,16 +1,27 @@
|
|
1
1
|
module Eqn
|
2
|
-
class
|
2
|
+
# Node class for a simple number.
|
3
|
+
class Number < EqnNode
|
3
4
|
def value(vars = {})
|
4
5
|
base = elements.shift.value(vars)
|
5
|
-
|
6
|
-
base
|
7
|
-
|
6
|
+
|
7
|
+
return base if term?
|
8
|
+
|
9
|
+
if instance_of?(Float)
|
10
|
+
# Apply any decimal if a float.
|
11
|
+
base + elements.shift.value(vars)
|
12
|
+
else
|
13
|
+
# Apply any exponent if a simple number.
|
14
|
+
base * elements.shift.value(vars)
|
15
|
+
end
|
8
16
|
end
|
9
17
|
|
10
|
-
class
|
18
|
+
class Float < Number; end
|
19
|
+
|
20
|
+
# Node class for a signed number.
|
21
|
+
class SignedNumber < EqnNode
|
11
22
|
def value(vars = {})
|
12
23
|
# Store sign if any.
|
13
|
-
sign_negative = elements.shift.negative? if elements.first.is_a?
|
24
|
+
sign_negative = elements.shift.negative? if elements.first.is_a?(Terminal::Sign)
|
14
25
|
|
15
26
|
# Evaluate float.
|
16
27
|
value = elements.shift.value(vars)
|
@@ -20,24 +31,15 @@ module Eqn
|
|
20
31
|
end
|
21
32
|
end
|
22
33
|
|
23
|
-
class
|
24
|
-
|
25
|
-
base = elements.shift.value(vars)
|
26
|
-
|
27
|
-
# Add any decimal.
|
28
|
-
base += elements.shift.value(vars) unless elements.empty?
|
29
|
-
|
30
|
-
base
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class Decimal < Treetop::Runtime::SyntaxNode
|
34
|
+
# Node class for the decimal part of a non-integer.
|
35
|
+
class Decimal < EqnNode
|
35
36
|
def value(_vars = {})
|
36
37
|
elements.shift.dec_value
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
class
|
41
|
+
# Node class for the exponent part of a number.
|
42
|
+
class Exponent < EqnNode
|
41
43
|
def value(vars = {})
|
42
44
|
10**elements.shift.value(vars)
|
43
45
|
end
|
data/lib/eqn/parser.rb
CHANGED
@@ -1,25 +1,19 @@
|
|
1
1
|
module Eqn
|
2
|
+
# Primary parser class to convert a string equation to a tree of nodes.
|
2
3
|
class Parser
|
3
4
|
class << self
|
4
|
-
def parse(
|
5
|
+
def parse(equation)
|
5
6
|
parser = EqnParser.new
|
6
7
|
|
7
|
-
# Pass the
|
8
|
-
|
8
|
+
# Pass the equation over to the parser instance.
|
9
|
+
root_node = parser.parse(equation)
|
9
10
|
|
10
11
|
# Raise any errors.
|
11
|
-
raise ParseError, "Parse error at offset: #{parser.index} -- #{parser.failure_reason}" if
|
12
|
+
raise ParseError, "Parse error at offset: #{parser.index} -- #{parser.failure_reason}" if root_node.nil?
|
12
13
|
|
13
14
|
# Remove extraneous nodes and return tree.
|
14
|
-
clean_tree
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
15
|
+
root_node.clean_tree!
|
18
16
|
|
19
|
-
def clean_tree(root_node)
|
20
|
-
return if root_node.elements.nil?
|
21
|
-
root_node.elements.delete_if { |node| node.class == Treetop::Runtime::SyntaxNode }
|
22
|
-
root_node.elements.each(&method(:clean_tree))
|
23
17
|
root_node
|
24
18
|
end
|
25
19
|
end
|
data/lib/eqn/terminal.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
module Eqn
|
2
2
|
module Terminal
|
3
|
-
class
|
3
|
+
# Node class for a variable.
|
4
|
+
class Variable < EqnNode
|
4
5
|
def value(vars = {})
|
5
|
-
val = vars[text_value.
|
6
|
+
val = vars[text_value.to_sym]
|
6
7
|
raise NoVariableValueError, "No value given for: #{text_value}" unless val
|
7
|
-
raise NonNumericVariableError, "Variable #{text_value} value is nonnumeric: #{val}" unless val.is_a?
|
8
|
+
raise NonNumericVariableError, "Variable #{text_value} value is nonnumeric: #{val}" unless val.is_a?(Numeric)
|
8
9
|
val
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
|
-
class
|
13
|
+
# Node class for a group of numbers.
|
14
|
+
class Digits < EqnNode
|
13
15
|
def dec_value
|
14
16
|
".#{text_value}".to_f
|
15
17
|
end
|
@@ -19,28 +21,31 @@ module Eqn
|
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
class
|
24
|
+
# Node class for a number sign.
|
25
|
+
class Sign < EqnNode
|
23
26
|
def negative?
|
24
27
|
text_value == '-'
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
28
|
-
class
|
31
|
+
# Node class for an operator.
|
32
|
+
class Op < EqnNode
|
29
33
|
def left_associative?
|
30
|
-
is_a?
|
34
|
+
is_a?(LeftAssociativeOp)
|
31
35
|
end
|
32
36
|
|
33
37
|
def value(_vars = {})
|
34
|
-
text_value == '^' ? :** : text_value.
|
38
|
+
text_value == '^' ? :** : text_value.to_sym
|
35
39
|
end
|
36
40
|
|
37
41
|
class LeftAssociativeOp < Op; end
|
38
42
|
class RightAssociativeOp < Op; end
|
39
43
|
end
|
40
44
|
|
41
|
-
class
|
45
|
+
# Node class for a comparation operator.
|
46
|
+
class CompOp < EqnNode
|
42
47
|
def value(_vars = {})
|
43
|
-
text_value == '=' ? :== : text_value.
|
48
|
+
text_value == '=' ? :== : text_value.to_sym
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end
|
data/lib/eqn/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eqn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.6.
|
4
|
+
version: 1.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Schneider
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: treetop
|
@@ -142,7 +142,7 @@ files:
|
|
142
142
|
- lib/eqn.treetop
|
143
143
|
- lib/eqn/calculator.rb
|
144
144
|
- lib/eqn/comparation.rb
|
145
|
-
- lib/eqn/
|
145
|
+
- lib/eqn/eqn_node.rb
|
146
146
|
- lib/eqn/errors.rb
|
147
147
|
- lib/eqn/expression.rb
|
148
148
|
- lib/eqn/function.rb
|