predicator 0.4.0 → 1.0.0
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 +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
data/lib/predicator/parser.y
CHANGED
@@ -1,57 +1,83 @@
|
|
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
|
8
|
+
INTEGER STRING IDENTIFIER
|
9
|
+
|
9
10
|
rule
|
10
11
|
predicate
|
11
12
|
: boolean_predicate
|
12
13
|
| logical_predicate
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| tLPAREN predicate tRPAREN { val[1] }
|
16
|
-
| value tBETWEEN value tAND value { Predicator::Predicates::Between.new val[0], val[2], val[4] }
|
14
|
+
| group_predicate
|
15
|
+
| comparison_predicate
|
17
16
|
;
|
18
17
|
boolean_predicate
|
19
|
-
:
|
20
|
-
|
|
18
|
+
: TRUE { AST::True.new true }
|
19
|
+
| FALSE { AST::False.new false }
|
20
|
+
| variable { AST::BooleanVariable.new val.first }
|
21
21
|
;
|
22
22
|
logical_predicate
|
23
|
-
:
|
24
|
-
| predicate
|
25
|
-
|
|
26
|
-
;
|
27
|
-
relation_predicate
|
28
|
-
: value tEQ value { Predicator::Predicates::Equal.new val[0], val[2] }
|
29
|
-
| value tGT value { Predicator::Predicates::GreaterThan.new val[0], val[2] }
|
30
|
-
| value tLT value { Predicator::Predicates::LessThan.new val[0], val[2] }
|
31
|
-
| value tGEQ value { Predicator::Predicates::GreaterThanOrEqual.new val[0], val[2] }
|
32
|
-
| value tLEQ value { Predicator::Predicates::LessThanOrEqual.new val[0], val[2] }
|
33
|
-
| value tNEQ value { Predicator::Predicates::NotEqual.new val[0], val[2] }
|
34
|
-
;
|
35
|
-
method_predicate
|
36
|
-
: value tDOT tBLANK { Predicator::Predicates::Method.new val[0], val[2] }
|
37
|
-
| value tDOT tPRESENT { Predicator::Predicates::Method.new val[0], val[2] }
|
23
|
+
: BANG predicate { AST::Not.new val.last }
|
24
|
+
| predicate AND predicate { AST::And.new val.first, val.last }
|
25
|
+
| predicate OR predicate { AST::Or.new val.first, val.last }
|
38
26
|
;
|
39
|
-
|
40
|
-
:
|
41
|
-
|
27
|
+
group_predicate
|
28
|
+
: LPAREN predicate RPAREN { AST::Group.new val[1] }
|
29
|
+
;
|
30
|
+
comparison_predicate
|
31
|
+
: value EQ value { AST::Equal.new val.first, val.last }
|
32
|
+
| value GT value { AST::GreaterThan.new val.first, val.last }
|
33
|
+
| value LT value { AST::LessThan.new val.first, val.last }
|
34
|
+
| value BETWEEN value AND value { AST::Between.new val.first, val[2], val.last }
|
35
|
+
| value IN array { AST::In.new val.first, val.last }
|
36
|
+
| value NOT IN array { AST::NotIn.new val.first, val.last }
|
42
37
|
;
|
43
|
-
|
44
|
-
:
|
45
|
-
| literal
|
38
|
+
array
|
39
|
+
: LBRACKET array_contents RBRACKET { AST::Array.new val[1] }
|
46
40
|
;
|
47
|
-
|
48
|
-
:
|
41
|
+
array_contents
|
42
|
+
: literal
|
43
|
+
| array_contents COMMA literal { [val.first, val.last].flatten }
|
44
|
+
;
|
45
|
+
value
|
46
|
+
: literal
|
47
|
+
| variable
|
49
48
|
;
|
50
49
|
literal
|
51
|
-
:
|
52
|
-
|
|
53
|
-
| tDATE { Date.new *val[0] }
|
50
|
+
: STRING { AST::String.new val.first }
|
51
|
+
| INTEGER { AST::Integer.new val.first.to_i }
|
54
52
|
;
|
55
53
|
variable
|
56
|
-
:
|
54
|
+
: IDENTIFIER { AST::Variable.new val.first }
|
55
|
+
| variable DOT IDENTIFIER { AST::Variable.new [val.first, val.last].flatten.join(".") }
|
57
56
|
;
|
57
|
+
end
|
58
|
+
|
59
|
+
---- inner
|
60
|
+
def initialize
|
61
|
+
@lexer = Lexer.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse string
|
65
|
+
@lexer.parse string
|
66
|
+
do_parse
|
67
|
+
end
|
68
|
+
|
69
|
+
def next_token
|
70
|
+
@lexer.next_token
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_error type, val, values
|
74
|
+
super
|
75
|
+
rescue Racc::ParseError
|
76
|
+
trace = values.each_with_index.map{|l, i| "#{' ' * i}#{l}"}
|
77
|
+
raise ParseError, "\nparse error on value #{val.inspect}\n#{trace.join("\n")}"
|
78
|
+
end
|
79
|
+
|
80
|
+
---- header
|
81
|
+
require "predicator/lexer.rex"
|
82
|
+
require "predicator/visitors"
|
83
|
+
require "predicator/ast"
|
data/lib/predicator/version.rb
CHANGED
@@ -0,0 +1,100 @@
|
|
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 binary node
|
27
|
+
node.children.each do |c|
|
28
|
+
@edges << "#{node.object_id} -> #{c.object_id};"
|
29
|
+
end
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :ternary, :binary
|
34
|
+
|
35
|
+
def unary node
|
36
|
+
@edges << "#{node.object_id} -> #{node.left.object_id};"
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit_EQ node
|
41
|
+
@nodes << "#{node.object_id} [label=\"=\"];"
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_GT node
|
46
|
+
@nodes << "#{node.object_id} [label=\">\"];"
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def visit_LT node
|
51
|
+
@nodes << "#{node.object_id} [label=\"<\"];"
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_BETWEEN node
|
56
|
+
@nodes << "#{node.object_id} [label=\"between\"];"
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_AND node
|
61
|
+
@nodes << "#{node.object_id} [label=\"and\"];"
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
def visit_OR node
|
66
|
+
@nodes << "#{node.object_id} [label=\"or\"];"
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_NOT node
|
71
|
+
@nodes << "#{node.object_id} [label=\"!\"];"
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_GROUP node
|
76
|
+
@nodes << "#{node.object_id} [label=\"( )\"];"
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_STRING node
|
81
|
+
value = node.left
|
82
|
+
@nodes << "#{node.object_id} [label=\"'#{value}'\"];"
|
83
|
+
end
|
84
|
+
|
85
|
+
def terminal node
|
86
|
+
value = node.left.to_s
|
87
|
+
@nodes << "#{node.object_id} [label=\"#{value}\"];"
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_source source, parent
|
91
|
+
ast = Predicator.parse source
|
92
|
+
vis = Dot.new
|
93
|
+
vis.accept ast
|
94
|
+
@nodes += vis.nodes
|
95
|
+
@edges += vis.edges
|
96
|
+
@edges << "#{parent.object_id} -> #{ast.object_id};"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,117 @@
|
|
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
|
+
super
|
41
|
+
@instructions.push ["compare", "EQ"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def visit_GT node
|
45
|
+
super
|
46
|
+
@instructions.push ["compare", "GT"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_LT node
|
50
|
+
super
|
51
|
+
@instructions.push ["compare", "LT"]
|
52
|
+
end
|
53
|
+
|
54
|
+
def visit_BETWEEN node
|
55
|
+
super
|
56
|
+
@instructions.push ["compare", "BETWEEN"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_IN node
|
60
|
+
super
|
61
|
+
@instructions.push ["compare", "IN"]
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_NOTIN node
|
65
|
+
super
|
66
|
+
@instructions.push ["compare", "NOTIN"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def visit_ARRAY node
|
70
|
+
contents = node.left.map{ |item| item.left }
|
71
|
+
@instructions.push ["array", contents]
|
72
|
+
end
|
73
|
+
|
74
|
+
def visit_VARIABLE node
|
75
|
+
@instructions.push ["load", node.symbol]
|
76
|
+
end
|
77
|
+
|
78
|
+
def visit_BOOL node
|
79
|
+
super
|
80
|
+
@instructions.push ["to_bool"]
|
81
|
+
end
|
82
|
+
|
83
|
+
def terminal node
|
84
|
+
@instructions.push ["lit", node.symbol]
|
85
|
+
end
|
86
|
+
|
87
|
+
def jump_instruction condition, node
|
88
|
+
["j#{condition}", node.object_id.to_s]
|
89
|
+
end
|
90
|
+
|
91
|
+
def label_instruction node
|
92
|
+
label = node.object_id.to_s
|
93
|
+
@label_locations[label] = @instructions.size
|
94
|
+
["label", label]
|
95
|
+
end
|
96
|
+
|
97
|
+
def update_jumps
|
98
|
+
@instructions.each_with_index do |inst, idx|
|
99
|
+
next unless inst.first =~ /^j/
|
100
|
+
label = inst.pop
|
101
|
+
offset = calculate_offset idx, @label_locations[label]
|
102
|
+
inst.push offset
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def calculate_offset from_idx, to_idx
|
107
|
+
offset = to_idx - from_idx
|
108
|
+
num_labels = @instructions[from_idx...to_idx].count{ |i| i.first == "label" }
|
109
|
+
offset - num_labels
|
110
|
+
end
|
111
|
+
|
112
|
+
def remove_labels
|
113
|
+
@instructions.delete_if{ |inst| inst.first == "label" }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Predicator
|
2
|
+
module Visitors
|
3
|
+
class String < Visitor
|
4
|
+
private
|
5
|
+
|
6
|
+
def terminal node
|
7
|
+
node.left.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_STRING node
|
11
|
+
"'#{node.left}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_ARRAY node
|
15
|
+
contents = node.left.map{ |item| visit item }.join(", ")
|
16
|
+
"[#{contents}]"
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit_NOT node
|
20
|
+
"!#{visit node.left}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit_GROUP node
|
24
|
+
"(#{visit node.left})"
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_EQ node
|
28
|
+
[visit(node.left), " = ", visit(node.right)].join
|
29
|
+
end
|
30
|
+
|
31
|
+
def visit_GT node
|
32
|
+
[visit(node.left), " > ", visit(node.right)].join
|
33
|
+
end
|
34
|
+
|
35
|
+
def visit_LT node
|
36
|
+
[visit(node.left), " < ", visit(node.right)].join
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_AND node
|
40
|
+
[visit(node.left), " and ", visit(node.right)].join
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_OR node
|
44
|
+
[visit(node.left), " or ", visit(node.right)].join
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_BETWEEN node
|
48
|
+
[visit(node.left), " between ", visit(node.middle), " and ", visit(node.right)].join
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_IN node
|
52
|
+
[visit(node.left), " in ", visit(node.right)].join
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_NOTIN node
|
56
|
+
[visit(node.left), " not in ", visit(node.right)].join
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Predicator
|
2
|
+
module Visitors
|
3
|
+
class Visitor
|
4
|
+
DISPATCH_CACHE = Hash.new { |h,k|
|
5
|
+
h[k] = "visit_#{k}"
|
6
|
+
}
|
7
|
+
|
8
|
+
def accept node
|
9
|
+
visit node
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def visit node
|
15
|
+
send DISPATCH_CACHE[node.type], node
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_children node
|
19
|
+
node.children.each {|child| visit child}
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_NOT node; visit_children node; end
|
23
|
+
def visit_GROUP node; visit_children node; end
|
24
|
+
def visit_BOOL node; visit_children node; end
|
25
|
+
|
26
|
+
def visit_EQ node; visit_children node; end
|
27
|
+
def visit_GT node; visit_children node; end
|
28
|
+
def visit_LT node; visit_children node; end
|
29
|
+
def visit_AND node; visit_children node; end
|
30
|
+
def visit_OR node; visit_children node; end
|
31
|
+
def visit_IN node; visit_children node; end
|
32
|
+
def visit_NOTIN node; visit_children node; end
|
33
|
+
|
34
|
+
def visit_BETWEEN node; visit_children node; end
|
35
|
+
|
36
|
+
def terminal node; end
|
37
|
+
def visit_TRUE node; terminal node; end
|
38
|
+
def visit_FALSE node; terminal node; end
|
39
|
+
def visit_INTEGER node; terminal node; end
|
40
|
+
def visit_STRING node; terminal node; end
|
41
|
+
def visit_VARIABLE node; terminal node; end
|
42
|
+
|
43
|
+
def visit_ARRAY node
|
44
|
+
node.left.each{ |item| visit item }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|