predicator 0.3.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/predicator.svg)](http://badge.fury.io/rb/predicator)
|
2
|
-
[![Build Status](https://travis-ci.org/
|
3
|
-
[![
|
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
|
-
|
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
|