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.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.rubocop.yml +46 -0
- data/.travis.yml +3 -2
- data/HISTORY.md +31 -0
- data/README.md +33 -12
- data/Rakefile +14 -5
- data/lib/predicator.rb +18 -4
- data/lib/predicator/ast.rb +190 -0
- data/lib/predicator/context.rb +7 -12
- data/lib/predicator/evaluator.rb +153 -0
- data/lib/predicator/lexer.rex +69 -0
- data/lib/predicator/lexer.rex.rb +187 -0
- data/lib/predicator/parser.rb +427 -26
- data/lib/predicator/parser.y +103 -35
- data/lib/predicator/version.rb +1 -1
- data/lib/predicator/visitors.rb +5 -0
- data/lib/predicator/visitors/dot.rb +113 -0
- data/lib/predicator/visitors/each.rb +16 -0
- data/lib/predicator/visitors/instructions.rb +183 -0
- data/lib/predicator/visitors/string.rb +76 -0
- data/lib/predicator/visitors/visitor.rb +100 -0
- data/predicator.gemspec +10 -2
- metadata +67 -28
- data/lib/predicator/generated_parser.rb +0 -307
- data/lib/predicator/lexer.rb +0 -117
- data/lib/predicator/predicates/and.rb +0 -20
- data/lib/predicator/predicates/between.rb +0 -23
- data/lib/predicator/predicates/equal.rb +0 -9
- data/lib/predicator/predicates/false.rb +0 -13
- data/lib/predicator/predicates/greater_than.rb +0 -9
- data/lib/predicator/predicates/greater_than_or_equal.rb +0 -9
- data/lib/predicator/predicates/less_than.rb +0 -9
- data/lib/predicator/predicates/less_than_or_equal.rb +0 -9
- data/lib/predicator/predicates/not.rb +0 -20
- data/lib/predicator/predicates/not_equal.rb +0 -9
- data/lib/predicator/predicates/or.rb +0 -20
- data/lib/predicator/predicates/relation.rb +0 -17
- data/lib/predicator/predicates/true.rb +0 -13
- data/lib/predicator/variable.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d3d6163b7499f9afc436a5b7f11ac3b38e128f6e2f8eb6a37fc6fe6521080e1d
|
4
|
+
data.tar.gz: b2647db6156463d1d06317a3cd82305cd2cf29a464484017e920e653d9db7dcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4233d02d68f77beec56049b8ab6bb13c93b114b003d57a903b945f29f9caace9dd9e835b0e9bf7baf9a60dd581b8aceadc2b3f3151e49539d1fbce6ebc8c7d9
|
7
|
+
data.tar.gz: a93c002f76da05a95d1d426a8a94db8107b59627217ccf2a4d559a381c941b6d7ea7c1a1ede8e5063aa3c8d2d52b4ce3d8397e77e014dc4b7aeb4626a8f6905f
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -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
|
data/.travis.yml
CHANGED
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
|
[](http://badge.fury.io/rb/predicator)
|
2
|
-
[](https://coveralls.io/github/johnnyt/predicator?branch=master)
|
2
|
+
[](https://travis-ci.org/predicator/predicator)
|
3
|
+
[](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
|
-
|
11
|
+
Simple usage:
|
13
12
|
|
14
13
|
```ruby
|
15
14
|
require "predicator"
|
16
15
|
|
17
|
-
|
16
|
+
Predicator.evaluate "score > 600 or (score > 580 and income > 9000)", score: 590 # false
|
18
17
|
|
19
|
-
|
20
|
-
|
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
|
-
|
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/
|
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.
|
7
|
-
t.
|
10
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
11
|
+
t.warning = false
|
8
12
|
end
|
9
13
|
|
10
|
-
|
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 :
|
14
|
-
sh "racc lib/predicator/parser.
|
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
|
data/lib/predicator.rb
CHANGED
@@ -1,10 +1,24 @@
|
|
1
1
|
require "predicator/context"
|
2
|
-
require "predicator/
|
2
|
+
require "predicator/evaluator"
|
3
3
|
require "predicator/parser"
|
4
|
-
require "predicator/version"
|
5
4
|
|
6
5
|
module Predicator
|
7
|
-
def self.parse
|
8
|
-
Predicator::Parser.new.parse
|
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
|
data/lib/predicator/context.rb
CHANGED
@@ -1,23 +1,18 @@
|
|
1
|
-
require "ostruct"
|
2
|
-
|
3
1
|
module Predicator
|
4
2
|
class Context
|
5
|
-
|
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
|
-
|
13
|
-
bindings[name.to_s] = value
|
9
|
+
@bindings[name.to_s] = value
|
14
10
|
end
|
11
|
+
alias :[]= :bind
|
15
12
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
input.value_in self
|
13
|
+
def binding_for name
|
14
|
+
@bindings[name.to_s]
|
20
15
|
end
|
21
|
-
alias :[] :
|
16
|
+
alias :[] :binding_for
|
22
17
|
end
|
23
18
|
end
|