dentaku 2.0.3 → 2.0.4
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/.travis.yml +0 -1
- data/CHANGELOG.md +6 -0
- data/lib/dentaku.rb +13 -0
- data/lib/dentaku/ast/arithmetic.rb +25 -14
- data/lib/dentaku/ast/identifier.rb +8 -2
- data/lib/dentaku/bulk_expression_solver.rb +28 -10
- data/lib/dentaku/calculator.rb +8 -3
- data/lib/dentaku/exceptions.rb +1 -0
- data/lib/dentaku/token_scanner.rb +26 -14
- data/lib/dentaku/version.rb +1 -1
- data/spec/bulk_expression_solver_spec.rb +9 -8
- data/spec/calculator_spec.rb +24 -3
- data/spec/exceptions_spec.rb +9 -0
- data/spec/token_scanner_spec.rb +12 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 568297af6c05c99fdd87b6a2d70cd78b4d1f5828
|
4
|
+
data.tar.gz: 9e7668dd3998237a5fe6aaf50370dadfb6941bee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51685ec73f241afaed1bf66961b55837792100a0ecf06c2d5b283eca227fdb3129c76045fe676b8416fa40d54e11d1ef0da8ef298c504faeace2f77caf168955
|
7
|
+
data.tar.gz: a9a6914b90950b7616e3c82c704ab58921d0107045a3f440f5de2848acbf056ee95fefd8f2f63b663cb33f71f72f3733d94f13b7dd1f0895c1285f5bcab9181c
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## [v2.0.4] 2015-09-03
|
4
|
+
- fix BigDecimal conversion bug
|
5
|
+
- add caching for bulk expression solving dependency order
|
6
|
+
- allow for custom configuration for token scanners
|
7
|
+
|
3
8
|
## [v2.0.3] 2015-08-25
|
4
9
|
- bug fixes
|
5
10
|
- performance enhancements
|
@@ -80,6 +85,7 @@
|
|
80
85
|
## [v0.1.0] 2012-01-20
|
81
86
|
- initial release
|
82
87
|
|
88
|
+
[v2.0.4]: https://github.com/rubysolo/dentaku/compare/v2.0.3...v2.0.4
|
83
89
|
[v2.0.3]: https://github.com/rubysolo/dentaku/compare/v2.0.1...v2.0.3
|
84
90
|
[v2.0.1]: https://github.com/rubysolo/dentaku/compare/v2.0.0...v2.0.1
|
85
91
|
[v2.0.0]: https://github.com/rubysolo/dentaku/compare/v1.2.6...v2.0.0
|
data/lib/dentaku.rb
CHANGED
@@ -7,6 +7,11 @@ module Dentaku
|
|
7
7
|
calculator.evaluate(expression, data)
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.enable_caching!
|
11
|
+
enable_ast_cache!
|
12
|
+
enable_dependency_order_cache!
|
13
|
+
end
|
14
|
+
|
10
15
|
def self.enable_ast_cache!
|
11
16
|
@enable_ast_caching = true
|
12
17
|
end
|
@@ -15,6 +20,14 @@ module Dentaku
|
|
15
20
|
@enable_ast_caching
|
16
21
|
end
|
17
22
|
|
23
|
+
def self.enable_dependency_order_cache!
|
24
|
+
@enable_dependency_order_caching = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.cache_dependency_order?
|
28
|
+
@enable_dependency_order_caching
|
29
|
+
end
|
30
|
+
|
18
31
|
private
|
19
32
|
|
20
33
|
def self.calculator
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative './operation'
|
2
2
|
require 'bigdecimal'
|
3
|
+
require 'bigdecimal/util'
|
3
4
|
|
4
5
|
module Dentaku
|
5
6
|
module AST
|
@@ -13,16 +14,28 @@ module Dentaku
|
|
13
14
|
:numeric
|
14
15
|
end
|
15
16
|
|
17
|
+
def value(context={})
|
18
|
+
l = cast(left.value(context))
|
19
|
+
r = cast(right.value(context))
|
20
|
+
l.public_send(operator, r)
|
21
|
+
end
|
22
|
+
|
16
23
|
private
|
17
24
|
|
25
|
+
def cast(value, prefer_integer=true)
|
26
|
+
v = BigDecimal.new(value, Float::DIG+1)
|
27
|
+
v = v.to_i if prefer_integer && v.frac.zero?
|
28
|
+
v
|
29
|
+
end
|
30
|
+
|
18
31
|
def valid_node?(node)
|
19
32
|
node.is_a?(Identifier) || node.type == :numeric
|
20
33
|
end
|
21
34
|
end
|
22
35
|
|
23
36
|
class Addition < Arithmetic
|
24
|
-
def
|
25
|
-
|
37
|
+
def operator
|
38
|
+
:+
|
26
39
|
end
|
27
40
|
|
28
41
|
def self.precedence
|
@@ -31,8 +44,8 @@ module Dentaku
|
|
31
44
|
end
|
32
45
|
|
33
46
|
class Subtraction < Arithmetic
|
34
|
-
def
|
35
|
-
|
47
|
+
def operator
|
48
|
+
:-
|
36
49
|
end
|
37
50
|
|
38
51
|
def self.precedence
|
@@ -41,8 +54,8 @@ module Dentaku
|
|
41
54
|
end
|
42
55
|
|
43
56
|
class Multiplication < Arithmetic
|
44
|
-
def
|
45
|
-
|
57
|
+
def operator
|
58
|
+
:*
|
46
59
|
end
|
47
60
|
|
48
61
|
def self.precedence
|
@@ -52,12 +65,10 @@ module Dentaku
|
|
52
65
|
|
53
66
|
class Division < Arithmetic
|
54
67
|
def value(context={})
|
55
|
-
r =
|
68
|
+
r = cast(right.value(context), false)
|
56
69
|
raise ZeroDivisionError if r.zero?
|
57
70
|
|
58
|
-
|
59
|
-
v = v.to_i if v.frac.zero?
|
60
|
-
v
|
71
|
+
cast(cast(left.value(context)) / r)
|
61
72
|
end
|
62
73
|
|
63
74
|
def self.precedence
|
@@ -66,8 +77,8 @@ module Dentaku
|
|
66
77
|
end
|
67
78
|
|
68
79
|
class Modulo < Arithmetic
|
69
|
-
def
|
70
|
-
|
80
|
+
def operator
|
81
|
+
:%
|
71
82
|
end
|
72
83
|
|
73
84
|
def self.precedence
|
@@ -76,8 +87,8 @@ module Dentaku
|
|
76
87
|
end
|
77
88
|
|
78
89
|
class Exponentiation < Arithmetic
|
79
|
-
def
|
80
|
-
|
90
|
+
def operator
|
91
|
+
:**
|
81
92
|
end
|
82
93
|
|
83
94
|
def self.precedence
|
@@ -13,7 +13,7 @@ module Dentaku
|
|
13
13
|
v = context[identifier]
|
14
14
|
case v
|
15
15
|
when Node
|
16
|
-
v.value
|
16
|
+
v.value(context)
|
17
17
|
when NilClass
|
18
18
|
raise UnboundVariableError.new([identifier])
|
19
19
|
else
|
@@ -22,7 +22,13 @@ module Dentaku
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def dependencies(context={})
|
25
|
-
context.has_key?(identifier) ? [] : [identifier]
|
25
|
+
context.has_key?(identifier) ? dependencies_of(context[identifier]) : [identifier]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def dependencies_of(node)
|
31
|
+
node.respond_to?(:dependencies) ? node.dependencies : []
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
@@ -6,9 +6,9 @@ require 'dentaku/tokenizer'
|
|
6
6
|
|
7
7
|
module Dentaku
|
8
8
|
class BulkExpressionSolver
|
9
|
-
def initialize(expression_hash,
|
9
|
+
def initialize(expression_hash, calculator)
|
10
10
|
self.expression_hash = expression_hash
|
11
|
-
self.calculator =
|
11
|
+
self.calculator = calculator
|
12
12
|
end
|
13
13
|
|
14
14
|
def solve!
|
@@ -26,6 +26,10 @@ module Dentaku
|
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
+
def self.dependency_cache
|
30
|
+
@dep_cache ||= {}
|
31
|
+
end
|
32
|
+
|
29
33
|
attr_accessor :expression_hash, :calculator
|
30
34
|
|
31
35
|
def return_undefined_handler
|
@@ -39,28 +43,42 @@ module Dentaku
|
|
39
43
|
def load_results(&block)
|
40
44
|
variables_in_resolve_order.each_with_object({}) do |var_name, r|
|
41
45
|
begin
|
42
|
-
r[var_name] = calculator.memory[var_name] ||
|
46
|
+
r[var_name] = calculator.memory[var_name] ||
|
47
|
+
evaluate!(expressions[var_name], expressions.merge(r))
|
43
48
|
rescue Dentaku::UnboundVariableError, ZeroDivisionError => ex
|
44
49
|
r[var_name] = block.call(ex)
|
45
50
|
end
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
49
|
-
def dependencies(expression)
|
50
|
-
Parser.new(Tokenizer.new.tokenize(expression)).parse.dependencies
|
51
|
-
end
|
52
|
-
|
53
54
|
def expressions
|
54
55
|
@expressions ||= Hash[expression_hash.map { |k,v| [k.to_s, v] }]
|
55
56
|
end
|
56
57
|
|
57
58
|
def expression_dependencies
|
58
|
-
Hash[expressions.map { |var, expr| [var, dependencies(expr)] }]
|
59
|
+
Hash[expressions.map { |var, expr| [var, calculator.dependencies(expr)] }].tap do |d|
|
60
|
+
d.values.each do |deps|
|
61
|
+
unresolved = deps.reject { |ud| d.has_key?(ud) }
|
62
|
+
unresolved.each { |u| add_dependencies(d, u) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_dependencies(current_dependencies, variable)
|
68
|
+
node = calculator.memory[variable]
|
69
|
+
if node.respond_to?(:dependencies)
|
70
|
+
current_dependencies[variable] = node.dependencies
|
71
|
+
node.dependencies.each { |d| add_dependencies(current_dependencies, d) }
|
72
|
+
end
|
59
73
|
end
|
60
74
|
|
61
75
|
def variables_in_resolve_order
|
62
|
-
|
63
|
-
|
76
|
+
cache_key = expressions.keys.map(&:to_s).sort.join("|")
|
77
|
+
@ordered_deps ||= self.class.dependency_cache.fetch(cache_key) {
|
78
|
+
DependencyResolver.find_resolve_order(expression_dependencies).tap do |d|
|
79
|
+
self.class.dependency_cache[cache_key] = d if Dentaku.cache_dependency_order?
|
80
|
+
end
|
81
|
+
}
|
64
82
|
end
|
65
83
|
|
66
84
|
def evaluate!(expression, results)
|
data/lib/dentaku/calculator.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'dentaku'
|
1
2
|
require 'dentaku/bulk_expression_solver'
|
2
3
|
require 'dentaku/exceptions'
|
3
4
|
require 'dentaku/token'
|
@@ -31,7 +32,7 @@ module Dentaku
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def evaluate!(expression, data={})
|
34
|
-
|
35
|
+
store(data) do
|
35
36
|
node = expression
|
36
37
|
node = ast(node) unless node.is_a?(AST::Node)
|
37
38
|
node.value(memory)
|
@@ -39,11 +40,11 @@ module Dentaku
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def solve!(expression_hash)
|
42
|
-
BulkExpressionSolver.new(expression_hash,
|
43
|
+
BulkExpressionSolver.new(expression_hash, self).solve!
|
43
44
|
end
|
44
45
|
|
45
46
|
def solve(expression_hash, &block)
|
46
|
-
BulkExpressionSolver.new(expression_hash,
|
47
|
+
BulkExpressionSolver.new(expression_hash, self).solve(&block)
|
47
48
|
end
|
48
49
|
|
49
50
|
def dependencies(expression)
|
@@ -79,6 +80,10 @@ module Dentaku
|
|
79
80
|
end
|
80
81
|
alias_method :bind, :store
|
81
82
|
|
83
|
+
def store_formula(key, formula)
|
84
|
+
store(key, ast(formula))
|
85
|
+
end
|
86
|
+
|
82
87
|
def clear
|
83
88
|
@memory = {}
|
84
89
|
end
|
data/lib/dentaku/exceptions.rb
CHANGED
@@ -24,23 +24,35 @@ module Dentaku
|
|
24
24
|
end
|
25
25
|
|
26
26
|
class << self
|
27
|
-
def
|
28
|
-
|
29
|
-
whitespace,
|
30
|
-
numeric,
|
31
|
-
double_quoted_string,
|
32
|
-
single_quoted_string,
|
33
|
-
negate,
|
34
|
-
operator,
|
35
|
-
grouping,
|
36
|
-
comparator,
|
37
|
-
combinator,
|
38
|
-
boolean,
|
39
|
-
function,
|
40
|
-
identifier
|
27
|
+
def available_scanners
|
28
|
+
[
|
29
|
+
:whitespace,
|
30
|
+
:numeric,
|
31
|
+
:double_quoted_string,
|
32
|
+
:single_quoted_string,
|
33
|
+
:negate,
|
34
|
+
:operator,
|
35
|
+
:grouping,
|
36
|
+
:comparator,
|
37
|
+
:combinator,
|
38
|
+
:boolean,
|
39
|
+
:function,
|
40
|
+
:identifier
|
41
41
|
]
|
42
42
|
end
|
43
43
|
|
44
|
+
def scanners=(token_scanners)
|
45
|
+
@scanners = (token_scanners & available_scanners).map { |scanner|
|
46
|
+
self.send(scanner)
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def scanners
|
51
|
+
@scanners ||= available_scanners.map { |scanner|
|
52
|
+
self.send(scanner)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
44
56
|
def whitespace
|
45
57
|
new(:whitespace, '\s+')
|
46
58
|
end
|
data/lib/dentaku/version.rb
CHANGED
@@ -2,6 +2,8 @@ require 'spec_helper'
|
|
2
2
|
require 'dentaku/bulk_expression_solver'
|
3
3
|
|
4
4
|
RSpec.describe Dentaku::BulkExpressionSolver do
|
5
|
+
let(:calculator) { Dentaku::Calculator.new }
|
6
|
+
|
5
7
|
describe "#solve!" do
|
6
8
|
it "evaluates properly with variables, even if some in memory" do
|
7
9
|
expressions = {
|
@@ -9,8 +11,7 @@ RSpec.describe Dentaku::BulkExpressionSolver do
|
|
9
11
|
weekly_apple_budget: "apples * 7",
|
10
12
|
pear: "1"
|
11
13
|
}
|
12
|
-
|
13
|
-
solver = described_class.new(expressions, memory)
|
14
|
+
solver = described_class.new(expressions, calculator.store(apples: 3))
|
14
15
|
expect(solver.solve!)
|
15
16
|
.to eq(pear: 1, weekly_apple_budget: 21, weekly_fruit_budget: 25)
|
16
17
|
end
|
@@ -18,20 +19,20 @@ RSpec.describe Dentaku::BulkExpressionSolver do
|
|
18
19
|
it "lets you know if a variable is unbound" do
|
19
20
|
expressions = {more_apples: "apples + 1"}
|
20
21
|
expect {
|
21
|
-
described_class.new(expressions,
|
22
|
+
described_class.new(expressions, calculator).solve!
|
22
23
|
}.to raise_error(Dentaku::UnboundVariableError)
|
23
24
|
end
|
24
25
|
|
25
26
|
it "lets you know if the result is a div/0 error" do
|
26
27
|
expressions = {more_apples: "1/0"}
|
27
28
|
expect {
|
28
|
-
described_class.new(expressions,
|
29
|
+
described_class.new(expressions, calculator).solve!
|
29
30
|
}.to raise_error(ZeroDivisionError)
|
30
31
|
end
|
31
32
|
|
32
33
|
it "does not require keys to be parseable" do
|
33
34
|
expressions = { "the value of x, incremented" => "x + 1" }
|
34
|
-
solver = described_class.new(expressions, "x" => 3)
|
35
|
+
solver = described_class.new(expressions, calculator.store("x" => 3))
|
35
36
|
expect(solver.solve!).to eq({ "the value of x, incremented" => 4 })
|
36
37
|
end
|
37
38
|
end
|
@@ -39,19 +40,19 @@ RSpec.describe Dentaku::BulkExpressionSolver do
|
|
39
40
|
describe "#solve" do
|
40
41
|
it "returns :undefined when variables are unbound" do
|
41
42
|
expressions = {more_apples: "apples + 1"}
|
42
|
-
expect(described_class.new(expressions,
|
43
|
+
expect(described_class.new(expressions, calculator).solve)
|
43
44
|
.to eq(more_apples: :undefined)
|
44
45
|
end
|
45
46
|
|
46
47
|
it "allows passing in a custom value to an error handler when a variable is unbound" do
|
47
48
|
expressions = {more_apples: "apples + 1"}
|
48
|
-
expect(described_class.new(expressions,
|
49
|
+
expect(described_class.new(expressions, calculator).solve { :foo })
|
49
50
|
.to eq(more_apples: :foo)
|
50
51
|
end
|
51
52
|
|
52
53
|
it "allows passing in a custom value to an error handler when there is a div/0 error" do
|
53
54
|
expressions = {more_apples: "1/0"}
|
54
|
-
expect(described_class.new(expressions,
|
55
|
+
expect(described_class.new(expressions, calculator).solve { :foo })
|
55
56
|
.to eq(more_apples: :foo)
|
56
57
|
end
|
57
58
|
end
|
data/spec/calculator_spec.rb
CHANGED
@@ -29,6 +29,8 @@ describe Dentaku::Calculator do
|
|
29
29
|
expect(calculator.evaluate('3 + 0 / -3')).to eq(3)
|
30
30
|
expect(calculator.evaluate('15 % 8')).to eq(7)
|
31
31
|
expect(calculator.evaluate('(((695759/735000)^(1/(1981-1991)))-1)*1000').round(4)).to eq(5.5018)
|
32
|
+
expect(calculator.evaluate('0.253/0.253')).to eq(1)
|
33
|
+
expect(calculator.evaluate('0.253/d', d: 0.253)).to eq(1)
|
32
34
|
end
|
33
35
|
|
34
36
|
describe 'memory' do
|
@@ -51,6 +53,11 @@ describe Dentaku::Calculator do
|
|
51
53
|
expect(calculator.evaluate!('first')).to eq 1
|
52
54
|
expect(calculator.evaluate!('second')).to eq 2
|
53
55
|
end
|
56
|
+
|
57
|
+
it 'stores formulas' do
|
58
|
+
calculator.store_formula('area', 'length * width')
|
59
|
+
expect(calculator.evaluate!('area', length: 5, width: 5)).to eq 25
|
60
|
+
end
|
54
61
|
end
|
55
62
|
|
56
63
|
describe 'dependencies' do
|
@@ -68,15 +75,15 @@ describe Dentaku::Calculator do
|
|
68
75
|
expect(with_memory.solve!(
|
69
76
|
weekly_fruit_budget: "weekly_apple_budget + pear * 4",
|
70
77
|
weekly_apple_budget: "apples * 7",
|
71
|
-
pear:
|
78
|
+
pear: "1"
|
72
79
|
)).to eq(pear: 1, weekly_apple_budget: 21, weekly_fruit_budget: 25)
|
73
80
|
end
|
74
81
|
|
75
82
|
it "preserves hash keys" do
|
76
83
|
expect(calculator.solve!(
|
77
84
|
'meaning_of_life' => 'age + kids',
|
78
|
-
'age'
|
79
|
-
'kids'
|
85
|
+
'age' => 40,
|
86
|
+
'kids' => 2
|
80
87
|
)).to eq('age' => 40, 'kids' => 2, 'meaning_of_life' => 42)
|
81
88
|
end
|
82
89
|
|
@@ -96,6 +103,20 @@ describe Dentaku::Calculator do
|
|
96
103
|
calculator.solve!(more_apples: "apples + 1")
|
97
104
|
}.to raise_error(Dentaku::UnboundVariableError)
|
98
105
|
end
|
106
|
+
|
107
|
+
it 'can reference stored formulas' do
|
108
|
+
calculator.store_formula("base_area", "length * width")
|
109
|
+
calculator.store_formula("volume", "base_area * height")
|
110
|
+
|
111
|
+
result = calculator.solve!(
|
112
|
+
weight: "volume * 5.432",
|
113
|
+
height: "3",
|
114
|
+
length: "2",
|
115
|
+
width: "length * 2",
|
116
|
+
)
|
117
|
+
|
118
|
+
expect(result[:weight]).to eq 130.368
|
119
|
+
end
|
99
120
|
end
|
100
121
|
|
101
122
|
describe 'solve' do
|
data/spec/token_scanner_spec.rb
CHANGED
@@ -25,4 +25,16 @@ describe Dentaku::TokenScanner do
|
|
25
25
|
it 'returns a list of all configured scanners' do
|
26
26
|
expect(described_class.scanners.length).to eq 12
|
27
27
|
end
|
28
|
+
|
29
|
+
it 'allows customizing available scanners' do
|
30
|
+
described_class.scanners = [:whitespace, :numeric]
|
31
|
+
expect(described_class.scanners.length).to eq 2
|
32
|
+
described_class.scanners = described_class.available_scanners
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'ignores invalid scanners' do
|
36
|
+
described_class.scanners = [:whitespace, :numeric, :fake]
|
37
|
+
expect(described_class.scanners.length).to eq 2
|
38
|
+
described_class.scanners = described_class.available_scanners
|
39
|
+
end
|
28
40
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dentaku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Solomon White
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- spec/bulk_expression_solver_spec.rb
|
114
114
|
- spec/calculator_spec.rb
|
115
115
|
- spec/dentaku_spec.rb
|
116
|
+
- spec/exceptions_spec.rb
|
116
117
|
- spec/external_function_spec.rb
|
117
118
|
- spec/parser_spec.rb
|
118
119
|
- spec/spec_helper.rb
|
@@ -155,6 +156,7 @@ test_files:
|
|
155
156
|
- spec/bulk_expression_solver_spec.rb
|
156
157
|
- spec/calculator_spec.rb
|
157
158
|
- spec/dentaku_spec.rb
|
159
|
+
- spec/exceptions_spec.rb
|
158
160
|
- spec/external_function_spec.rb
|
159
161
|
- spec/parser_spec.rb
|
160
162
|
- spec/spec_helper.rb
|