predicator 0.3.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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