dentaku 1.2.6 → 2.0.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.
- checksums.yaml +4 -4
- data/README.md +52 -57
- data/Rakefile +1 -1
- data/lib/dentaku.rb +8 -0
- data/lib/dentaku/ast.rb +22 -0
- data/lib/dentaku/ast/addition.rb +15 -0
- data/lib/dentaku/ast/combinators.rb +15 -0
- data/lib/dentaku/ast/comparators.rb +47 -0
- data/lib/dentaku/ast/division.rb +15 -0
- data/lib/dentaku/ast/exponentiation.rb +15 -0
- data/lib/dentaku/ast/function.rb +54 -0
- data/lib/dentaku/ast/functions/if.rb +26 -0
- data/lib/dentaku/ast/functions/max.rb +5 -0
- data/lib/dentaku/ast/functions/min.rb +5 -0
- data/lib/dentaku/ast/functions/not.rb +5 -0
- data/lib/dentaku/ast/functions/round.rb +5 -0
- data/lib/dentaku/ast/functions/rounddown.rb +5 -0
- data/lib/dentaku/ast/functions/roundup.rb +5 -0
- data/lib/dentaku/ast/functions/ruby_math.rb +8 -0
- data/lib/dentaku/ast/grouping.rb +13 -0
- data/lib/dentaku/ast/identifier.rb +29 -0
- data/lib/dentaku/ast/multiplication.rb +15 -0
- data/lib/dentaku/ast/negation.rb +25 -0
- data/lib/dentaku/ast/nil.rb +9 -0
- data/lib/dentaku/ast/node.rb +13 -0
- data/lib/dentaku/ast/numeric.rb +17 -0
- data/lib/dentaku/ast/operation.rb +20 -0
- data/lib/dentaku/ast/string.rb +17 -0
- data/lib/dentaku/ast/subtraction.rb +15 -0
- data/lib/dentaku/bulk_expression_solver.rb +6 -11
- data/lib/dentaku/calculator.rb +26 -20
- data/lib/dentaku/parser.rb +131 -0
- data/lib/dentaku/token.rb +4 -0
- data/lib/dentaku/token_matchers.rb +29 -0
- data/lib/dentaku/token_scanner.rb +18 -3
- data/lib/dentaku/tokenizer.rb +10 -2
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/function_spec.rb +19 -0
- data/spec/ast/node_spec.rb +37 -0
- data/spec/bulk_expression_solver_spec.rb +12 -5
- data/spec/calculator_spec.rb +14 -1
- data/spec/external_function_spec.rb +12 -28
- data/spec/parser_spec.rb +88 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/token_scanner_spec.rb +4 -3
- data/spec/tokenizer_spec.rb +32 -6
- metadata +36 -16
- data/lib/dentaku/binary_operation.rb +0 -35
- data/lib/dentaku/evaluator.rb +0 -166
- data/lib/dentaku/expression.rb +0 -56
- data/lib/dentaku/external_function.rb +0 -10
- data/lib/dentaku/rule_set.rb +0 -153
- data/spec/binary_operation_spec.rb +0 -45
- data/spec/evaluator_spec.rb +0 -145
- data/spec/expression_spec.rb +0 -25
- data/spec/rule_set_spec.rb +0 -43
@@ -1,45 +0,0 @@
|
|
1
|
-
require 'dentaku/binary_operation'
|
2
|
-
|
3
|
-
describe Dentaku::BinaryOperation do
|
4
|
-
let(:operation) { described_class.new(2, 3) }
|
5
|
-
let(:logical) { described_class.new(true, false) }
|
6
|
-
|
7
|
-
it 'raises a number to a power' do
|
8
|
-
expect(operation.pow).to eq [:numeric, 8]
|
9
|
-
end
|
10
|
-
|
11
|
-
it 'adds two numbers' do
|
12
|
-
expect(operation.add).to eq [:numeric, 5]
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'subtracts two numbers' do
|
16
|
-
expect(operation.subtract).to eq [:numeric, -1]
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'multiplies two numbers' do
|
20
|
-
expect(operation.multiply).to eq [:numeric, 6]
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'divides two numbers' do
|
24
|
-
expect(operation.divide).to eq [:numeric, (BigDecimal.new('2.0')/BigDecimal.new('3.0'))]
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'compares two numbers' do
|
28
|
-
expect(operation.le).to eq [:logical, true]
|
29
|
-
expect(operation.lt).to eq [:logical, true]
|
30
|
-
expect(operation.ne).to eq [:logical, true]
|
31
|
-
|
32
|
-
expect(operation.ge).to eq [:logical, false]
|
33
|
-
expect(operation.gt).to eq [:logical, false]
|
34
|
-
expect(operation.eq).to eq [:logical, false]
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'performs logical AND and OR' do
|
38
|
-
expect(logical.and).to eq [:logical, false]
|
39
|
-
expect(logical.or).to eq [:logical, true]
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'mods two numbers' do
|
43
|
-
expect(operation.mod).to eq [:numeric, 2%3]
|
44
|
-
end
|
45
|
-
end
|
data/spec/evaluator_spec.rb
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'dentaku/evaluator'
|
3
|
-
|
4
|
-
describe Dentaku::Evaluator do
|
5
|
-
let(:rule_set) { Dentaku::RuleSet.new }
|
6
|
-
let(:evaluator) { Dentaku::Evaluator.new(rule_set) }
|
7
|
-
|
8
|
-
describe 'rule scanning' do
|
9
|
-
it 'finds a matching rule' do
|
10
|
-
rule = [Dentaku::TokenMatcher.new(:numeric, nil)]
|
11
|
-
stream = [Dentaku::Token.new(:numeric, 1), Dentaku::Token.new(:operator, :add), Dentaku::Token.new(:numeric, 1)]
|
12
|
-
position, _match = evaluator.find_rule_match(rule, stream)
|
13
|
-
expect(position).to eq(0)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
describe 'evaluating' do
|
18
|
-
it 'empty expression is be truthy' do
|
19
|
-
expect(evaluator.evaluate([])).to be
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'empty expression equals 0' do
|
23
|
-
expect(evaluator.evaluate([])).to eq(0)
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'single numeric evaluates to its value' do
|
27
|
-
expect(evaluator.evaluate([Dentaku::Token.new(:numeric, 10)])).to eq(10)
|
28
|
-
expect(evaluator.evaluate([Dentaku::Token.new(:string, 'a')])).to eq('a')
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'evaluates one apply step' do
|
32
|
-
stream = token_stream(1, :add, 1, :add, 1)
|
33
|
-
expected = token_stream(2, :add, 1)
|
34
|
-
|
35
|
-
expect(evaluator.evaluate_step(stream, 0, 3, :apply)).to eq(expected)
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'evaluates one grouping step' do
|
39
|
-
stream = token_stream(:open, 1, :add, 1, :close, :multiply, 5)
|
40
|
-
expected = token_stream(2, :multiply, 5)
|
41
|
-
|
42
|
-
expect(evaluator.evaluate_step(stream, 0, 5, :evaluate_group)).to eq(expected)
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'supports unary minus' do
|
46
|
-
expect(evaluator.evaluate(token_stream(:subtract, 1))).to eq(-1)
|
47
|
-
expect(evaluator.evaluate(token_stream(1, :subtract, :subtract, 1))).to eq(2)
|
48
|
-
expect(evaluator.evaluate(token_stream(1, :subtract, :subtract, :subtract, 1))).to eq(0)
|
49
|
-
expect(evaluator.evaluate(token_stream(:subtract, 1, :add, 1))).to eq(0)
|
50
|
-
expect(evaluator.evaluate(token_stream(3, :add, 0, :multiply, :subtract, 3))).to eq(3)
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'evaluates a number multiplied by an exponent' do
|
54
|
-
expect(evaluator.evaluate(token_stream(10, :pow, 2))).to eq(100)
|
55
|
-
expect(evaluator.evaluate(token_stream(0, :multiply, 10, :pow, 5))).to eq(0)
|
56
|
-
expect(evaluator.evaluate(token_stream(0, :multiply, 10, :pow, :subtract, 5))).to eq(0)
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'supports unary percentage' do
|
60
|
-
expect(evaluator.evaluate(token_stream(50, :mod))).to eq(0.5)
|
61
|
-
expect(evaluator.evaluate(token_stream(50, :mod, :multiply, 100))).to eq(50)
|
62
|
-
end
|
63
|
-
|
64
|
-
describe 'maths' do
|
65
|
-
it 'performs addition' do
|
66
|
-
expect(evaluator.evaluate(token_stream(1, :add, 1))).to eq(2)
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'respects order of precedence' do
|
70
|
-
expect(evaluator.evaluate(token_stream(1, :add, 1, :multiply, 5))).to eq(6)
|
71
|
-
expect(evaluator.evaluate(token_stream(2, :add, 10, :mod, 2))).to eq(2)
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'respects explicit grouping' do
|
75
|
-
expect(evaluator.evaluate(token_stream(:open, 1, :add, 1, :close, :multiply, 5))).to eq(10)
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'returns floating point from division when there is a remainder' do
|
79
|
-
expect(evaluator.evaluate(token_stream(5, :divide, 4))).to eq(1.25)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
describe 'find_rule_match' do
|
84
|
-
it 'matches a function call' do
|
85
|
-
if_pattern, _ = *rule_set.rules.first
|
86
|
-
position, tokens = evaluator.find_rule_match(if_pattern, token_stream(:if, :fopen, true, :comma, 1, :comma, 2, :close))
|
87
|
-
expect(position).to eq 0
|
88
|
-
expect(tokens.length).to eq 8
|
89
|
-
end
|
90
|
-
|
91
|
-
describe 'with start-anchored token' do
|
92
|
-
let(:number) { [Dentaku::TokenMatcher.new(:numeric).caret] }
|
93
|
-
let(:string) { [Dentaku::TokenMatcher.new(:string).caret] }
|
94
|
-
let(:stream) { token_stream(1, 'foo') }
|
95
|
-
|
96
|
-
it 'matches anchored to the beginning of the token stream' do
|
97
|
-
position, tokens = evaluator.find_rule_match(number, stream)
|
98
|
-
expect(position).to eq 0
|
99
|
-
expect(tokens.length).to eq 1
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'does not match later in the stream' do
|
103
|
-
position, _tokens = evaluator.find_rule_match(string, stream)
|
104
|
-
expect(position).to be_nil
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
describe 'functions' do
|
110
|
-
it 'is evaluated' do
|
111
|
-
expect(evaluator.evaluate(token_stream(:round, :fopen, 5, :divide, 3.0, :close))).to eq 2
|
112
|
-
expect(evaluator.evaluate(token_stream(:round, :fopen, 5, :divide, 3.0, :comma, 2, :close))).to eq 1.67
|
113
|
-
expect(evaluator.evaluate(token_stream(:roundup, :fopen, 5, :divide, 1.2, :close))).to eq 5
|
114
|
-
expect(evaluator.evaluate(token_stream(:rounddown, :fopen, 5, :divide, 1.2, :close))).to eq 4
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
describe 'logic' do
|
119
|
-
it 'evaluates conditional' do
|
120
|
-
expect(evaluator.evaluate(token_stream(5, :gt, 1))).to be_truthy
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'expands inequality ranges' do
|
124
|
-
stream = token_stream(5, :lt, 10, :le, 10)
|
125
|
-
expected = token_stream(5, :lt, 10, :and, 10, :le, 10)
|
126
|
-
expect(evaluator.evaluate_step(stream, 0, 5, :expand_range)).to eq(expected)
|
127
|
-
|
128
|
-
expect(evaluator.evaluate(token_stream(5, :lt, 10, :le, 10))).to be_truthy
|
129
|
-
expect(evaluator.evaluate(token_stream(3, :gt, 5, :ge, 1))).to be_falsey
|
130
|
-
|
131
|
-
expect { evaluator.evaluate(token_stream(3, :gt, 2, :lt, 1)) }.to raise_error
|
132
|
-
end
|
133
|
-
|
134
|
-
it 'evaluates combined conditionals' do
|
135
|
-
expect(evaluator.evaluate(token_stream(5, :gt, 1, :or, false))).to be_truthy
|
136
|
-
expect(evaluator.evaluate(token_stream(5, :gt, 1, :and, false))).to be_falsey
|
137
|
-
end
|
138
|
-
|
139
|
-
it 'negates a logical value' do
|
140
|
-
expect(evaluator.evaluate(token_stream(:not, :fopen, 5, :gt, 1, :or, false, :close))).to be_falsey
|
141
|
-
expect(evaluator.evaluate(token_stream(:not, :fopen, 5, :gt, 1, :and, false, :close))).to be_truthy
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
data/spec/expression_spec.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'dentaku/expression'
|
3
|
-
|
4
|
-
describe Dentaku::Expression do
|
5
|
-
describe 'an all literal expression' do
|
6
|
-
it 'is fully bound' do
|
7
|
-
static = described_class.new('1 + 1')
|
8
|
-
expect(static).not_to be_unbound
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
describe 'an expression with variable identifiers' do
|
13
|
-
it 'is unbound' do
|
14
|
-
dynamic = described_class.new('a > 5')
|
15
|
-
expect(dynamic).to be_unbound
|
16
|
-
end
|
17
|
-
|
18
|
-
describe 'with values set for all variables' do
|
19
|
-
it 'is fully bound' do
|
20
|
-
dynamic = described_class.new('a > 5', {a: 7})
|
21
|
-
expect(dynamic).not_to be_unbound
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/spec/rule_set_spec.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require 'dentaku/rule_set'
|
2
|
-
|
3
|
-
describe Dentaku::RuleSet do
|
4
|
-
subject { described_class.new }
|
5
|
-
|
6
|
-
it 'yields core rules' do
|
7
|
-
functions = []
|
8
|
-
subject.each { |pattern, function| functions << function }
|
9
|
-
expect(functions).to eq [:if,
|
10
|
-
:round,
|
11
|
-
:round_int,
|
12
|
-
:round_int,
|
13
|
-
:not,
|
14
|
-
:evaluate_group,
|
15
|
-
:negate,
|
16
|
-
:apply,
|
17
|
-
:pow_negate,
|
18
|
-
:apply,
|
19
|
-
:apply,
|
20
|
-
:mul_negate,
|
21
|
-
:apply,
|
22
|
-
:percentage,
|
23
|
-
:negate,
|
24
|
-
:expand_range,
|
25
|
-
:expand_range,
|
26
|
-
:apply,
|
27
|
-
:apply,
|
28
|
-
:apply,
|
29
|
-
]
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'adds custom function patterns' do
|
33
|
-
functions = []
|
34
|
-
subject.add_function(
|
35
|
-
name: :min,
|
36
|
-
type: :numeric,
|
37
|
-
signature: [ :arguments ],
|
38
|
-
body: ->(*args) { args.min }
|
39
|
-
)
|
40
|
-
subject.each { |pattern, function| functions << function }
|
41
|
-
expect(functions).to include('min')
|
42
|
-
end
|
43
|
-
end
|