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