predicate 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -0
  3. data/LICENSE.md +17 -19
  4. data/README.md +433 -0
  5. data/bin/g +2 -0
  6. data/lib/predicate.rb +15 -2
  7. data/lib/predicate/dsl.rb +138 -0
  8. data/lib/predicate/factory.rb +130 -33
  9. data/lib/predicate/grammar.rb +11 -2
  10. data/lib/predicate/grammar.sexp.yml +29 -0
  11. data/lib/predicate/nodes/${op_name}.rb.jeny +12 -0
  12. data/lib/predicate/nodes/binary_func.rb +20 -0
  13. data/lib/predicate/nodes/contradiction.rb +2 -7
  14. data/lib/predicate/nodes/dyadic_comp.rb +1 -3
  15. data/lib/predicate/nodes/empty.rb +14 -0
  16. data/lib/predicate/nodes/eq.rb +1 -3
  17. data/lib/predicate/nodes/expr.rb +1 -3
  18. data/lib/predicate/nodes/has_size.rb +14 -0
  19. data/lib/predicate/nodes/identifier.rb +1 -3
  20. data/lib/predicate/nodes/in.rb +2 -6
  21. data/lib/predicate/nodes/intersect.rb +3 -23
  22. data/lib/predicate/nodes/literal.rb +1 -3
  23. data/lib/predicate/nodes/match.rb +1 -21
  24. data/lib/predicate/nodes/nadic_bool.rb +1 -3
  25. data/lib/predicate/nodes/native.rb +1 -3
  26. data/lib/predicate/nodes/not.rb +1 -3
  27. data/lib/predicate/nodes/opaque.rb +1 -3
  28. data/lib/predicate/nodes/qualified_identifier.rb +1 -3
  29. data/lib/predicate/nodes/set_op.rb +26 -0
  30. data/lib/predicate/nodes/subset.rb +11 -0
  31. data/lib/predicate/nodes/superset.rb +11 -0
  32. data/lib/predicate/nodes/tautology.rb +2 -7
  33. data/lib/predicate/nodes/unary_func.rb +16 -0
  34. data/lib/predicate/nodes/var.rb +46 -0
  35. data/lib/predicate/processors/qualifier.rb +4 -0
  36. data/lib/predicate/processors/renamer.rb +4 -0
  37. data/lib/predicate/processors/to_s.rb +28 -0
  38. data/lib/predicate/processors/unqualifier.rb +4 -0
  39. data/lib/predicate/sequel/to_sequel.rb +3 -0
  40. data/lib/predicate/sugar.rb +47 -0
  41. data/lib/predicate/version.rb +1 -1
  42. data/spec/dsl/test_dsl.rb +204 -0
  43. data/spec/dsl/test_evaluate.rb +65 -0
  44. data/spec/dsl/test_respond_to_missing.rb +35 -0
  45. data/spec/dsl/test_to_skake_case.rb +38 -0
  46. data/spec/factory/shared/a_comparison_factory_method.rb +1 -0
  47. data/spec/factory/test_${op_name}.rb.jeny +12 -0
  48. data/spec/factory/test_empty.rb +11 -0
  49. data/spec/factory/test_has_size.rb +11 -0
  50. data/spec/factory/test_match.rb +1 -0
  51. data/spec/factory/test_set_ops.rb +18 -0
  52. data/spec/factory/test_var.rb +22 -0
  53. data/spec/factory/test_vars.rb +27 -0
  54. data/spec/nodes/${op_name}.jeny/test_evaluate.rb.jeny +19 -0
  55. data/spec/nodes/empty/test_evaluate.rb +42 -0
  56. data/spec/nodes/has_size/test_evaluate.rb +44 -0
  57. data/spec/predicate/test_and_split.rb +18 -0
  58. data/spec/predicate/test_attr_split.rb +18 -0
  59. data/spec/predicate/test_constant_variables.rb +24 -2
  60. data/spec/predicate/test_constants.rb +24 -0
  61. data/spec/predicate/test_evaluate.rb +205 -3
  62. data/spec/predicate/test_to_s.rb +37 -0
  63. data/spec/sequel/test_to_sequel.rb +16 -0
  64. data/spec/shared/a_predicate.rb +30 -0
  65. data/spec/spec_helper.rb +1 -0
  66. data/spec/test_predicate.rb +68 -33
  67. data/spec/test_readme.rb +80 -0
  68. data/spec/test_sugar.rb +48 -0
  69. data/tasks/test.rake +3 -3
  70. metadata +40 -12
  71. data/spec/factory/test_between.rb +0 -12
  72. data/spec/factory/test_intersect.rb +0 -12
@@ -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}"
@@ -11,6 +11,10 @@ class Predicate
11
11
  raise NotSupportedError
12
12
  end
13
13
 
14
+ def on_var(sexpr)
15
+ raise NotSupportedError
16
+ end
17
+
14
18
  alias :on_missing :copy_and_apply
15
19
 
16
20
  end
@@ -112,8 +112,11 @@ class Predicate
112
112
  def on_unsupported(sexpr)
113
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
@@ -1,7 +1,7 @@
1
1
  class Predicate
2
2
  module Version
3
3
  MAJOR = 2
4
- MINOR = 4
4
+ MINOR = 5
5
5
  TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
@@ -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
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ class Predicate
4
+ describe Dsl, "to_snake_case" do
5
+
6
+ def snake(str)
7
+ Dsl.new.__send__(:to_snake_case, str)
8
+ end
9
+
10
+ it 'works on snake case already' do
11
+ {
12
+ "snake" => "snake",
13
+ "snake_case" => "snake_case"
14
+ }.each_pair do |k,v|
15
+ expect(snake(k)).to eq(v)
16
+ end
17
+ end
18
+
19
+ it 'works on camelCase already' do
20
+ {
21
+ "camelCase" => "camel_case",
22
+ "theCamelCase" => "the_camel_case"
23
+ }.each_pair do |k,v|
24
+ expect(snake(k)).to eq(v)
25
+ end
26
+ end
27
+
28
+ it 'works on PascalCase already' do
29
+ {
30
+ "PascalCase" => "pascal_case",
31
+ "ThePascalCase" => "the_pascal_case"
32
+ }.each_pair do |k,v|
33
+ expect(snake(k)).to eq(v)
34
+ end
35
+ end
36
+
37
+ end
38
+ end