aql 0.0.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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +18 -0
- data/Gemfile +6 -0
- data/Gemfile.devtools +64 -0
- data/Guardfile +18 -0
- data/LICENSE +20 -0
- data/README.md +86 -0
- data/Rakefile +2 -0
- data/TODO +1 -0
- data/aql.gemspec +23 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/roodi.yml +18 -0
- data/config/site.reek +100 -0
- data/config/yardstick.yml +2 -0
- data/examples/aql.rb +31 -0
- data/lib/aql.rb +62 -0
- data/lib/aql/buffer.rb +129 -0
- data/lib/aql/constants.rb +19 -0
- data/lib/aql/node.rb +45 -0
- data/lib/aql/node/attribute.rb +25 -0
- data/lib/aql/node/block.rb +28 -0
- data/lib/aql/node/call.rb +83 -0
- data/lib/aql/node/literal.rb +48 -0
- data/lib/aql/node/literal/composed.rb +10 -0
- data/lib/aql/node/literal/composed/document.rb +59 -0
- data/lib/aql/node/literal/composed/list.rb +42 -0
- data/lib/aql/node/literal/primitive.rb +10 -0
- data/lib/aql/node/literal/primitive/number.rb +28 -0
- data/lib/aql/node/literal/primitive/string.rb +27 -0
- data/lib/aql/node/literal/singleton.rb +51 -0
- data/lib/aql/node/name.rb +31 -0
- data/lib/aql/node/null.rb +21 -0
- data/lib/aql/node/operation.rb +20 -0
- data/lib/aql/node/operation/binary.rb +33 -0
- data/lib/aql/node/operation/for.rb +77 -0
- data/lib/aql/node/operation/limit.rb +42 -0
- data/lib/aql/node/operation/nary.rb +57 -0
- data/lib/aql/node/operation/unary.rb +75 -0
- data/lib/aql/node/operator.rb +20 -0
- data/lib/aql/node/operator/assignment.rb +28 -0
- data/lib/aql/node/operator/binary.rb +92 -0
- data/lib/aql/node/operator/nary.rb +48 -0
- data/lib/aql/node/operator/ternary.rb +30 -0
- data/lib/aql/node/operator/unary.rb +39 -0
- data/spec/shared/aql_behavior.rb +7 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/aql_helper.rb +12 -0
- data/spec/unit/aql/buffer/append_spec.rb +59 -0
- data/spec/unit/aql/buffer/binary_spec.rb +22 -0
- data/spec/unit/aql/buffer/class_methods/utf8_encode_spec.rb +21 -0
- data/spec/unit/aql/buffer/content_spec.rb +24 -0
- data/spec/unit/aql/buffer/delimited_spec.rb +31 -0
- data/spec/unit/aql/buffer/parentheses_spec.rb +33 -0
- data/spec/unit/aql/buffer/wrap_delimited_spec.rb +42 -0
- data/spec/unit/aql/class_methods/literal_node_spec.rb +15 -0
- data/spec/unit/aql/class_methods/name_node_spec.rb +19 -0
- data/spec/unit/aql/node/attribute/aql_spec.rb +22 -0
- data/spec/unit/aql/node/block/aql_spec.rb +28 -0
- data/spec/unit/aql/node/call/aql_spec.rb +39 -0
- data/spec/unit/aql/node/literal/class_methods/build_spec.rb +139 -0
- data/spec/unit/aql/node/literal/class_methods/construct_spec.rb +16 -0
- data/spec/unit/aql/node/literal/class_methods/handle_spec.rb +22 -0
- data/spec/unit/aql/node/literal/composed/document/aql_spec.rb +32 -0
- data/spec/unit/aql/node/literal/composed/document/attribute/aql_spec.rb +15 -0
- data/spec/unit/aql/node/literal/composed/document/class_methods/construct_spec.rb +24 -0
- data/spec/unit/aql/node/literal/composed/list/aql_spec.rb +20 -0
- data/spec/unit/aql/node/literal/composed/list/class_methods/construct_spec.rb +37 -0
- data/spec/unit/aql/node/literal/primitive/number/aql_spec.rb +23 -0
- data/spec/unit/aql/node/literal/primitive/string/aql_spec.rb +21 -0
- data/spec/unit/aql/node/literal/singleton/aql_spec.rb +14 -0
- data/spec/unit/aql/node/literal/singleton/class_methods/construct_spec.rb +30 -0
- data/spec/unit/aql/node/name/aql_spec.rb +33 -0
- data/spec/unit/aql/node/null/aql_spec.rb +7 -0
- data/spec/unit/aql/node/operation/binary/let/aql_spec.rb +10 -0
- data/spec/unit/aql/node/operation/for/aql_spec.rb +18 -0
- data/spec/unit/aql/node/operation/keyword_spec.rb +16 -0
- data/spec/unit/aql/node/operation/limit/aql_spec.rb +19 -0
- data/spec/unit/aql/node/operation/nary/aql_spec.rb +16 -0
- data/spec/unit/aql/node/operation/nary/collect/into/aql_spec.rb +11 -0
- data/spec/unit/aql/node/operation/nary/sort/aql_spec.rb +22 -0
- data/spec/unit/aql/node/operation/unary/aql_spec.rb +12 -0
- data/spec/unit/aql/node/operation/unary/direction/aql_spec.rb +14 -0
- data/spec/unit/aql/node/operation/unary/filter/aql_spec.rb +20 -0
- data/spec/unit/aql/node/operation/unary/return/aql_spec.rb +17 -0
- data/spec/unit/aql/node/operator/assignment/aql_spec.rb +10 -0
- data/spec/unit/aql/node/operator/binary/aql_spec.rb +15 -0
- data/spec/unit/aql/node/operator/nary/aql_spec.rb +31 -0
- data/spec/unit/aql/node/operator/operator_spec.rb +18 -0
- data/spec/unit/aql/node/operator/ternary/aql_spec.rb +12 -0
- data/spec/unit/aql/node/operator/unary/aql_spec.rb +12 -0
- data/spec/unit/aql/node/visit_spec.rb +27 -0
- metadata +262 -0
data/lib/aql/buffer.rb
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
# The emit buffer
|
|
3
|
+
class Buffer
|
|
4
|
+
include Equalizer.new(:contents)
|
|
5
|
+
|
|
6
|
+
# Initialize object
|
|
7
|
+
#
|
|
8
|
+
# @return [undefined]
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
#
|
|
12
|
+
def initialize
|
|
13
|
+
@buffer = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Emit wrapped delimited
|
|
17
|
+
#
|
|
18
|
+
# @param [String] open
|
|
19
|
+
# @param [Enumerable<Node>] nodes
|
|
20
|
+
# @param [String] close
|
|
21
|
+
#
|
|
22
|
+
# @return [self]
|
|
23
|
+
#
|
|
24
|
+
# @api private
|
|
25
|
+
#
|
|
26
|
+
def wrap_delimited(open, nodes, close)
|
|
27
|
+
parentheses(open, close) do
|
|
28
|
+
delimited(nodes)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Emit block in parentheses
|
|
33
|
+
#
|
|
34
|
+
# @return [self]
|
|
35
|
+
#
|
|
36
|
+
# @api private
|
|
37
|
+
#
|
|
38
|
+
def parentheses(open = '(', close = ')')
|
|
39
|
+
append(open)
|
|
40
|
+
yield
|
|
41
|
+
append(close)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Emit delimited nodes
|
|
45
|
+
#
|
|
46
|
+
# @param [Enumerable<Node>] nodes
|
|
47
|
+
#
|
|
48
|
+
# @return [self]
|
|
49
|
+
#
|
|
50
|
+
# @api private
|
|
51
|
+
#
|
|
52
|
+
def delimited(nodes, delimiter = ', ')
|
|
53
|
+
max = nodes.length - 1
|
|
54
|
+
nodes.each_with_index do |element, index|
|
|
55
|
+
element.visit(self)
|
|
56
|
+
append(delimiter) if index < max
|
|
57
|
+
end
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Emit binary
|
|
62
|
+
#
|
|
63
|
+
# @param [Node] left
|
|
64
|
+
# @param [Symbol] operator
|
|
65
|
+
# @param [Node] right
|
|
66
|
+
#
|
|
67
|
+
# @return [self]
|
|
68
|
+
#
|
|
69
|
+
# @api private
|
|
70
|
+
#
|
|
71
|
+
def binary(left, operator, right)
|
|
72
|
+
parentheses do
|
|
73
|
+
left.visit(self)
|
|
74
|
+
append(" #{operator} ")
|
|
75
|
+
right.visit(self)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Append content to buffer
|
|
80
|
+
#
|
|
81
|
+
# @param [String] content
|
|
82
|
+
#
|
|
83
|
+
# @return [self]
|
|
84
|
+
#
|
|
85
|
+
# @api private
|
|
86
|
+
#
|
|
87
|
+
def append(content)
|
|
88
|
+
@buffer << self.class.utf8_encode(content)
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Return content
|
|
93
|
+
#
|
|
94
|
+
# @return [String]
|
|
95
|
+
#
|
|
96
|
+
# @api private
|
|
97
|
+
#
|
|
98
|
+
def content
|
|
99
|
+
@buffer.join.freeze
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if defined?(Encoding)
|
|
103
|
+
# Encode string in UTF-8
|
|
104
|
+
#
|
|
105
|
+
# @param [String] string
|
|
106
|
+
#
|
|
107
|
+
# @return [String]
|
|
108
|
+
#
|
|
109
|
+
# @api private
|
|
110
|
+
#
|
|
111
|
+
def self.utf8_encode(string)
|
|
112
|
+
string.encode(Encoding::UTF_8)
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
# Dummy encode string for rubies that do not support encoding hell
|
|
116
|
+
#
|
|
117
|
+
# @param [String] string
|
|
118
|
+
#
|
|
119
|
+
# @return [String]
|
|
120
|
+
#
|
|
121
|
+
# @api private
|
|
122
|
+
#
|
|
123
|
+
def self.utf8_encode(string)
|
|
124
|
+
string
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
# The list of AQL keywords from http://www.arangodb.org/manuals/current/Aql.html#AqlKeywords
|
|
3
|
+
KEYWORDS = IceNine.deep_freeze(%w(
|
|
4
|
+
FOR
|
|
5
|
+
RETURN
|
|
6
|
+
FILTER
|
|
7
|
+
SORT
|
|
8
|
+
LIMIT
|
|
9
|
+
LET
|
|
10
|
+
COLLECT
|
|
11
|
+
ASC
|
|
12
|
+
DESC
|
|
13
|
+
IN
|
|
14
|
+
INTO
|
|
15
|
+
NULL
|
|
16
|
+
TRUE
|
|
17
|
+
FALSE
|
|
18
|
+
).to_set)
|
|
19
|
+
end
|
data/lib/aql/node.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
# Abstract base class for AQL nodes
|
|
3
|
+
class Node
|
|
4
|
+
include Adamantium, AbstractType
|
|
5
|
+
|
|
6
|
+
# Return source representation
|
|
7
|
+
#
|
|
8
|
+
# @return [String]
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
#
|
|
12
|
+
def aql
|
|
13
|
+
emitter = Buffer.new
|
|
14
|
+
emit(emitter)
|
|
15
|
+
emitter.content
|
|
16
|
+
end
|
|
17
|
+
memoize :aql
|
|
18
|
+
|
|
19
|
+
# Visit node
|
|
20
|
+
#
|
|
21
|
+
# @param [Buffer] buffer
|
|
22
|
+
#
|
|
23
|
+
# @return [self]
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
#
|
|
27
|
+
def visit(buffer)
|
|
28
|
+
emit(buffer)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Emit AQL
|
|
35
|
+
#
|
|
36
|
+
# @param [Emitter] emitter
|
|
37
|
+
#
|
|
38
|
+
# @return [undefined]
|
|
39
|
+
#
|
|
40
|
+
# @api private
|
|
41
|
+
#
|
|
42
|
+
abstract_method :emit
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
class Node
|
|
3
|
+
# Attribute node
|
|
4
|
+
class Attribute < self
|
|
5
|
+
include Concord.new(:target, :name)
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Emit attribute
|
|
10
|
+
#
|
|
11
|
+
# @param [Buffer] buffer
|
|
12
|
+
#
|
|
13
|
+
# @return [undefined]
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
def emit(buffer)
|
|
18
|
+
target.visit(buffer)
|
|
19
|
+
buffer.append('.')
|
|
20
|
+
name.visit(buffer)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
class Node
|
|
3
|
+
# Block of AQL statements
|
|
4
|
+
class Block < self
|
|
5
|
+
include Concord.new(:elements)
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Emit contents
|
|
10
|
+
#
|
|
11
|
+
# @param [Buffer] buffer
|
|
12
|
+
#
|
|
13
|
+
# @return [undefined]
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
def emit(buffer)
|
|
18
|
+
local = elements
|
|
19
|
+
max = local.length - 1
|
|
20
|
+
local.each_with_index do |element, index|
|
|
21
|
+
element.visit(buffer)
|
|
22
|
+
buffer.append(' ') if index < max
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
class Node
|
|
3
|
+
# AST node for function call
|
|
4
|
+
class Call < self
|
|
5
|
+
include Concord.new(:name, :arguments)
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Emit node
|
|
10
|
+
#
|
|
11
|
+
# @param [Buffer] buffer
|
|
12
|
+
#
|
|
13
|
+
# @return [undefined]
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
def emit(buffer)
|
|
18
|
+
buffer.append(name)
|
|
19
|
+
buffer.parentheses do
|
|
20
|
+
emit_arguments_with_for_check(buffer)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Emit arguments with for check
|
|
25
|
+
#
|
|
26
|
+
# Handles edge case described in https://github.com/triAGENS/ArangoDB/issues/399
|
|
27
|
+
#
|
|
28
|
+
# @param [Buffer] buffer
|
|
29
|
+
#
|
|
30
|
+
# @return [undefined]
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
#
|
|
34
|
+
def emit_arguments_with_for_check(buffer)
|
|
35
|
+
if for_argument?
|
|
36
|
+
emit_arguments_with_extra_parentheses(buffer)
|
|
37
|
+
else
|
|
38
|
+
emit_arguments(buffer)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Emit arguments with parentheses
|
|
43
|
+
#
|
|
44
|
+
# @param [Buffer] buffer
|
|
45
|
+
#
|
|
46
|
+
# @return [undefined]
|
|
47
|
+
#
|
|
48
|
+
# @api private
|
|
49
|
+
#
|
|
50
|
+
def emit_arguments_with_extra_parentheses(buffer)
|
|
51
|
+
buffer.parentheses { emit_arguments(buffer) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Test for `for` body
|
|
55
|
+
#
|
|
56
|
+
# @return [true]
|
|
57
|
+
# if an instance of Node::Operation::For is the only argument
|
|
58
|
+
#
|
|
59
|
+
# @return [false]
|
|
60
|
+
# otherwise
|
|
61
|
+
#
|
|
62
|
+
# @api private
|
|
63
|
+
#
|
|
64
|
+
def for_argument?
|
|
65
|
+
local = arguments
|
|
66
|
+
local.length == 1 and local.first.kind_of?(Operation::For)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Emit arguments
|
|
70
|
+
#
|
|
71
|
+
# @param [Buffer] buffer
|
|
72
|
+
#
|
|
73
|
+
# @return [undefined]
|
|
74
|
+
#
|
|
75
|
+
# @api private
|
|
76
|
+
#
|
|
77
|
+
def emit_arguments(buffer)
|
|
78
|
+
buffer.delimited(arguments)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
class Node
|
|
3
|
+
# Abstract base class for literal nodes
|
|
4
|
+
class Literal < self
|
|
5
|
+
REGISTRY = {}
|
|
6
|
+
|
|
7
|
+
# Register handler
|
|
8
|
+
#
|
|
9
|
+
# @param [Class] klass
|
|
10
|
+
#
|
|
11
|
+
# @return [undefined]
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
#
|
|
15
|
+
def self.handle(klass)
|
|
16
|
+
REGISTRY[klass]=self
|
|
17
|
+
end
|
|
18
|
+
private_class_method :handle
|
|
19
|
+
|
|
20
|
+
# Build literal handler
|
|
21
|
+
#
|
|
22
|
+
# @param [Object] object
|
|
23
|
+
#
|
|
24
|
+
# @return [Node::Literal]
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
#
|
|
28
|
+
def self.build(object)
|
|
29
|
+
klass = object.class
|
|
30
|
+
handler = REGISTRY.fetch(klass) do
|
|
31
|
+
raise "No support for literal #{klass}"
|
|
32
|
+
end
|
|
33
|
+
handler.construct(object)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Construct object
|
|
37
|
+
#
|
|
38
|
+
# @return [Node::Literal]
|
|
39
|
+
#
|
|
40
|
+
# @api private
|
|
41
|
+
#
|
|
42
|
+
def self.construct(*args)
|
|
43
|
+
new(*args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module AQL
|
|
2
|
+
class Node
|
|
3
|
+
class Literal
|
|
4
|
+
class Composed
|
|
5
|
+
# Literal document
|
|
6
|
+
class Document < self
|
|
7
|
+
handle(Hash)
|
|
8
|
+
|
|
9
|
+
# Construct node from primitive
|
|
10
|
+
#
|
|
11
|
+
# @param [Hash] hash
|
|
12
|
+
#
|
|
13
|
+
# @return [Node::Literal::Document]
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
def self.construct(hash)
|
|
18
|
+
attributes = hash.map do |key, value|
|
|
19
|
+
Attribute.new(Literal.build(key), Literal.build(value))
|
|
20
|
+
end
|
|
21
|
+
new(attributes)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Literal document attribute
|
|
25
|
+
class Attribute < Literal
|
|
26
|
+
include Concord.new(:key, :value)
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Emit node
|
|
31
|
+
#
|
|
32
|
+
# @return [self]
|
|
33
|
+
#
|
|
34
|
+
# @api private
|
|
35
|
+
#
|
|
36
|
+
def emit(buffer)
|
|
37
|
+
key.visit(buffer)
|
|
38
|
+
buffer.append(': ')
|
|
39
|
+
value.visit(buffer)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Emit node
|
|
46
|
+
#
|
|
47
|
+
# @return [undefined]
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
#
|
|
51
|
+
def emit(buffer)
|
|
52
|
+
buffer.wrap_delimited('{', body, '}')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|