predicator 0.3.0 → 1.2.1

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 (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +46 -0
  4. data/.travis.yml +3 -2
  5. data/HISTORY.md +31 -0
  6. data/README.md +33 -12
  7. data/Rakefile +14 -5
  8. data/lib/predicator.rb +18 -4
  9. data/lib/predicator/ast.rb +190 -0
  10. data/lib/predicator/context.rb +7 -12
  11. data/lib/predicator/evaluator.rb +153 -0
  12. data/lib/predicator/lexer.rex +69 -0
  13. data/lib/predicator/lexer.rex.rb +187 -0
  14. data/lib/predicator/parser.rb +427 -26
  15. data/lib/predicator/parser.y +103 -35
  16. data/lib/predicator/version.rb +1 -1
  17. data/lib/predicator/visitors.rb +5 -0
  18. data/lib/predicator/visitors/dot.rb +113 -0
  19. data/lib/predicator/visitors/each.rb +16 -0
  20. data/lib/predicator/visitors/instructions.rb +183 -0
  21. data/lib/predicator/visitors/string.rb +76 -0
  22. data/lib/predicator/visitors/visitor.rb +100 -0
  23. data/predicator.gemspec +10 -2
  24. metadata +67 -28
  25. data/lib/predicator/generated_parser.rb +0 -307
  26. data/lib/predicator/lexer.rb +0 -117
  27. data/lib/predicator/predicates/and.rb +0 -20
  28. data/lib/predicator/predicates/between.rb +0 -23
  29. data/lib/predicator/predicates/equal.rb +0 -9
  30. data/lib/predicator/predicates/false.rb +0 -13
  31. data/lib/predicator/predicates/greater_than.rb +0 -9
  32. data/lib/predicator/predicates/greater_than_or_equal.rb +0 -9
  33. data/lib/predicator/predicates/less_than.rb +0 -9
  34. data/lib/predicator/predicates/less_than_or_equal.rb +0 -9
  35. data/lib/predicator/predicates/not.rb +0 -20
  36. data/lib/predicator/predicates/not_equal.rb +0 -9
  37. data/lib/predicator/predicates/or.rb +0 -20
  38. data/lib/predicator/predicates/relation.rb +0 -17
  39. data/lib/predicator/predicates/true.rb +0 -13
  40. data/lib/predicator/variable.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b6f75523bd14777d85c7d52472823bad7eafa31f
4
- data.tar.gz: cbf63f511abaccbac93ed564256d5d0caea7da87
2
+ SHA256:
3
+ metadata.gz: d3d6163b7499f9afc436a5b7f11ac3b38e128f6e2f8eb6a37fc6fe6521080e1d
4
+ data.tar.gz: b2647db6156463d1d06317a3cd82305cd2cf29a464484017e920e653d9db7dcc
5
5
  SHA512:
6
- metadata.gz: 7c0c9e0fa3737b4c6f1ec27a20254197fbe0a52825f37cb2978c02c829473d3fd3777f5e5937cb08c754783acc0c1a9188f8e31054a4ab4eb1dfab325873c68a
7
- data.tar.gz: af32c18e7d32ccf7047f02d7bafaa71fbb7d3fa0b7383d47f78854440fdb1ef45884d832f37e1ff364ca19a5ff90783fe6715b4e33c9b7e247ff6cb169a80444
6
+ metadata.gz: e4233d02d68f77beec56049b8ab6bb13c93b114b003d57a903b945f29f9caace9dd9e835b0e9bf7baf9a60dd581b8aceadc2b3f3151e49539d1fbce6ebc8c7d9
7
+ data.tar.gz: a93c002f76da05a95d1d426a8a94db8107b59627217ccf2a4d559a381c941b6d7ea7c1a1ede8e5063aa3c8d2d52b4ce3d8397e77e014dc4b7aeb4626a8f6905f
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /test/reports/
9
9
  /tmp/
10
+ .ruby-gemset
11
+ .ruby-version
@@ -0,0 +1,46 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ DisabledByDefault: true
4
+ Exclude:
5
+ - "lib/predicator/parser.rb" #generated file
6
+ - "lib/predicator/lexer.rex.rb" #generated file
7
+
8
+ Bundler/DuplicatedGem:
9
+ Enabled: true
10
+ Lint:
11
+ Enabled: true
12
+ Performance:
13
+ Enabled: true
14
+ Security:
15
+ Enabled: true
16
+
17
+ # Single quotes being faster is hardly measurable and only affects parse time.
18
+ # Enforcing double quotes reduces the times where you need to change them
19
+ # when introducing an interpolation. Use single quotes only if their semantics
20
+ # are needed.
21
+ Style/StringLiterals:
22
+ EnforcedStyle: double_quotes
23
+
24
+ # Seattle style
25
+ Style/MethodDefParentheses:
26
+ EnforcedStyle: require_no_parentheses
27
+
28
+ # Allow regex argument in Seattle style
29
+ Lint/AmbiguousRegexpLiteral:
30
+ Enabled: false
31
+
32
+ # Allow block argument in Seattle style
33
+ Lint/AmbiguousOperator:
34
+ Enabled: false
35
+
36
+ # No long classes
37
+ Style/ClassLength:
38
+ Enabled: true
39
+ Exclude:
40
+ - "test/**/*.rb"
41
+ - "lib/predicator/visitors/instructions.rb"
42
+ - "lib/predicator/evaluator.rb"
43
+
44
+ Lint/HandleExceptions:
45
+ Exclude:
46
+ - lib/tasks/**/*.rake
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- before_install: gem install bundler
3
+ - 2.0
4
+ - 2.1
5
+ - 2.2
data/HISTORY.md CHANGED
@@ -1,3 +1,34 @@
1
+ ### 1.2.1 / 2020-08-04
2
+
3
+ * Moves project to riddler/predicator
4
+
5
+ ### 1.2.0 / 2018-09-13
6
+
7
+ * Adds type casting to comparisons
8
+ * Adds date type and predicates
9
+ * Adds "from now" and "ago" relative dates
10
+ * Adds "present" and "blank" predicates
11
+ * Adds "starts with" and "ends with" string comparisons
12
+
13
+ ### 1.1.0 / 2017-12-06
14
+
15
+ * Rescues errors when comparing invalid types
16
+
17
+ ### 1.0.0 / 2017-09-22
18
+
19
+ * Adds new lexer
20
+ * Introduces the stack machine and instructions
21
+ * Adds LessThan predicate
22
+ * Adds Between predicate
23
+ * Adds In and Not In predicates (with arrays)
24
+ * Adds BooleanVariable predicate
25
+
26
+ ### 0.4.0 / 2016-06-09
27
+
28
+ * Adds double dispatch for relation predicates
29
+ * Adds method predicate (.blank? and .present?)
30
+ * Adds Context#eval
31
+
1
32
  ### 0.3.0 / 2016-03-11
2
33
 
3
34
  * Adds more relation predicates (GT, LT, GTE, LTE, NEQ)
data/README.md CHANGED
@@ -1,25 +1,47 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/predicator.svg)](http://badge.fury.io/rb/predicator)
2
- [![Build Status](https://travis-ci.org/johnnyt/predicator.svg?branch=master)](https://travis-ci.org/johnnyt/predicator)
3
- [![Dependency Status](https://img.shields.io/gemnasium/johnnyt/predicator.svg)](https://gemnasium.com/johnnyt/predicator)
4
- [![Coverage Status](https://coveralls.io/repos/github/johnnyt/predicator/badge.svg?branch=master)](https://coveralls.io/github/johnnyt/predicator?branch=master)
2
+ [![Build Status](https://travis-ci.org/predicator/predicator.svg?branch=master)](https://travis-ci.org/predicator/predicator)
3
+ [![Coverage Status](https://coveralls.io/repos/github/predicator/predicator/badge.svg?branch=master)](https://coveralls.io/github/predicator/predicator?branch=master)
5
4
 
6
5
  # Predicator
7
6
 
8
- Predicator is a predicate engine
7
+ Predicator is a safe (does not eval code), admin or business user facing predicate engine. It turns a string predicate like `"score > 600 or (score > 580 and monthly_income > 9000)"` along with a supplied context into a `true` or `false`. This predicate can be stored as an attribute of a model (ex: an Offer model could store a predicate indicating if it is available to a customer).
9
8
 
10
9
  ## Usage
11
10
 
12
- Example usage:
11
+ Simple usage:
13
12
 
14
13
  ```ruby
15
14
  require "predicator"
16
15
 
17
- pred = Predicator.parse "a.b = 5"
16
+ Predicator.evaluate "score > 600 or (score > 580 and income > 9000)", score: 590 # false
18
17
 
19
- context = Predicator::Context.new
20
- context[:a] = {b:5}
18
+ Predicator.evaluate "score > 600 or (score > 580 and income > 9000)", score: 590, income: 9500 # true
19
+ ```
20
+
21
+ Example usage with a model:
21
22
 
22
- pred.satisfied? context
23
+ ```ruby
24
+ class Customer
25
+ ...
26
+ def to_hash
27
+ {
28
+ ...
29
+ score: score,
30
+ income: income,
31
+ ...
32
+ }
33
+ end
34
+ end
35
+
36
+ class Offer
37
+ attr_accessor :available_predicate
38
+
39
+ ...
40
+
41
+ def available_to? customer
42
+ Predicator.evaluate available_predicate, customer.to_hash
43
+ end
44
+ end
23
45
  ```
24
46
 
25
47
  ## Installation
@@ -41,14 +63,13 @@ Or install it yourself as:
41
63
  ## Development
42
64
 
43
65
  After checking out the repo, run `bin/setup` to install dependencies.
44
- Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
66
+ Then, run `rake test` (or just `rake`) to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
45
67
 
46
68
  To install this gem onto your local machine, run `bundle exec rake install`.
47
- To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
48
69
 
49
70
  ## Contributing
50
71
 
51
- Bug reports and pull requests are welcome on GitHub at https://github.com/johnnyt/predicator.
72
+ Bug reports and pull requests are welcome on GitHub at https://github.com/predicator/predicator.
52
73
  This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
53
74
 
54
75
 
data/Rakefile CHANGED
@@ -1,15 +1,24 @@
1
+ require "rubygems"
1
2
  require "bundler/gem_tasks"
2
3
  require "rake/testtask"
4
+ require "oedipus_lex"
5
+
6
+ Rake.application.rake_require "oedipus_lex"
3
7
 
4
8
  Rake::TestTask.new :test do |t|
5
9
  t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
10
+ t.test_files = FileList["test/**/test_*.rb"]
11
+ t.warning = false
8
12
  end
9
13
 
10
- task :default => :test
14
+ desc "Generate the lexer"
15
+ task lexer: "lib/predicator/lexer.rex.rb"
11
16
 
12
17
  desc "Compile and generate the parser"
13
- task :compile do
14
- sh "racc lib/predicator/parser.y -o lib/predicator/generated_parser.rb"
18
+ task parser: :lexer do
19
+ sh "racc -l -o lib/predicator/parser.rb lib/predicator/parser.y"
15
20
  end
21
+
22
+ task test: :parser
23
+
24
+ task default: :test
@@ -1,10 +1,24 @@
1
1
  require "predicator/context"
2
- require "predicator/lexer"
2
+ require "predicator/evaluator"
3
3
  require "predicator/parser"
4
- require "predicator/version"
5
4
 
6
5
  module Predicator
7
- def self.parse string
8
- Predicator::Parser.new.parse string
6
+ def self.parse source
7
+ Predicator::Parser.new.parse source
8
+ end
9
+
10
+ def self.compile source
11
+ ast = parse source
12
+ ast.to_instructions
13
+ end
14
+
15
+ def self.evaluate source, context={}
16
+ instructions = compile source
17
+ evaluate_instructions instructions, context
18
+ end
19
+
20
+ def self.evaluate_instructions instructions, context={}
21
+ evaluator = Evaluator.new instructions, context
22
+ evaluator.result
9
23
  end
10
24
  end
@@ -0,0 +1,190 @@
1
+ module Predicator
2
+ module AST
3
+ class Node
4
+ include Enumerable
5
+
6
+ attr_accessor :left
7
+
8
+ def initialize left
9
+ @left = left
10
+ end
11
+
12
+ def each &block
13
+ Visitors::Each.new(block).accept self
14
+ end
15
+
16
+ def to_dot
17
+ Visitors::Dot.new.accept self
18
+ end
19
+
20
+ def to_instructions
21
+ Visitors::Instructions.new.accept self
22
+ end
23
+
24
+ def to_s
25
+ Visitors::String.new.accept self
26
+ end
27
+
28
+ def type
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def variable?; false; end
33
+ def literal?; false; end
34
+ end
35
+
36
+ class Terminal < Node
37
+ alias :symbol :left
38
+ end
39
+
40
+ class Literal < Terminal
41
+ def type; :LITERAL; end
42
+ def literal?; true; end
43
+ end
44
+
45
+ class Variable < Terminal
46
+ def type; :VARIABLE; end
47
+ def variable?; true; end
48
+ end
49
+
50
+ %w[ True False Integer String Date Duration Blank Present ].each do |t|
51
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
52
+ class #{t} < Literal;
53
+ def type; :#{t.upcase}; end
54
+ end
55
+ eoruby
56
+ end
57
+
58
+ class Unary < Node
59
+ def children; [left] end
60
+ end
61
+
62
+ class IntegerArray < Unary
63
+ def type; :INTARRAY; end
64
+ end
65
+
66
+ class StringArray < Unary
67
+ def type; :STRARRAY; end
68
+ end
69
+
70
+ class Not < Unary
71
+ def type; :NOT; end
72
+ end
73
+
74
+ class Group < Unary
75
+ def type; :GROUP; end
76
+ end
77
+
78
+ class DateFromNow < Unary
79
+ def type; :DATEFROMNOW; end
80
+ end
81
+
82
+ class DateAgo < Unary
83
+ def type; :DATEAGO; end
84
+ end
85
+
86
+ class Binary < Node
87
+ attr_accessor :right
88
+
89
+ def initialize left, right
90
+ super left
91
+ @right = right
92
+ end
93
+
94
+ def children; [left, right] end
95
+ end
96
+
97
+ class IntegerEqual < Binary
98
+ def type; :INTEQ; end
99
+ end
100
+
101
+ class StringEqual < Binary
102
+ def type; :STREQ; end
103
+ end
104
+
105
+ class DateEqual < Binary
106
+ def type; :DATEQ; end
107
+ end
108
+
109
+ class IntegerGreaterThan < Binary
110
+ def type; :INTGT; end
111
+ end
112
+
113
+ class StringGreaterThan < Binary
114
+ def type; :STRGT; end
115
+ end
116
+
117
+ class DateGreaterThan < Binary
118
+ def type; :DATGT; end
119
+ end
120
+
121
+ class IntegerLessThan < Binary
122
+ def type; :INTLT; end
123
+ end
124
+
125
+ class StringLessThan < Binary
126
+ def type; :STRLT; end
127
+ end
128
+
129
+ class DateLessThan < Binary
130
+ def type; :DATLT; end
131
+ end
132
+
133
+ class IntegerIn < Binary
134
+ def type; :INTIN; end
135
+ end
136
+
137
+ class StringIn < Binary
138
+ def type; :STRIN; end
139
+ end
140
+
141
+ class IntegerNotIn < Binary
142
+ def type; :INTNOTIN; end
143
+ end
144
+
145
+ class StringNotIn < Binary
146
+ def type; :STRNOTIN; end
147
+ end
148
+
149
+ class StringStartsWith < Binary
150
+ def type; :STRSTARTSWITH; end
151
+ end
152
+
153
+ class StringEndsWith < Binary
154
+ def type; :STRENDSWITH; end
155
+ end
156
+
157
+ class And < Binary
158
+ def type; :AND; end
159
+ end
160
+
161
+ class Or < Binary
162
+ def type; :OR; end
163
+ end
164
+
165
+ class Ternary < Node
166
+ attr_accessor :middle, :right
167
+
168
+ def initialize left, middle, right
169
+ super left
170
+ @middle = middle
171
+ @right = right
172
+ end
173
+
174
+ def children; [left, middle, right] end
175
+ end
176
+
177
+ class IntegerBetween < Ternary
178
+ def type; :INTBETWEEN; end
179
+ end
180
+
181
+ class DateBetween < Ternary
182
+ def type; :DATBETWEEN; end
183
+ end
184
+
185
+ class BooleanVariable < Unary
186
+ def type; :BOOL; end
187
+ end
188
+
189
+ end
190
+ end
@@ -1,23 +1,18 @@
1
- require "ostruct"
2
-
3
1
  module Predicator
4
2
  class Context
5
- attr_reader :bindings
6
-
7
- def initialize
3
+ def initialize params={}
8
4
  @bindings = {}
5
+ params.each{ |key,value| bind key, value }
9
6
  end
10
7
 
11
8
  def bind name, value
12
- value = OpenStruct.new(value) if value.kind_of? Hash
13
- bindings[name.to_s] = value
9
+ @bindings[name.to_s] = value
14
10
  end
11
+ alias :[]= :bind
15
12
 
16
- def value_for input
17
- return input unless input.kind_of? Predicator::Variable
18
-
19
- input.value_in self
13
+ def binding_for name
14
+ @bindings[name.to_s]
20
15
  end
21
- alias :[] :value_for
16
+ alias :[] :binding_for
22
17
  end
23
18
  end