predicate 2.3.2 → 2.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.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/LICENSE.md +17 -19
- data/README.md +435 -0
- data/bin/g +2 -0
- data/lib/predicate/dsl.rb +138 -0
- data/lib/predicate/factory.rb +142 -37
- data/lib/predicate/grammar.rb +11 -2
- data/lib/predicate/grammar.sexp.yml +29 -0
- data/lib/predicate/nodes/${op_name}.rb.jeny +12 -0
- data/lib/predicate/nodes/and.rb +9 -0
- data/lib/predicate/nodes/binary_func.rb +20 -0
- data/lib/predicate/nodes/contradiction.rb +2 -7
- data/lib/predicate/nodes/dyadic_comp.rb +1 -3
- data/lib/predicate/nodes/empty.rb +14 -0
- data/lib/predicate/nodes/eq.rb +11 -3
- data/lib/predicate/nodes/expr.rb +9 -3
- data/lib/predicate/nodes/has_size.rb +14 -0
- data/lib/predicate/nodes/identifier.rb +1 -3
- data/lib/predicate/nodes/in.rb +7 -6
- data/lib/predicate/nodes/intersect.rb +3 -23
- data/lib/predicate/nodes/literal.rb +1 -3
- data/lib/predicate/nodes/match.rb +1 -21
- data/lib/predicate/nodes/nadic_bool.rb +1 -3
- data/lib/predicate/nodes/native.rb +1 -3
- data/lib/predicate/nodes/not.rb +1 -3
- data/lib/predicate/nodes/opaque.rb +1 -3
- data/lib/predicate/nodes/qualified_identifier.rb +1 -3
- data/lib/predicate/nodes/set_op.rb +26 -0
- data/lib/predicate/nodes/subset.rb +11 -0
- data/lib/predicate/nodes/superset.rb +11 -0
- data/lib/predicate/nodes/tautology.rb +6 -7
- data/lib/predicate/nodes/unary_func.rb +16 -0
- data/lib/predicate/nodes/var.rb +46 -0
- data/lib/predicate/processors/qualifier.rb +4 -0
- data/lib/predicate/processors/renamer.rb +4 -0
- data/lib/predicate/processors/to_s.rb +28 -0
- data/lib/predicate/processors/unqualifier.rb +21 -0
- data/lib/predicate/processors.rb +1 -0
- data/lib/predicate/sequel/to_sequel.rb +4 -1
- data/lib/predicate/sugar.rb +47 -0
- data/lib/predicate/version.rb +2 -2
- data/lib/predicate.rb +26 -2
- data/spec/dsl/test_dsl.rb +204 -0
- data/spec/dsl/test_evaluate.rb +65 -0
- data/spec/dsl/test_respond_to_missing.rb +35 -0
- data/spec/dsl/test_to_skake_case.rb +38 -0
- data/spec/factory/shared/a_comparison_factory_method.rb +1 -0
- data/spec/factory/test_${op_name}.rb.jeny +12 -0
- data/spec/factory/test_comp.rb +28 -5
- data/spec/factory/test_empty.rb +11 -0
- data/spec/factory/test_has_size.rb +11 -0
- data/spec/factory/test_match.rb +1 -0
- data/spec/factory/test_set_ops.rb +18 -0
- data/spec/factory/test_var.rb +22 -0
- data/spec/factory/test_vars.rb +27 -0
- data/spec/nodes/${op_name}.jeny/test_evaluate.rb.jeny +19 -0
- data/spec/nodes/empty/test_evaluate.rb +42 -0
- data/spec/nodes/has_size/test_evaluate.rb +44 -0
- data/spec/predicate/test_and_split.rb +18 -0
- data/spec/predicate/test_attr_split.rb +18 -0
- data/spec/predicate/test_constant_variables.rb +24 -2
- data/spec/predicate/test_constants.rb +24 -0
- data/spec/predicate/test_evaluate.rb +205 -3
- data/spec/predicate/test_free_variables.rb +1 -1
- data/spec/predicate/test_to_hash.rb +40 -0
- data/spec/predicate/test_to_s.rb +37 -0
- data/spec/predicate/test_unqualify.rb +18 -0
- data/spec/sequel/test_to_sequel.rb +25 -0
- data/spec/shared/a_predicate.rb +30 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/test_predicate.rb +78 -33
- data/spec/test_readme.rb +80 -0
- data/spec/test_sugar.rb +48 -0
- data/tasks/test.rake +3 -3
- metadata +45 -14
- data/spec/factory/test_between.rb +0 -12
- data/spec/factory/test_intersect.rb +0 -12
@@ -0,0 +1,26 @@
|
|
1
|
+
class Predicate
|
2
|
+
module SetOp
|
3
|
+
include Expr
|
4
|
+
|
5
|
+
def priority; 80; end
|
6
|
+
|
7
|
+
def left
|
8
|
+
self[1]
|
9
|
+
end
|
10
|
+
alias :identifier :left
|
11
|
+
|
12
|
+
def right
|
13
|
+
self[2]
|
14
|
+
end
|
15
|
+
alias :values :right
|
16
|
+
|
17
|
+
def free_variables
|
18
|
+
@free_variables ||= left.free_variables | right.free_variables
|
19
|
+
end
|
20
|
+
|
21
|
+
def constant_variables
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -18,13 +18,8 @@ class Predicate
|
|
18
18
|
self
|
19
19
|
end
|
20
20
|
|
21
|
-
def dyadic_priority
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def priority
|
26
|
-
100
|
27
|
-
end
|
21
|
+
def dyadic_priority; 1000; end
|
22
|
+
def priority; 100; end
|
28
23
|
|
29
24
|
def free_variables
|
30
25
|
@free_variables ||= []
|
@@ -38,5 +33,9 @@ class Predicate
|
|
38
33
|
true
|
39
34
|
end
|
40
35
|
|
36
|
+
def to_hash
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
|
41
40
|
end
|
42
41
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Var
|
3
|
+
include Expr
|
4
|
+
|
5
|
+
def priority; 100; end
|
6
|
+
|
7
|
+
def formaldef
|
8
|
+
self[1]
|
9
|
+
end
|
10
|
+
|
11
|
+
def semantics
|
12
|
+
self[2]
|
13
|
+
end
|
14
|
+
|
15
|
+
def free_variables
|
16
|
+
@free_variables ||= [ [formaldef, semantics] ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def dig_terms
|
20
|
+
@dig_terms ||= case formaldef
|
21
|
+
when String
|
22
|
+
formaldef.split(".").map{|elm|
|
23
|
+
elm =~ /^\d+$/ ? elm.to_i : elm.to_sym
|
24
|
+
}
|
25
|
+
when Array
|
26
|
+
formaldef
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Unrecognized variable def `#{formaldef}`"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def evaluate(on)
|
33
|
+
case semantics
|
34
|
+
when :dig
|
35
|
+
dig_terms.inject(on){|ctx,elm| ctx.dig(elm) }
|
36
|
+
when :send
|
37
|
+
dig_terms.inject(on){|ctx,elm| ctx.__send__(elm.to_sym) }
|
38
|
+
when :public_send
|
39
|
+
dig_terms.inject(on){|ctx,elm| ctx.public_send(elm.to_sym) }
|
40
|
+
else
|
41
|
+
raise ArgumentError, "Unrecognized variable semantics `#{semantics}`"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -57,6 +57,14 @@ class Predicate
|
|
57
57
|
"#{apply(sexpr.identifier)} INTERSECTS #{to_literal(sexpr.values)}"
|
58
58
|
end
|
59
59
|
|
60
|
+
def on_subset(sexpr)
|
61
|
+
"#{apply(sexpr.identifier)} IS SUBSET OF #{to_literal(sexpr.values)}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_superset(sexpr)
|
65
|
+
"#{apply(sexpr.identifier)} IS SUPERSET OF #{to_literal(sexpr.values)}"
|
66
|
+
end
|
67
|
+
|
60
68
|
def on_literal(sexpr)
|
61
69
|
to_literal(sexpr.last)
|
62
70
|
end
|
@@ -69,16 +77,36 @@ class Predicate
|
|
69
77
|
"#{apply(sexpr.left)} =~ #{apply(sexpr.right)}"
|
70
78
|
end
|
71
79
|
|
80
|
+
def on_empty(sexpr)
|
81
|
+
"EMPTY(#{commalist(sexpr.body)})"
|
82
|
+
end
|
83
|
+
|
84
|
+
def on_size(sexpr)
|
85
|
+
"SIZE(#{commalist(sexpr.body)})"
|
86
|
+
end
|
87
|
+
|
88
|
+
#jeny(predicate) def on_${op_name}(sexpr)
|
89
|
+
#jeny(predicate) "${OP_NAME}(#{commalist(sexpr.body)})"
|
90
|
+
#jeny(predicate) end
|
91
|
+
|
72
92
|
def on_native(sexpr)
|
73
93
|
sexpr.last.inspect
|
74
94
|
end
|
75
95
|
|
96
|
+
def on_var(sexpr)
|
97
|
+
"#{sexpr.semantics}(#{sexpr.formaldef})"
|
98
|
+
end
|
99
|
+
|
76
100
|
def on_missing(sexpr)
|
77
101
|
raise "Unimplemented: #{sexpr.first}"
|
78
102
|
end
|
79
103
|
|
80
104
|
protected
|
81
105
|
|
106
|
+
def commalist(of)
|
107
|
+
of.map{|o| apply(o) }.join(',')
|
108
|
+
end
|
109
|
+
|
82
110
|
def to_literal(x)
|
83
111
|
case x
|
84
112
|
when Placeholder then "$#{x.object_id}"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Predicate
|
2
|
+
class Unqualifier < Sexpr::Rewriter
|
3
|
+
|
4
|
+
grammar Grammar
|
5
|
+
|
6
|
+
def on_qualified_identifier(sexpr)
|
7
|
+
[ :identifier, sexpr.last ]
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_native(sexpr)
|
11
|
+
raise NotSupportedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_var(sexpr)
|
15
|
+
raise NotSupportedError
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :on_missing :copy_and_apply
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/predicate/processors.rb
CHANGED
@@ -110,10 +110,13 @@ class Predicate
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def on_unsupported(sexpr)
|
113
|
-
raise NotSupportedError
|
113
|
+
raise NotSupportedError, "Unsupported predicate #{sexpr}"
|
114
114
|
end
|
115
|
+
alias :on_var :on_unsupported
|
115
116
|
alias :on_native :on_unsupported
|
116
117
|
alias :on_intersect :on_unsupported
|
118
|
+
alias :on_subset :on_unsupported
|
119
|
+
alias :on_superset :on_unsupported
|
117
120
|
end
|
118
121
|
include Methods
|
119
122
|
end # class ToSequel
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Sugar
|
3
|
+
|
4
|
+
[ :eq, :neq, :lt, :lte, :gt, :gte ].each do |m|
|
5
|
+
define_method(m) do |left, right=nil|
|
6
|
+
return comp(m, left) if TupleLike===left && right.nil?
|
7
|
+
super(left, right)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def between(middle, lower_bound, upper_bound)
|
12
|
+
_factor_predicate [:and, [:gte, sexpr(middle), sexpr(lower_bound)],
|
13
|
+
[:lte, sexpr(middle), sexpr(upper_bound)]]
|
14
|
+
end
|
15
|
+
|
16
|
+
def match(left, right, options = nil)
|
17
|
+
super(left, right, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def min_size(left, right)
|
21
|
+
unless right.is_a?(Integer)
|
22
|
+
raise ArgumentError, "Integer expected, got #{right}"
|
23
|
+
end
|
24
|
+
if RUBY_VERSION >= "2.6"
|
25
|
+
has_size(left, Range.new(right,nil))
|
26
|
+
else
|
27
|
+
has_size(left, Range.new(right,(2**32-1)))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def max_size(left, right)
|
32
|
+
unless right.is_a?(Integer)
|
33
|
+
raise ArgumentError, "Integer expected, got #{right}"
|
34
|
+
end
|
35
|
+
has_size(left, 0..right)
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_null(operand)
|
39
|
+
eq(operand, nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
#jeny(sugar) def ${op_name}(*args)
|
43
|
+
#jeny(sugar) TODO
|
44
|
+
#jeny(sugar) end
|
45
|
+
|
46
|
+
end # module Sugar
|
47
|
+
end # class Predicate
|
data/lib/predicate/version.rb
CHANGED
data/lib/predicate.rb
CHANGED
@@ -2,12 +2,16 @@ require 'sexpr'
|
|
2
2
|
require_relative 'predicate/version'
|
3
3
|
require_relative 'predicate/placeholder'
|
4
4
|
require_relative 'predicate/factory'
|
5
|
+
require_relative 'predicate/sugar'
|
5
6
|
require_relative 'predicate/grammar'
|
6
7
|
require_relative 'predicate/processors'
|
8
|
+
require_relative 'predicate/dsl'
|
7
9
|
class Predicate
|
8
10
|
|
9
|
-
class
|
10
|
-
class
|
11
|
+
class Error < StandardError; end
|
12
|
+
class NotSupportedError < Error; end
|
13
|
+
class UnboundError < Error; end
|
14
|
+
class TypeError < Error; end
|
11
15
|
|
12
16
|
TupleLike = ->(t){ t.is_a?(Hash) }
|
13
17
|
|
@@ -21,6 +25,7 @@ class Predicate
|
|
21
25
|
|
22
26
|
class << self
|
23
27
|
include Factory
|
28
|
+
include Sugar
|
24
29
|
|
25
30
|
def coerce(arg)
|
26
31
|
case arg
|
@@ -36,6 +41,14 @@ class Predicate
|
|
36
41
|
end
|
37
42
|
alias :parse :coerce
|
38
43
|
|
44
|
+
def dsl(var = var(".", :dig), &bl)
|
45
|
+
Predicate::Dsl.new(var, false).instance_eval(&bl)
|
46
|
+
end
|
47
|
+
|
48
|
+
def currying(var = var(".", :dig), &bl)
|
49
|
+
Predicate::Dsl.new(var, true).instance_eval(&bl)
|
50
|
+
end
|
51
|
+
|
39
52
|
private
|
40
53
|
|
41
54
|
def _factor_predicate(arg)
|
@@ -88,6 +101,10 @@ class Predicate
|
|
88
101
|
Predicate.new(expr.qualify(qualifier))
|
89
102
|
end
|
90
103
|
|
104
|
+
def unqualify
|
105
|
+
Predicate.new(expr.unqualify)
|
106
|
+
end
|
107
|
+
|
91
108
|
def rename(renaming)
|
92
109
|
Predicate.new(expr.rename(renaming))
|
93
110
|
end
|
@@ -138,4 +155,11 @@ class Predicate
|
|
138
155
|
expr.to_s(scope)
|
139
156
|
end
|
140
157
|
|
158
|
+
# If possible, converts this predicate back to a `{ attr: value, ... }`
|
159
|
+
# hash. Raises an IllegalArgumentError if the predicate cannot be
|
160
|
+
# represented that way.
|
161
|
+
def to_hash
|
162
|
+
expr.to_hash
|
163
|
+
end
|
164
|
+
|
141
165
|
end # class Predicate
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Predicate
|
4
|
+
describe Dsl do
|
5
|
+
subject{
|
6
|
+
Predicate::Dsl.new(:x).instance_eval(&bl)
|
7
|
+
}
|
8
|
+
|
9
|
+
context 'when used on a comparison op' do
|
10
|
+
context 'curried' do
|
11
|
+
let(:bl){
|
12
|
+
Proc.new{ eq(6) }
|
13
|
+
}
|
14
|
+
|
15
|
+
it { expect(subject).to eq(Predicate.eq(:x, 6))}
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'non curried' do
|
19
|
+
let(:bl){
|
20
|
+
Proc.new{ eq(:y, 6) }
|
21
|
+
}
|
22
|
+
|
23
|
+
it { expect(subject).to eq(Predicate.eq(:y, 6))}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when used on between' do
|
28
|
+
context 'curried' do
|
29
|
+
let(:bl){
|
30
|
+
Proc.new{ between(2, 6) }
|
31
|
+
}
|
32
|
+
|
33
|
+
it { expect(subject).to eq(Predicate.gte(:x, 2) & Predicate.lte(:x, 6))}
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'non curried' do
|
37
|
+
let(:bl){
|
38
|
+
Proc.new{ between(:y, 2, 6) }
|
39
|
+
}
|
40
|
+
|
41
|
+
it { expect(subject).to eq(Predicate.gte(:y, 2) & Predicate.lte(:y, 6))}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when used on a sugar op' do
|
46
|
+
context 'curried' do
|
47
|
+
let(:bl){
|
48
|
+
Proc.new{ min_size(6) }
|
49
|
+
}
|
50
|
+
|
51
|
+
it { expect(subject).to eq(Predicate.has_size(:x, Range.new(6, nil)))}
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'curried' do
|
55
|
+
let(:bl){
|
56
|
+
Proc.new{ min_size(:y, 6) }
|
57
|
+
}
|
58
|
+
|
59
|
+
it { expect(subject).to eq(Predicate.has_size(:y, Range.new(6, nil)))}
|
60
|
+
end
|
61
|
+
end if RUBY_VERSION >= "2.6"
|
62
|
+
|
63
|
+
context 'when used on match' do
|
64
|
+
context 'curryied' do
|
65
|
+
let(:bl){
|
66
|
+
Proc.new{ match(/a-z/) }
|
67
|
+
}
|
68
|
+
|
69
|
+
it { expect(subject).to eq(Predicate.match(:x, /a-z/, {}))}
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'curryied with options' do
|
73
|
+
let(:bl){
|
74
|
+
Proc.new{ match(/a-z/, {case_sensitive: false}) }
|
75
|
+
}
|
76
|
+
|
77
|
+
it { expect(subject).to eq(Predicate.match(:x, /a-z/, {case_sensitive: false}))}
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'non curried' do
|
81
|
+
let(:bl){
|
82
|
+
Proc.new{ match(:y, /a-z/) }
|
83
|
+
}
|
84
|
+
|
85
|
+
it { expect(subject).to eq(Predicate.match(:y, /a-z/, {}))}
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'non curried with options' do
|
89
|
+
let(:bl){
|
90
|
+
Proc.new{ match(:y, /a-z/, {case_sensitive: false}) }
|
91
|
+
}
|
92
|
+
|
93
|
+
it { expect(subject).to eq(Predicate.match(:y, /a-z/, {case_sensitive: false}))}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'when used on size' do
|
98
|
+
context 'curried' do
|
99
|
+
let(:bl){
|
100
|
+
Proc.new{ size(1..10) }
|
101
|
+
}
|
102
|
+
|
103
|
+
it { expect(subject).to eq(Predicate.has_size(:x, 1..10))}
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'curried with Integer' do
|
107
|
+
let(:bl){
|
108
|
+
Proc.new{ size(10) }
|
109
|
+
}
|
110
|
+
|
111
|
+
it { expect(subject).to eq(Predicate.has_size(:x, 10))}
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'not curried' do
|
115
|
+
let(:bl){
|
116
|
+
Proc.new{ size(:y, 1..10) }
|
117
|
+
}
|
118
|
+
|
119
|
+
it { expect(subject).to eq(Predicate.has_size(:y, 1..10))}
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'not curried with Integer' do
|
123
|
+
let(:bl){
|
124
|
+
Proc.new{ size(:y, 10) }
|
125
|
+
}
|
126
|
+
|
127
|
+
it { expect(subject).to eq(Predicate.has_size(:y, 10))}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when used with some camelCase' do
|
132
|
+
context 'curried' do
|
133
|
+
let(:bl){
|
134
|
+
Proc.new{ hasSize(1..10) }
|
135
|
+
}
|
136
|
+
|
137
|
+
it { expect(subject).to eq(Predicate.has_size(:x, 1..10))}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'when used with full text versions and their negation' do
|
142
|
+
context 'less_than' do
|
143
|
+
let(:bl){
|
144
|
+
Proc.new{ less_than(:x, 1) }
|
145
|
+
}
|
146
|
+
|
147
|
+
it { expect(subject).to eq(Predicate.lt(:x, 1))}
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'lessThan' do
|
151
|
+
let(:bl){
|
152
|
+
Proc.new{ lessThan(:x, 1) }
|
153
|
+
}
|
154
|
+
|
155
|
+
it { expect(subject).to eq(Predicate.lt(:x, 1))}
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'notLessThan' do
|
159
|
+
let(:bl){
|
160
|
+
Proc.new{ notLessThan(:x, 1) }
|
161
|
+
}
|
162
|
+
|
163
|
+
it { expect(subject).to eq(!Predicate.lt(:x, 1))}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'when used with a negated form' do
|
168
|
+
context 'curried' do
|
169
|
+
let(:bl){
|
170
|
+
Proc.new{ notSize(1..10) }
|
171
|
+
}
|
172
|
+
|
173
|
+
it { expect(subject).to eq(!Predicate.has_size(:x, 1..10))}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'when used on null' do
|
178
|
+
context 'curried' do
|
179
|
+
let(:bl){
|
180
|
+
Proc.new{ null() }
|
181
|
+
}
|
182
|
+
|
183
|
+
it { expect(subject).to eq(Predicate.eq(:x, nil))}
|
184
|
+
end
|
185
|
+
|
186
|
+
context 'negated' do
|
187
|
+
let(:bl){
|
188
|
+
Proc.new{ notNull() }
|
189
|
+
}
|
190
|
+
|
191
|
+
it { expect(subject).to eq(Predicate.neq(:x, nil))}
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'not curried' do
|
195
|
+
let(:bl){
|
196
|
+
Proc.new{ null(:y) }
|
197
|
+
}
|
198
|
+
|
199
|
+
it { expect(subject).to eq(Predicate.eq(:y, nil))}
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Predicate in a curried form' do
|
4
|
+
|
5
|
+
context 'with a variable name' do
|
6
|
+
it 'supports tautology, contradiction' do
|
7
|
+
p = Predicate.currying(:x){ tautology }
|
8
|
+
expect(p).to eql(Predicate.tautology)
|
9
|
+
|
10
|
+
p = Predicate.currying(:x){ contradiction }
|
11
|
+
expect(p).to eql(Predicate.contradiction)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'support comparison operators' do
|
15
|
+
p = Predicate.currying(:x){
|
16
|
+
gt(0) & lt(12)
|
17
|
+
}
|
18
|
+
expect(p.evaluate(x: 0)).to be_falsy
|
19
|
+
expect(p.evaluate(x: 1)).to be_truthy
|
20
|
+
expect(p.evaluate(x: 11)).to be_truthy
|
21
|
+
expect(p.evaluate(x: 12)).to be_falsy
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'supports between shortcut' do
|
25
|
+
p = Predicate.currying(:x){
|
26
|
+
between(1, 11)
|
27
|
+
}
|
28
|
+
expect(p.evaluate(x: 0)).to be_falsy
|
29
|
+
expect(p.evaluate(x: 1)).to be_truthy
|
30
|
+
expect(p.evaluate(x: 11)).to be_truthy
|
31
|
+
expect(p.evaluate(x: 12)).to be_falsy
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'supports match' do
|
35
|
+
p = Predicate.currying(:x){
|
36
|
+
match(/a/)
|
37
|
+
}
|
38
|
+
expect(p.evaluate(x: "zzz")).to be_falsy
|
39
|
+
expect(p.evaluate(x: "abc")).to be_truthy
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'without a variable name' do
|
44
|
+
it 'applies to the object passed' do
|
45
|
+
p = Predicate.currying{
|
46
|
+
gt(0) & lt(12)
|
47
|
+
}
|
48
|
+
expect(p.evaluate(0)).to be_falsy
|
49
|
+
expect(p.evaluate(1)).to be_truthy
|
50
|
+
expect(p.evaluate(11)).to be_truthy
|
51
|
+
expect(p.evaluate(12)).to be_falsy
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'on shortcuts' do
|
56
|
+
it 'applies to the object passed' do
|
57
|
+
p = Predicate.currying{
|
58
|
+
min_size(5)
|
59
|
+
}
|
60
|
+
expect(p.evaluate("1")).to be_falsy
|
61
|
+
expect(p.evaluate("013456789")).to be_truthy
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Predicate
|
4
|
+
describe Dsl do
|
5
|
+
subject{
|
6
|
+
Predicate::Dsl.new(:x)
|
7
|
+
}
|
8
|
+
|
9
|
+
it 'respond to negated forms' do
|
10
|
+
[
|
11
|
+
:notSize,
|
12
|
+
:notEq
|
13
|
+
].each do |m|
|
14
|
+
expect(subject.send(:respond_to_missing?, m)).to eql(true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'respond to camelCased forms' do
|
19
|
+
[
|
20
|
+
:hasSize
|
21
|
+
].each do |m|
|
22
|
+
expect(subject.send(:respond_to_missing?, m)).to eql(true)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'respond false otherwise' do
|
27
|
+
[
|
28
|
+
:nosuchone,
|
29
|
+
:notsuchone
|
30
|
+
].each do |m|
|
31
|
+
expect(subject.send(:respond_to_missing?, m)).to eql(false)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|