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
@@ -1,52 +1,120 @@
1
- class Predicator::GeneratedParser
1
+ class Predicator::Parser
2
+
2
3
  options no_result_var
3
- prechigh
4
- right tBANG
5
- left tAND tOR
6
- preclow
7
- token tTRUE tFALSE tSTRING tFLOAT tINTEGER tDATE tIDENTIFIER tAND tOR tBETWEEN
8
- tDOT tLPAREN tRPAREN tBANG tEQ tNEQ tLEQ tGEQ tLT tGT
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
- : relation_predicate
12
- | boolean_predicate
14
+ : boolean_predicate
13
15
  | logical_predicate
14
- | value tBETWEEN value tAND value { Predicator::Predicates::Between.new val[0], val[2], val[4] }
15
- | tLPAREN predicate tRPAREN
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
- : tTRUE { Predicator::Predicates::True.new }
27
- | tFALSE { Predicator::Predicates::False.new }
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
- : predicate tAND predicate { Predicator::Predicates::And.new [val[0], val[2]] }
31
- | predicate tOR predicate { Predicator::Predicates::Or.new [val[0], val[2]] }
32
- | tBANG predicate { Predicator::Predicates::Not.new val[0] }
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
- value
35
- : scalar
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
- scalar
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
- | literal
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
- : tSTRING { val[0] }
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
- literal
46
- : tFLOAT { val[0].to_f }
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
- : tIDENTIFIER tDOT tIDENTIFIER { Predicator::Variable.new val[0], val[2] }
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"
@@ -1,3 +1,3 @@
1
1
  module Predicator
2
- VERSION = "0.3.0"
2
+ VERSION = "1.2.1"
3
3
  end
@@ -0,0 +1,5 @@
1
+ require "predicator/visitors/visitor"
2
+ require "predicator/visitors/each"
3
+ require "predicator/visitors/dot"
4
+ require "predicator/visitors/string"
5
+ require "predicator/visitors/instructions"
@@ -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,16 @@
1
+ module Predicator
2
+ module Visitors
3
+ class Each < Visitor
4
+ attr_reader :block
5
+
6
+ def initialize block
7
+ @block = block
8
+ end
9
+
10
+ def visit node
11
+ super
12
+ block.call node
13
+ end
14
+ end
15
+ end
16
+ 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