predicator 0.4.0 → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +3 -2
- data/HISTORY.md +9 -0
- data/README.md +7 -8
- data/Rakefile +14 -5
- data/lib/predicator.rb +18 -10
- data/lib/predicator/ast.rb +138 -0
- data/lib/predicator/context.rb +7 -63
- data/lib/predicator/evaluator.rb +108 -0
- data/lib/predicator/lexer.rex +51 -0
- data/lib/predicator/lexer.rex.rb +160 -0
- data/lib/predicator/parser.rb +291 -7
- data/lib/predicator/parser.y +66 -40
- data/lib/predicator/version.rb +1 -1
- data/lib/predicator/visitors.rb +5 -0
- data/lib/predicator/visitors/dot.rb +100 -0
- data/lib/predicator/visitors/each.rb +16 -0
- data/lib/predicator/visitors/instructions.rb +117 -0
- data/lib/predicator/visitors/string.rb +60 -0
- data/lib/predicator/visitors/visitor.rb +48 -0
- data/predicator.gemspec +3 -2
- metadata +29 -32
- data/lib/predicator/errors.rb +0 -5
- data/lib/predicator/generated_parser.rb +0 -335
- data/lib/predicator/lexer.rb +0 -125
- data/lib/predicator/nodes.rb +0 -6
- data/lib/predicator/nodes/base_node.rb +0 -53
- data/lib/predicator/nodes/date_node.rb +0 -13
- data/lib/predicator/nodes/fixnum_node.rb +0 -9
- data/lib/predicator/nodes/float_node.rb +0 -9
- data/lib/predicator/nodes/nil_class_node.rb +0 -25
- data/lib/predicator/nodes/string_node.rb +0 -13
- data/lib/predicator/predicates.rb +0 -14
- data/lib/predicator/predicates/and.rb +0 -20
- data/lib/predicator/predicates/between.rb +0 -31
- 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/method.rb +0 -17
- 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 -31
- 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
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac9999a26155d5318cda41ac8d119352e63aa0b5
|
4
|
+
data.tar.gz: 3c1411fe73bae5d3ca3ee30604856b975d9027c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d5156cbdd62b081816d8d4035a5de20f410bddc9bc57540054de0e433ce61510d80ce694c452e1b4a3f219c7fc9ce8edea38e73db3d763a7f37587fb1b5d7eb
|
7
|
+
data.tar.gz: 25e56d9f070f10da1e956922072fe6f3ab086b4f16a99a77c281dd6e1c53028f5230a3247f2d473df54b844d916e0758f9be46ba1e2f3761ceb8f7e9b8646aca
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/HISTORY.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
### 1.0.0 / 2017-09-22
|
2
|
+
|
3
|
+
* Adds new lexer
|
4
|
+
* Introduces the stack machine and instructions
|
5
|
+
* Adds LessThan predicate
|
6
|
+
* Adds Between predicate
|
7
|
+
* Adds In and Not In predicates (with arrays)
|
8
|
+
* Adds BooleanVariable predicate
|
9
|
+
|
1
10
|
### 0.4.0 / 2016-06-09
|
2
11
|
|
3
12
|
* Adds double dispatch for relation predicates
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
[](http://badge.fury.io/rb/predicator)
|
2
|
-
[](https://travis-ci.org/predicator/predicator)
|
3
3
|
[](https://gemnasium.com/johnnyt/predicator)
|
4
|
-
[](https://coveralls.io/github/predicator/predicator?branch=master)
|
5
5
|
|
6
6
|
# Predicator
|
7
7
|
|
8
|
-
Predicator is a predicate engine
|
8
|
+
Predicator is a predicate engine.
|
9
9
|
|
10
10
|
## Usage
|
11
11
|
|
@@ -14,12 +14,11 @@ Example usage:
|
|
14
14
|
```ruby
|
15
15
|
require "predicator"
|
16
16
|
|
17
|
-
|
17
|
+
Predicator.evaluate "age > 21" # false
|
18
18
|
|
19
|
-
|
20
|
-
context[:a] = {b:5}
|
19
|
+
Predicator.evaluate "age > 21", age: 10 # false
|
21
20
|
|
22
|
-
|
21
|
+
Predicator.evaluate "age > 21", age: 50 # true
|
23
22
|
```
|
24
23
|
|
25
24
|
## Installation
|
@@ -48,7 +47,7 @@ To release a new version, update the version number in `version.rb`, and then ru
|
|
48
47
|
|
49
48
|
## Contributing
|
50
49
|
|
51
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
50
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/predicator/predicator.
|
52
51
|
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
52
|
|
54
53
|
|
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,16 +1,24 @@
|
|
1
|
-
require "date"
|
2
|
-
|
3
1
|
require "predicator/context"
|
4
|
-
require "predicator/
|
5
|
-
require "predicator/lexer"
|
6
|
-
require "predicator/nodes"
|
2
|
+
require "predicator/evaluator"
|
7
3
|
require "predicator/parser"
|
8
|
-
require "predicator/predicates"
|
9
|
-
require "predicator/variable"
|
10
|
-
require "predicator/version"
|
11
4
|
|
12
5
|
module Predicator
|
13
|
-
def self.parse
|
14
|
-
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
|
15
23
|
end
|
16
24
|
end
|
@@ -0,0 +1,138 @@
|
|
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_predicate
|
25
|
+
Visitors::Predicate.new.accept self
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
Visitors::String.new.accept self
|
30
|
+
end
|
31
|
+
|
32
|
+
def type
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
def variable?; false; end
|
37
|
+
def literal?; false; end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Terminal < Node
|
41
|
+
alias :symbol :left
|
42
|
+
end
|
43
|
+
|
44
|
+
class Literal < Terminal
|
45
|
+
def type; :LITERAL; end
|
46
|
+
def literal?; true; end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Variable < Terminal
|
50
|
+
def type; :VARIABLE; end
|
51
|
+
def variable?; true; end
|
52
|
+
end
|
53
|
+
|
54
|
+
%w[ True False Integer String ].each do |t|
|
55
|
+
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
56
|
+
class #{t} < Literal;
|
57
|
+
def type; :#{t.upcase}; end
|
58
|
+
end
|
59
|
+
eoruby
|
60
|
+
end
|
61
|
+
|
62
|
+
class Unary < Node
|
63
|
+
def children; [left] end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Array < Unary
|
67
|
+
def type; :ARRAY; 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 Binary < Node
|
79
|
+
attr_accessor :right
|
80
|
+
|
81
|
+
def initialize left, right
|
82
|
+
super left
|
83
|
+
@right = right
|
84
|
+
end
|
85
|
+
|
86
|
+
def children; [left, right] end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Equal < Binary
|
90
|
+
def type; :EQ; end
|
91
|
+
end
|
92
|
+
|
93
|
+
class GreaterThan < Binary
|
94
|
+
def type; :GT; end
|
95
|
+
end
|
96
|
+
|
97
|
+
class LessThan < Binary
|
98
|
+
def type; :LT; end
|
99
|
+
end
|
100
|
+
|
101
|
+
class In < Binary
|
102
|
+
def type; :IN; end
|
103
|
+
end
|
104
|
+
|
105
|
+
class NotIn < Binary
|
106
|
+
def type; :NOTIN; end
|
107
|
+
end
|
108
|
+
|
109
|
+
class And < Binary
|
110
|
+
def type; :AND; end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Or < Binary
|
114
|
+
def type; :OR; end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Ternary < Node
|
118
|
+
attr_accessor :middle, :right
|
119
|
+
|
120
|
+
def initialize left, middle, right
|
121
|
+
super left
|
122
|
+
@middle = middle
|
123
|
+
@right = right
|
124
|
+
end
|
125
|
+
|
126
|
+
def children; [left, middle, right] end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Between < Ternary
|
130
|
+
def type; :BETWEEN; end
|
131
|
+
end
|
132
|
+
|
133
|
+
class BooleanVariable < Unary
|
134
|
+
def type; :BOOL; end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
data/lib/predicator/context.rb
CHANGED
@@ -1,74 +1,18 @@
|
|
1
|
-
require "ostruct"
|
2
|
-
|
3
1
|
module Predicator
|
4
2
|
class Context
|
5
|
-
|
6
|
-
|
7
|
-
def initialize attrs={}
|
3
|
+
def initialize params={}
|
8
4
|
@bindings = {}
|
9
|
-
|
5
|
+
params.each{ |key,value| bind key, value }
|
10
6
|
end
|
11
7
|
|
12
|
-
def bind name,
|
13
|
-
|
14
|
-
obj = OpenStruct.new obj
|
15
|
-
end
|
16
|
-
bindings[name.to_s] = obj
|
8
|
+
def bind name, value
|
9
|
+
@bindings[name.to_s] = value
|
17
10
|
end
|
18
11
|
alias :[]= :bind
|
19
12
|
|
20
|
-
def
|
21
|
-
|
22
|
-
node_class = Predicator::Nodes::BaseNode.class_for value
|
23
|
-
node_class.new value
|
24
|
-
end
|
25
|
-
|
26
|
-
def value_for input
|
27
|
-
if input.kind_of? Predicator::Variable
|
28
|
-
input.value_in self
|
29
|
-
else
|
30
|
-
input
|
31
|
-
end
|
32
|
-
end
|
33
|
-
alias :[] :value_for
|
34
|
-
|
35
|
-
def respond_to? method
|
36
|
-
bindings.key?(method.to_s) || super
|
37
|
-
end
|
38
|
-
|
39
|
-
def method_missing method, *args, &block
|
40
|
-
found_binding = bindings.fetch method.to_s, nil
|
41
|
-
if found_binding.nil?
|
42
|
-
super
|
43
|
-
else
|
44
|
-
found_binding
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def eval string
|
49
|
-
string.gsub(/{{([^}]+)}}/) do |match|
|
50
|
-
name, attribute = $1.strip.split "."
|
51
|
-
variable = Predicator::Variable.new name, attribute
|
52
|
-
value_for variable
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def to_hash
|
57
|
-
hsh = {}
|
58
|
-
bindings.each do |key, value|
|
59
|
-
hsh[key] = hash_value_for value
|
60
|
-
end
|
61
|
-
hsh
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def hash_value_for value
|
67
|
-
if value.kind_of? OpenStruct
|
68
|
-
value.to_h
|
69
|
-
else
|
70
|
-
value.to_hash
|
71
|
-
end
|
13
|
+
def binding_for name
|
14
|
+
@bindings[name.to_s]
|
72
15
|
end
|
16
|
+
alias :[] :binding_for
|
73
17
|
end
|
74
18
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Predicator
|
2
|
+
class Evaluator
|
3
|
+
attr_reader :instructions, :stack, :context
|
4
|
+
|
5
|
+
def initialize instructions, context_data={}
|
6
|
+
@instructions = instructions
|
7
|
+
@context = context_for context_data
|
8
|
+
@stack = []
|
9
|
+
@ip = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def context_for context_data
|
13
|
+
return context_data unless context_data.kind_of? Hash
|
14
|
+
Context.new context_data
|
15
|
+
end
|
16
|
+
|
17
|
+
def result
|
18
|
+
while @ip < instructions.length
|
19
|
+
process @instructions[@ip]
|
20
|
+
@ip += 1
|
21
|
+
end
|
22
|
+
stack.pop
|
23
|
+
end
|
24
|
+
|
25
|
+
def process instruction
|
26
|
+
case instruction.first
|
27
|
+
when "not"
|
28
|
+
stack.push !stack.pop
|
29
|
+
when "jfalse"
|
30
|
+
jump_if_false instruction.last
|
31
|
+
when "jtrue"
|
32
|
+
jump_if_true instruction.last
|
33
|
+
when "lit", "array"
|
34
|
+
stack.push instruction.last
|
35
|
+
when "load"
|
36
|
+
stack.push context[instruction.last]
|
37
|
+
when "to_bool"
|
38
|
+
stack.push !!stack.pop
|
39
|
+
when "compare"
|
40
|
+
if instruction.last == "BETWEEN"
|
41
|
+
compare_BETWEEN
|
42
|
+
else
|
43
|
+
compare instruction.last
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def jump_if_false offset
|
49
|
+
if stack[-1] == false
|
50
|
+
adjusted_offset = offset - 1
|
51
|
+
@ip += adjusted_offset
|
52
|
+
else
|
53
|
+
stack.pop
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def jump_if_true offset
|
58
|
+
if stack[-1] == true
|
59
|
+
adjusted_offset = offset - 1
|
60
|
+
@ip += adjusted_offset
|
61
|
+
else
|
62
|
+
stack.pop
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def compare comparison
|
67
|
+
right = stack.pop
|
68
|
+
left = stack.pop
|
69
|
+
if left.nil? || right.nil?
|
70
|
+
stack.push false
|
71
|
+
else
|
72
|
+
stack.push send("compare_#{comparison}", left, right)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def compare_EQ left, right
|
77
|
+
left == right
|
78
|
+
end
|
79
|
+
|
80
|
+
def compare_GT left, right
|
81
|
+
left > right
|
82
|
+
end
|
83
|
+
|
84
|
+
def compare_LT left, right
|
85
|
+
left < right
|
86
|
+
end
|
87
|
+
|
88
|
+
def compare_IN left, right
|
89
|
+
right.include? left
|
90
|
+
end
|
91
|
+
|
92
|
+
def compare_NOTIN left, right
|
93
|
+
!right.include? left
|
94
|
+
end
|
95
|
+
|
96
|
+
def compare_BETWEEN
|
97
|
+
max = stack.pop
|
98
|
+
min = stack.pop
|
99
|
+
val = stack.pop
|
100
|
+
if max.nil? || min.nil? || val.nil?
|
101
|
+
stack.push false
|
102
|
+
else
|
103
|
+
result = val.between? min, max
|
104
|
+
stack.push result
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|