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
data/lib/predicator/parser.y
CHANGED
@@ -1,52 +1,120 @@
|
|
1
|
-
class Predicator::
|
1
|
+
class Predicator::Parser
|
2
|
+
|
2
3
|
options no_result_var
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
|
5
|
+
token TRUE FALSE LPAREN RPAREN LBRACKET RBRACKET
|
6
|
+
BANG NOT DOT COMMA AT AND OR
|
7
|
+
EQ GT LT BETWEEN IN PRESENT BLANK
|
8
|
+
INTEGER STRING IDENTIFIER
|
9
|
+
DATE DURATION AGO FROMNOW
|
10
|
+
STARTSWITH ENDSWITH
|
11
|
+
|
9
12
|
rule
|
10
13
|
predicate
|
11
|
-
:
|
12
|
-
| boolean_predicate
|
14
|
+
: boolean_predicate
|
13
15
|
| logical_predicate
|
14
|
-
|
|
15
|
-
|
|
16
|
-
;
|
17
|
-
relation_predicate
|
18
|
-
: value tEQ value { Predicator::Predicates::Equal.new val[0], val[2] }
|
19
|
-
| value tGT value { Predicator::Predicates::GreaterThan.new val[0], val[2] }
|
20
|
-
| value tLT value { Predicator::Predicates::LessThan.new val[0], val[2] }
|
21
|
-
| value tGEQ value { Predicator::Predicates::GreaterThanOrEqual.new val[0], val[2] }
|
22
|
-
| value tLEQ value { Predicator::Predicates::LessThanOrEqual.new val[0], val[2] }
|
23
|
-
| value tNEQ value { Predicator::Predicates::NotEqual.new val[0], val[2] }
|
16
|
+
| group_predicate
|
17
|
+
| comparison_predicate
|
24
18
|
;
|
25
19
|
boolean_predicate
|
26
|
-
:
|
27
|
-
|
|
20
|
+
: TRUE { AST::True.new true }
|
21
|
+
| FALSE { AST::False.new false }
|
22
|
+
| variable { AST::BooleanVariable.new val.first }
|
28
23
|
;
|
29
24
|
logical_predicate
|
30
|
-
:
|
31
|
-
| predicate
|
32
|
-
|
|
25
|
+
: BANG predicate { AST::Not.new val.last }
|
26
|
+
| predicate AND predicate { AST::And.new val.first, val.last }
|
27
|
+
| predicate OR predicate { AST::Or.new val.first, val.last }
|
28
|
+
| variable PRESENT { AST::Present.new val.first }
|
29
|
+
| variable BLANK { AST::Blank.new val.first }
|
30
|
+
;
|
31
|
+
group_predicate
|
32
|
+
: LPAREN predicate RPAREN { AST::Group.new val[1] }
|
33
|
+
;
|
34
|
+
comparison_predicate
|
35
|
+
: integer_comparison_predicate
|
36
|
+
| string_comparison_predicate
|
37
|
+
| date_comparison_predicate
|
38
|
+
;
|
39
|
+
integer_comparison_predicate
|
40
|
+
: variable EQ integer { AST::IntegerEqual.new val.first, val.last }
|
41
|
+
| variable GT integer { AST::IntegerGreaterThan.new val.first, val.last }
|
42
|
+
| variable LT integer { AST::IntegerLessThan.new val.first, val.last }
|
43
|
+
| variable BETWEEN integer AND integer { AST::IntegerBetween.new val.first, val[2], val.last }
|
44
|
+
| variable IN integer_array { AST::IntegerIn.new val.first, val.last }
|
45
|
+
| variable NOT IN integer_array { AST::IntegerNotIn.new val.first, val.last }
|
46
|
+
;
|
47
|
+
string_comparison_predicate
|
48
|
+
: variable EQ string { AST::StringEqual.new val.first, val.last }
|
49
|
+
| variable GT string { AST::StringGreaterThan.new val.first, val.last }
|
50
|
+
| variable LT string { AST::StringLessThan.new val.first, val.last }
|
51
|
+
| variable IN string_array { AST::StringIn.new val.first, val.last }
|
52
|
+
| variable NOT IN string_array { AST::StringNotIn.new val.first, val.last }
|
53
|
+
| variable STARTSWITH string { AST::StringStartsWith.new val.first, val.last }
|
54
|
+
| variable ENDSWITH string { AST::StringEndsWith.new val.first, val.last }
|
33
55
|
;
|
34
|
-
|
35
|
-
:
|
36
|
-
| variable
|
56
|
+
date_comparison_predicate
|
57
|
+
: variable EQ date { AST::DateEqual.new val.first, val.last }
|
58
|
+
| variable GT date { AST::DateGreaterThan.new val.first, val.last }
|
59
|
+
| variable LT date { AST::DateLessThan.new val.first, val.last }
|
60
|
+
| variable BETWEEN date AND date { AST::DateBetween.new val.first, val[2], val.last }
|
37
61
|
;
|
38
|
-
|
62
|
+
integer_array
|
63
|
+
: LBRACKET integer_array_contents RBRACKET { AST::IntegerArray.new val[1] }
|
64
|
+
;
|
65
|
+
integer_array_contents
|
66
|
+
: integer
|
67
|
+
| integer_array_contents COMMA integer { [val.first, val.last].flatten }
|
68
|
+
;
|
69
|
+
string_array
|
70
|
+
: LBRACKET string_array_contents RBRACKET { AST::StringArray.new val[1] }
|
71
|
+
;
|
72
|
+
string_array_contents
|
39
73
|
: string
|
40
|
-
|
|
74
|
+
| string_array_contents COMMA string { [val.first, val.last].flatten }
|
75
|
+
;
|
76
|
+
integer
|
77
|
+
: INTEGER { AST::Integer.new val.first.to_i }
|
41
78
|
;
|
42
79
|
string
|
43
|
-
:
|
80
|
+
: STRING { AST::String.new val.first }
|
81
|
+
;
|
82
|
+
date
|
83
|
+
: DATE { AST::Date.new val.first }
|
84
|
+
| duration AGO { AST::DateAgo.new val.first }
|
85
|
+
| duration FROMNOW { AST::DateFromNow.new val.first }
|
44
86
|
;
|
45
|
-
|
46
|
-
:
|
47
|
-
| tINTEGER { val[0].to_i }
|
48
|
-
| tDATE { Date.new *val[0] }
|
87
|
+
duration
|
88
|
+
: DURATION { AST::Duration.new val.first }
|
49
89
|
;
|
50
90
|
variable
|
51
|
-
:
|
91
|
+
: IDENTIFIER { AST::Variable.new val.first }
|
92
|
+
| variable DOT IDENTIFIER { AST::Variable.new [val.first, val.last].flatten.join(".") }
|
52
93
|
;
|
94
|
+
end
|
95
|
+
|
96
|
+
---- inner
|
97
|
+
def initialize
|
98
|
+
@lexer = Lexer.new
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse string
|
102
|
+
@lexer.parse string
|
103
|
+
do_parse
|
104
|
+
end
|
105
|
+
|
106
|
+
def next_token
|
107
|
+
@lexer.next_token
|
108
|
+
end
|
109
|
+
|
110
|
+
def on_error type, val, values
|
111
|
+
super
|
112
|
+
rescue Racc::ParseError
|
113
|
+
trace = values.each_with_index.map{|l, i| "#{' ' * i}#{l}"}
|
114
|
+
raise ParseError, "\nparse error on value #{val.inspect}\n#{trace.join("\n")}"
|
115
|
+
end
|
116
|
+
|
117
|
+
---- header
|
118
|
+
require "predicator/lexer.rex"
|
119
|
+
require "predicator/visitors"
|
120
|
+
require "predicator/ast"
|
data/lib/predicator/version.rb
CHANGED
@@ -0,0 +1,113 @@
|
|
1
|
+
module Predicator
|
2
|
+
module Visitors
|
3
|
+
class Dot < Visitor
|
4
|
+
attr_reader :nodes, :edges
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@nodes = []
|
8
|
+
@edges = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def accept node
|
12
|
+
super
|
13
|
+
<<-eodot
|
14
|
+
digraph parse_tree {
|
15
|
+
size="8,5"
|
16
|
+
node [shape = none];
|
17
|
+
edge [dir = none];
|
18
|
+
#{@nodes.join "\n "}
|
19
|
+
#{@edges.join "\n "}
|
20
|
+
}
|
21
|
+
eodot
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def visit_children node
|
27
|
+
node.children.each do |c|
|
28
|
+
@edges << "#{node.object_id} -> #{c.object_id};"
|
29
|
+
end
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_EQ node
|
34
|
+
@nodes << "#{node.object_id} [label=\"=\"];"
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit_GT node
|
39
|
+
@nodes << "#{node.object_id} [label=\">\"];"
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_LT node
|
44
|
+
@nodes << "#{node.object_id} [label=\"<\"];"
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def visit_BETWEEN node
|
49
|
+
@nodes << "#{node.object_id} [label=\"between\"];"
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit_AND node
|
54
|
+
@nodes << "#{node.object_id} [label=\"and\"];"
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def visit_OR node
|
59
|
+
@nodes << "#{node.object_id} [label=\"or\"];"
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
def visit_NOT node
|
64
|
+
@nodes << "#{node.object_id} [label=\"!\"];"
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def visit_GROUP node
|
69
|
+
@nodes << "#{node.object_id} [label=\"( )\"];"
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def visit_DATEAGO node
|
74
|
+
@nodes << "#{node.object_id} [label=\"ago\"];"
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
def visit_DATEFROMNOW node
|
79
|
+
@nodes << "#{node.object_id} [label=\"from now\"];"
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
def visit_STRSTARTSWITH node
|
84
|
+
@nodes << "#{node.object_id} [label=\"starts with\"];"
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_STRENDSWITH node
|
89
|
+
@nodes << "#{node.object_id} [label=\"ends with\"];"
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
93
|
+
def visit_STRING node
|
94
|
+
value = node.left
|
95
|
+
@nodes << "#{node.object_id} [label=\"'#{value}'\"];"
|
96
|
+
end
|
97
|
+
|
98
|
+
def terminal node
|
99
|
+
value = node.left.to_s
|
100
|
+
@nodes << "#{node.object_id} [label=\"#{value}\"];"
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_source source, parent
|
104
|
+
ast = Predicator.parse source
|
105
|
+
vis = Dot.new
|
106
|
+
vis.accept ast
|
107
|
+
@nodes += vis.nodes
|
108
|
+
@edges += vis.edges
|
109
|
+
@edges << "#{parent.object_id} -> #{ast.object_id};"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module Predicator
|
2
|
+
module Visitors
|
3
|
+
class Instructions < Visitor
|
4
|
+
attr_reader :instructions
|
5
|
+
|
6
|
+
def initialize instructions=[]
|
7
|
+
@instructions = instructions
|
8
|
+
@label_locations = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def accept ast
|
12
|
+
super
|
13
|
+
update_jumps
|
14
|
+
remove_labels
|
15
|
+
@instructions
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def visit_AND node
|
21
|
+
visit node.left
|
22
|
+
@instructions.push jump_instruction("false", node)
|
23
|
+
visit node.right
|
24
|
+
@instructions.push label_instruction(node)
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_OR node
|
28
|
+
visit node.left
|
29
|
+
@instructions.push jump_instruction("true", node)
|
30
|
+
visit node.right
|
31
|
+
@instructions.push label_instruction(node)
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_NOT node
|
35
|
+
super
|
36
|
+
@instructions.push ["not"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_EQ node
|
40
|
+
visit node.left
|
41
|
+
add_typecast_to_instructions node
|
42
|
+
visit node.right
|
43
|
+
@instructions.push ["compare", "EQ"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_GT node
|
47
|
+
visit node.left
|
48
|
+
add_typecast_to_instructions node
|
49
|
+
visit node.right
|
50
|
+
@instructions.push ["compare", "GT"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit_LT node
|
54
|
+
visit node.left
|
55
|
+
add_typecast_to_instructions node
|
56
|
+
visit node.right
|
57
|
+
@instructions.push ["compare", "LT"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_BETWEEN node
|
61
|
+
visit node.left
|
62
|
+
add_typecast_to_instructions node
|
63
|
+
visit node.middle
|
64
|
+
visit node.right
|
65
|
+
@instructions.push ["compare", "BETWEEN"]
|
66
|
+
end
|
67
|
+
|
68
|
+
def visit_IN node
|
69
|
+
visit node.left
|
70
|
+
add_typecast_to_instructions node
|
71
|
+
visit node.right
|
72
|
+
@instructions.push ["compare", "IN"]
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_NOTIN node
|
76
|
+
visit node.left
|
77
|
+
add_typecast_to_instructions node
|
78
|
+
visit node.right
|
79
|
+
@instructions.push ["compare", "NOTIN"]
|
80
|
+
end
|
81
|
+
|
82
|
+
def visit_STRSTARTSWITH node
|
83
|
+
visit node.left
|
84
|
+
add_typecast_to_instructions node
|
85
|
+
visit node.right
|
86
|
+
@instructions.push ["compare", "STARTSWITH"]
|
87
|
+
end
|
88
|
+
|
89
|
+
def visit_STRENDSWITH node
|
90
|
+
visit node.left
|
91
|
+
add_typecast_to_instructions node
|
92
|
+
visit node.right
|
93
|
+
@instructions.push ["compare", "ENDSWITH"]
|
94
|
+
end
|
95
|
+
|
96
|
+
def visit_ARRAY node
|
97
|
+
contents = node.left.map{ |item| item.left }
|
98
|
+
@instructions.push ["array", contents]
|
99
|
+
end
|
100
|
+
|
101
|
+
def visit_VARIABLE node
|
102
|
+
@instructions.push ["load", node.symbol]
|
103
|
+
end
|
104
|
+
|
105
|
+
def visit_BOOL node
|
106
|
+
super
|
107
|
+
@instructions.push ["to_bool"]
|
108
|
+
end
|
109
|
+
|
110
|
+
def visit_DATE node
|
111
|
+
@instructions.push ["lit", node.symbol]
|
112
|
+
@instructions.push ["to_date"]
|
113
|
+
end
|
114
|
+
|
115
|
+
def visit_DATEAGO node
|
116
|
+
visit node.left
|
117
|
+
@instructions.push ["date_ago"]
|
118
|
+
end
|
119
|
+
|
120
|
+
def visit_DATEFROMNOW node
|
121
|
+
visit node.left
|
122
|
+
@instructions.push ["date_from_now"]
|
123
|
+
end
|
124
|
+
|
125
|
+
def visit_DURATION node
|
126
|
+
as_seconds = node.symbol.to_i * 24 * 60 * 60
|
127
|
+
@instructions.push ["lit", as_seconds]
|
128
|
+
end
|
129
|
+
|
130
|
+
def visit_BLANK node
|
131
|
+
visit node.left
|
132
|
+
@instructions.push ["blank"]
|
133
|
+
end
|
134
|
+
|
135
|
+
def visit_PRESENT node
|
136
|
+
visit node.left
|
137
|
+
@instructions.push ["present"]
|
138
|
+
end
|
139
|
+
|
140
|
+
def terminal node
|
141
|
+
@instructions.push ["lit", node.symbol]
|
142
|
+
end
|
143
|
+
|
144
|
+
def add_typecast_to_instructions node
|
145
|
+
type = case node.right.type.to_s
|
146
|
+
when /INT/ then "to_int"
|
147
|
+
when /STR/ then "to_str"
|
148
|
+
when /DATE/ then "to_date"
|
149
|
+
end
|
150
|
+
@instructions.push [type]
|
151
|
+
end
|
152
|
+
|
153
|
+
def jump_instruction condition, node
|
154
|
+
["j#{condition}", node.object_id.to_s]
|
155
|
+
end
|
156
|
+
|
157
|
+
def label_instruction node
|
158
|
+
label = node.object_id.to_s
|
159
|
+
@label_locations[label] = @instructions.size
|
160
|
+
["label", label]
|
161
|
+
end
|
162
|
+
|
163
|
+
def update_jumps
|
164
|
+
@instructions.each_with_index do |inst, idx|
|
165
|
+
next unless inst.first =~ /^j/
|
166
|
+
label = inst.pop
|
167
|
+
offset = calculate_offset idx, @label_locations[label]
|
168
|
+
inst.push offset
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def calculate_offset from_idx, to_idx
|
173
|
+
offset = to_idx - from_idx
|
174
|
+
num_labels = @instructions[from_idx...to_idx].count{ |i| i.first == "label" }
|
175
|
+
offset - num_labels
|
176
|
+
end
|
177
|
+
|
178
|
+
def remove_labels
|
179
|
+
@instructions.delete_if{ |inst| inst.first == "label" }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|