predicator 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/predicator.svg)](http://badge.fury.io/rb/predicator)
|
2
|
-
[![Build Status](https://travis-ci.org/
|
2
|
+
[![Build Status](https://travis-ci.org/predicator/predicator.svg?branch=master)](https://travis-ci.org/predicator/predicator)
|
3
3
|
[![Dependency Status](https://img.shields.io/gemnasium/johnnyt/predicator.svg)](https://gemnasium.com/johnnyt/predicator)
|
4
|
-
[![Coverage Status](https://coveralls.io/repos/github/
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/github/predicator/predicator/badge.svg?branch=master)](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
|