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
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
|