predicate 2.4.0 → 2.5.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.
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