gammo 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,250 @@
1
+ require 'gammo/xpath/ast/value'
2
+
3
+ module Gammo
4
+ module XPath
5
+ module AST
6
+ # Class for representing a binary expression.
7
+ # @!visibility private
8
+ class BinaryExpr
9
+ # Constructs a binary expression by given "a" and "b".
10
+ # @param [Gammo::AST::Value, Gammo::AST:NodeSet] a
11
+ # @param [Gammo::AST::Value, Gammo::AST:NodeSet] b
12
+ # @!visibility private
13
+ def initialize(a, b)
14
+ @a = a
15
+ @b = b
16
+ end
17
+
18
+ # @!visibility private
19
+ def evaluate(context)
20
+ raise NotImplementedError, "BinaryExpr#evaluate must be implemented"
21
+ end
22
+
23
+ private
24
+
25
+ # @return [Array<Gammo::AST::Value...>]
26
+ # @!visibility private
27
+ def evaluate_values(context)
28
+ [@a.evaluate(context), @b.evaluate(context.dup)]
29
+ end
30
+ end
31
+
32
+ # Class for representing a binary expression that returns a boolean.
33
+ # @!visibility private
34
+ class BoolExpr < BinaryExpr
35
+ # @!visibility private
36
+ def evaluate(context)
37
+ compare(context, *evaluate_values(context))
38
+ end
39
+
40
+ private
41
+
42
+ # Compares both values and returns a boolean.
43
+ # @param [Context] context
44
+ # @param [Value] left
45
+ # @param [Value] right
46
+ # @return [TrueClass, FalseClass]
47
+ # @!visibility private
48
+ def compare(context, left, right)
49
+ return compare_with_node_set(
50
+ context, left.to_node_set(context), right) if left.node_set?
51
+ return compare_with_node_set(
52
+ context, right.to_node_set(context), left, reverse: true) if right.node_set?
53
+ do_compare(left, right)
54
+ end
55
+
56
+ # @!visibility private
57
+ def compare_with_node_set(context, node_set, value, reverse: false)
58
+ if value.node_set?
59
+ node_set.each do |lnode|
60
+ ls = string_from_node(lnode)
61
+ value.to_node_set(context).each do |rnode|
62
+ return true if compare(context, ls, string_from_node(rnode))
63
+ end
64
+ end
65
+ end
66
+ if value.number?
67
+ node_set.each do |node|
68
+ n = number_from_node(node)
69
+ return true if compare(context, *(reverse ? [value, n] : [n, value]))
70
+ end
71
+ return false
72
+ end
73
+ if value.string?
74
+ node_set.each do |node|
75
+ s = string_from_node(node)
76
+ return true if compare(context, *(reverse ? [value, s] : [s, value]))
77
+ end
78
+ return false
79
+ end
80
+ if value.bool?
81
+ b = node_set.to_bool
82
+ return compare(context, *(reverse ? [value, b] : [b, value]))
83
+ end
84
+ fail UnreachableError, 'unreachable pattern happens; please file an issue on github.'
85
+ end
86
+
87
+ # @!visibility private
88
+ def string_from_node(node)
89
+ case node
90
+ when Gammo::Node::Element, Gammo::Node::Document
91
+ AST::Value::String.new(node.inner_text)
92
+ when Gammo::Attribute
93
+ AST::Value::String.new(node.value)
94
+ when Gammo::Node::Comment, Gammo::Node::Text
95
+ AST::Value::String.new(node.data)
96
+ end
97
+ end
98
+
99
+ # @!visibility private
100
+ def number_from_node(node)
101
+ case node
102
+ when Gammo::Attribute
103
+ # TODO: Consider float case.
104
+ AST::Value::Number.new(node.value.to_i)
105
+ when Gammo::Node::Comment, Gammo::Node::Text
106
+ AST::Value::Number.new(node.data)
107
+ end
108
+ end
109
+
110
+ # @!visibility private
111
+ def equal?(left, right)
112
+ return left.to_bool == right.to_bool if left.bool? || right.bool?
113
+ return left.to_number == right.to_number if left.number? || right.number?
114
+ left.to_s == right.to_s
115
+ end
116
+ end
117
+
118
+ # @!visibility private
119
+ class EqExpr < BoolExpr
120
+ def do_compare(left, right)
121
+ equal?(left, right)
122
+ end
123
+ end
124
+
125
+ # @!visibility private
126
+ class NeqExpr < BoolExpr
127
+ def do_compare(left, right)
128
+ !equal?(left, right)
129
+ end
130
+ end
131
+
132
+ # @!visibility private
133
+ class LtExpr < BoolExpr
134
+ def do_compare(left, right)
135
+ left.to_number < right.to_number
136
+ end
137
+ end
138
+
139
+ # @!visibility private
140
+ class GtExpr < BoolExpr
141
+ def do_compare(left, right)
142
+ left.to_number > right.to_number
143
+ end
144
+ end
145
+
146
+ # @!visibility private
147
+ class LteExpr < BoolExpr
148
+ def do_compare(left, right)
149
+ left.to_number <= right.to_number
150
+ end
151
+ end
152
+
153
+ # @!visibility private
154
+ class GteExpr < BoolExpr
155
+ def do_compare(left, right)
156
+ left.to_number >= right.to_number
157
+ end
158
+ end
159
+
160
+ # Class for representing Arithmetic operators.
161
+ # @!visibility private
162
+ class ArithmeticExpr < BinaryExpr
163
+ def initialize(a, b)
164
+ super(a, b)
165
+ end
166
+
167
+ def evaluate(context)
168
+ # Expects left/right to be Integer.
169
+ Value::Number.new(do_arithmetic(*evaluate_values(context).map(&:to_number)))
170
+ end
171
+ end
172
+
173
+ # @!visibility private
174
+ class PlusExpr < ArithmeticExpr
175
+ def do_arithmetic(left, right)
176
+ left + right
177
+ end
178
+ end
179
+
180
+ # @!visibility private
181
+ class MinusExpr < ArithmeticExpr
182
+ def do_arithmetic(left, right)
183
+ left - right
184
+ end
185
+ end
186
+
187
+ # @!visibility private
188
+ class MultiplyExpr < ArithmeticExpr
189
+ def do_arithmetic(left, right)
190
+ left * right
191
+ end
192
+ end
193
+
194
+ # @!visibility private
195
+ class DividedExpr < ArithmeticExpr
196
+ def do_arithmetic(left, right)
197
+ left / right
198
+ end
199
+ end
200
+
201
+ # @!visibility private
202
+ class ModuloExpr < ArithmeticExpr
203
+ def do_arithmetic(left, right)
204
+ left % right
205
+ end
206
+ end
207
+
208
+ # @!visibility private
209
+ class UnionExpr < BinaryExpr
210
+ def evaluate(context)
211
+ cloned = context.clone
212
+ left, right = @a.evaluate(context), @b.evaluate(cloned)
213
+ left_node_set = left.to_node_set(context)
214
+ right_node_set = right.to_node_set(cloned)
215
+
216
+ duplicates = Set.new(left_node_set.nodes)
217
+ right_node_set.each { |node| left_node_set << node if duplicates.add?(node) }
218
+ left
219
+ end
220
+ end
221
+
222
+ # @!visibility private
223
+ class Negative
224
+ def initialize(expression)
225
+ @expression = expression
226
+ end
227
+
228
+ def evaluate(context)
229
+ AST::Value::Number.new(-@expression.evaluate(context).to_number)
230
+ end
231
+ end
232
+
233
+ # Class for representing predicate like "[foo='bar']" and "[0]".
234
+ # @!visibility private
235
+ class Predicate
236
+ def initialize(value)
237
+ @value = value
238
+ end
239
+
240
+ def evaluate(context)
241
+ ret = @value.evaluate(context)
242
+ if ret.instance_of?(AST::Value::Number)
243
+ return EqExpr.new(AST::Function::Position.new, ret).evaluate(context)
244
+ end
245
+ ret
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,179 @@
1
+ require 'gammo/xpath/ast/subclassify'
2
+ require 'gammo/xpath/ast/value'
3
+
4
+ module Gammo
5
+ module XPath
6
+ module AST
7
+ # Class for representing XPath core function library.
8
+ # https://www.w3.org/TR/1999/REC-xpath-19991116/#corelib
9
+ class Function
10
+ extend Subclassify
11
+
12
+ # @!visibility private
13
+ def initialize(*arguments)
14
+ @arguments = arguments
15
+ end
16
+
17
+ # @!visibility private
18
+ def evaluate(context)
19
+ raise NotImplementedError, '#evaluate must be implemented'
20
+ end
21
+
22
+ private
23
+
24
+ # @!visibility private
25
+ def number(val)
26
+ return val if val.instance_of?(Value::Number)
27
+ Value::Number.new(val)
28
+ end
29
+
30
+ # @!visibility private
31
+ def bool(val)
32
+ return val if val.instance_of?(Value::Boolean)
33
+ Value::Boolean.new(val)
34
+ end
35
+
36
+ # @!visibility private
37
+ def string(val)
38
+ return val if val.instance_of?(Value::String)
39
+ Value::String.new(val)
40
+ end
41
+
42
+ attr_reader :arguments
43
+
44
+ # @!visibility private
45
+ class Boolean < Function
46
+ declare :boolean
47
+
48
+ def evaluate(context)
49
+ bool arguments[0].evaluate(context)
50
+ end
51
+ end
52
+
53
+ # @!visibility private
54
+ class Not < Function
55
+ declare :not
56
+
57
+ def evaluate(context)
58
+ bool !arguments[0].evaluate(context)
59
+ end
60
+ end
61
+
62
+ # @!visibility private
63
+ class True < Function
64
+ declare :true
65
+
66
+ def evaluate(context)
67
+ bool true
68
+ end
69
+ end
70
+
71
+ # @!visibility private
72
+ class False < Function
73
+ declare :false
74
+
75
+ def evaluate(context)
76
+ bool false
77
+ end
78
+ end
79
+
80
+ # @!visibility private
81
+ class Ceiling < Function
82
+ declare :ceiling
83
+
84
+ def evaluate(context)
85
+ number arguments[0].evaluate(context).value.ceil
86
+ end
87
+ end
88
+
89
+ # @!visibility private
90
+ class String < Function
91
+ declare :string
92
+
93
+ def evaluate(context)
94
+ return string context.node.to_s if arguments.length.zero?
95
+ string arguments[0].evaluate(context).to_s
96
+ end
97
+ end
98
+
99
+ # @!visibility private
100
+ class Concat < Function
101
+ declare :concat
102
+
103
+ def evaluate(context)
104
+ string arguments.each_with_object(::String.new) { |argument, s|
105
+ s << argument.evaluate(context.clone).to_s
106
+ }
107
+ end
108
+ end
109
+
110
+ # @!visibility private
111
+ class StartsWith < Function
112
+ declare :'starts-with'
113
+
114
+ def evaluate(context)
115
+ s1 = arguments[0].evaluate(context).to_s
116
+ s2 = arguments[1].evaluate(context.clone).to_s
117
+ return bool(true) if s2.empty?
118
+ bool s1.start_with?(s2)
119
+ end
120
+ end
121
+
122
+ # @!visibility private
123
+ class Contains < Function
124
+ declare :contains
125
+
126
+ def evaluate(context)
127
+ substr = arguments[1].evaluate(context).to_s
128
+ return bool(true) if substr.empty?
129
+ bool arguments[0].evaluate(context).to_s.include?(substr)
130
+ end
131
+ end
132
+
133
+ # @!visibility private
134
+ class SubstringBefore < Function
135
+ declare :'substring-before'
136
+
137
+ def evaluate(context)
138
+ s1 = arguments[0].evaluate(context).to_s
139
+ s2 = arguments[1].evaluate(context.clone).to_s
140
+ return string '' if s2.empty?
141
+ return string '' unless pos = s1.index(s2)
142
+ string s1[0...pos]
143
+ end
144
+ end
145
+
146
+ # @!visibility private
147
+ class SubstringAfter < Function
148
+ declare :'substring-after'
149
+
150
+ def evaluate(context)
151
+ s1 = arguments[0].evaluate(context).to_s
152
+ s2 = arguments[1].evaluate(context.clone).to_s
153
+ return string '' if s2.empty?
154
+ return string '' unless pos = s1.rindex(s2)
155
+ string s1[(pos + s2.length)..-1]
156
+ end
157
+ end
158
+
159
+ # @!visibility private
160
+ class Last < Function
161
+ declare :last
162
+
163
+ def evaluate(context)
164
+ number context.size
165
+ end
166
+ end
167
+
168
+ # @!visibility private
169
+ class Position < Function
170
+ declare :position
171
+
172
+ def evaluate(context)
173
+ number context.position
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,86 @@
1
+ require 'gammo/xpath/ast/subclassify'
2
+ require 'gammo/xpath/errors'
3
+
4
+ module Gammo
5
+ module XPath
6
+ module AST
7
+ # @!visibility private
8
+ class NodeTest
9
+ extend Subclassify
10
+
11
+ def match?(node)
12
+ fail NotImplementedError, "#match must be implemented"
13
+ end
14
+
15
+ class Name < NodeTest
16
+ declare :name
17
+
18
+ attr_reader :local, :namespace
19
+
20
+ def initialize(local: nil, namespace: nil)
21
+ @local = local
22
+ @namespace = namespace
23
+ end
24
+
25
+ def xml_namespace?
26
+ namespace == 'http://www.w3.org/XML/1998/namespace'
27
+ end
28
+
29
+ def match?(node)
30
+ return false unless node
31
+ return false if xml_namespace?
32
+ return !namespace || namespace == node.namespace if local == ?*
33
+ # TODO: investigate
34
+ if node.instance_of?(Gammo::Attribute)
35
+ # TODO: need to work
36
+ node.key == local && node.namespace == namespace
37
+ else
38
+ if document = node.owner_document
39
+ # TODO: ignoring ascii case
40
+ return node.tag == local && (!namespace || node.namespace == namespace) if node.instance_of?(Gammo::Node::Element)
41
+ return node.tag == local && node.namespace == namespace && namespace
42
+ end
43
+ node.tag == local && node.namespace == namespace
44
+ end
45
+ end
46
+ end
47
+
48
+ # @!visibility private
49
+ class Any < NodeTest
50
+ declare :node
51
+
52
+ def match?(node)
53
+ true
54
+ end
55
+ end
56
+
57
+ # @!visibility private
58
+ class Text < NodeTest
59
+ declare :text
60
+
61
+ def match?(node)
62
+ node.instance_of?(Gammo::Node::Text)
63
+ end
64
+ end
65
+
66
+ # @!visibility private
67
+ class Comment < NodeTest
68
+ declare :comment
69
+
70
+ def match?(node)
71
+ node.instance_of?(Gammo::Node::Comment)
72
+ end
73
+ end
74
+
75
+ # @!visibility private
76
+ class ProcessingInstruction < NodeTest
77
+ declare :'processing-instruction'
78
+
79
+ def initialize
80
+ fail NotImplementedError, 'processing-instruction is not supported'
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end