safrano 0.2.0 → 0.3.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/lib/multipart.rb +92 -91
- data/lib/odata/attribute.rb +15 -6
- data/lib/odata/collection.rb +109 -106
- data/lib/odata/collection_filter.rb +14 -485
- data/lib/odata/collection_order.rb +15 -22
- data/lib/odata/entity.rb +31 -41
- data/lib/odata/filter/error.rb +53 -0
- data/lib/odata/filter/parse.rb +171 -0
- data/lib/odata/filter/sequel.rb +208 -0
- data/lib/odata/filter/token.rb +59 -0
- data/lib/odata/filter/tree.rb +368 -0
- data/lib/odata/relations.rb +36 -0
- data/lib/odata/url_parameters.rb +58 -0
- data/lib/odata/walker.rb +55 -42
- data/lib/request.rb +1 -3
- data/lib/safrano.rb +17 -0
- data/lib/safrano_core.rb +30 -7
- data/lib/sequel/plugins/join_by_paths.rb +239 -0
- data/lib/sequel_join_by_paths.rb +5 -0
- data/lib/service.rb +84 -112
- metadata +11 -3
@@ -0,0 +1,208 @@
|
|
1
|
+
require_relative './tree.rb'
|
2
|
+
module OData
|
3
|
+
module Filter
|
4
|
+
# Base class for Leaves, Trees, RootTrees etc
|
5
|
+
class Node
|
6
|
+
end
|
7
|
+
|
8
|
+
# Leaves are Nodes with a parent but no children
|
9
|
+
class Leave < Node
|
10
|
+
end
|
11
|
+
|
12
|
+
# RootTrees have childrens but no parent
|
13
|
+
class RootTree
|
14
|
+
def apply_to_dataset(dtcx, jh)
|
15
|
+
filtexpr = @children.first.leuqes(jh)
|
16
|
+
dtcx = jh.dataset.where(filtexpr).select_all(jh.start_model.table_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def sequel_expr(jh)
|
20
|
+
@children.first.leuqes(jh)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Tree's have Parent and children
|
25
|
+
class Tree < RootTree
|
26
|
+
end
|
27
|
+
|
28
|
+
# For functions... should have a single child---> the argument list
|
29
|
+
class FuncTree < Tree
|
30
|
+
def leuqes(jh)
|
31
|
+
case @value
|
32
|
+
when :startswith
|
33
|
+
Sequel.like(args[0].leuqes(jh),
|
34
|
+
args[1].leuqes_starts_like(jh))
|
35
|
+
when :endswith
|
36
|
+
Sequel.like(args[0].leuqes(jh),
|
37
|
+
args[1].leuqes_ends_like(jh))
|
38
|
+
when :substringof
|
39
|
+
# there are two possible signatures
|
40
|
+
if (args[0].is_a?(QString) && args[1].is_a?(Literal))
|
41
|
+
# substringof('Rhum', name) -->
|
42
|
+
# name contains substr 'Rhum'
|
43
|
+
Sequel.like(args[1].leuqes(jh),
|
44
|
+
args[0].leuqes_substringof_like(jh))
|
45
|
+
# according to the spec it should be like that?
|
46
|
+
# elsif args[1].is_a? QString
|
47
|
+
## substringof(name, '__Route du Rhum__') -->
|
48
|
+
## '__Route du Rhum__' contains name as a substring
|
49
|
+
# Sequel.like(args[1].leuqes_substringof_like(dtcx),
|
50
|
+
# args[0].leuqes(dtcx) )
|
51
|
+
# or like that?
|
52
|
+
# elsif args[1].is_a? QString
|
53
|
+
# # substringof(name, 'Route du Rhum') -->
|
54
|
+
# # name contains substr 'Rhum' ??
|
55
|
+
# Sequel.like(args[0].leuqes(dtcx) ,
|
56
|
+
# args[1].leuqes_substringof_like(dtcx) )
|
57
|
+
elsif (args[0].is_a?(Literal) && args[1].is_a?(Literal))
|
58
|
+
Sequel.like(args[1].leuqes(jh),
|
59
|
+
args[0].leuqes_substringof_like(jh))
|
60
|
+
else
|
61
|
+
# TODO... actually not supported?
|
62
|
+
raise OData::Filter::Parser::ErrorFunctionArgumentType
|
63
|
+
end
|
64
|
+
when :concat
|
65
|
+
Sequel.join([args[0].leuqes(jh),
|
66
|
+
args[1].leuqes(jh)])
|
67
|
+
when :length
|
68
|
+
Sequel.char_length(args.first.leuqes(jh))
|
69
|
+
when :trim
|
70
|
+
Sequel.trim(args.first.leuqes(jh))
|
71
|
+
when :toupper
|
72
|
+
Sequel.function(:upper, args.first.leuqes(jh))
|
73
|
+
when :tolower
|
74
|
+
Sequel.function(:lower, args.first.leuqes(jh))
|
75
|
+
else
|
76
|
+
raise OData::FilterParseError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Indentity Func to use as "parent" func of parenthesis expressions
|
82
|
+
# --> allow to handle generically parenthesis always as argument of
|
83
|
+
# some function
|
84
|
+
class IdentityFuncTree < FuncTree
|
85
|
+
def leuqes(jh)
|
86
|
+
args.first.leuqes(jh)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# unary op eg. NOT
|
91
|
+
class UnopTree < Tree
|
92
|
+
def leuqes(jh)
|
93
|
+
case @value
|
94
|
+
when :not
|
95
|
+
Sequel.~(@children.first.leuqes(jh))
|
96
|
+
else
|
97
|
+
raise OData::FilterParseError
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Bin ops
|
103
|
+
class BinopTree < Tree
|
104
|
+
def leuqes(jh)
|
105
|
+
leuqes_op = case @value
|
106
|
+
when :eq
|
107
|
+
:'='
|
108
|
+
when :ne
|
109
|
+
:'!='
|
110
|
+
when :le
|
111
|
+
:<=
|
112
|
+
when :ge
|
113
|
+
:'>='
|
114
|
+
when :lt
|
115
|
+
:<
|
116
|
+
when :gt
|
117
|
+
:>
|
118
|
+
when :or
|
119
|
+
:OR
|
120
|
+
when :and
|
121
|
+
:AND
|
122
|
+
else
|
123
|
+
raise OData::FilterParseError
|
124
|
+
end
|
125
|
+
|
126
|
+
Sequel::SQL::BooleanExpression.new(leuqes_op,
|
127
|
+
@children[0].leuqes(jh),
|
128
|
+
@children[1].leuqes(jh))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Arguments or lists
|
133
|
+
class ArgTree
|
134
|
+
end
|
135
|
+
|
136
|
+
# Numbers (floating point, ints, dec)
|
137
|
+
class FPNumber
|
138
|
+
def leuqes(_jh)
|
139
|
+
Sequel.lit(@value)
|
140
|
+
end
|
141
|
+
|
142
|
+
def leuqes_starts_like(_jh)
|
143
|
+
"#{@value.to_s}%"
|
144
|
+
end
|
145
|
+
|
146
|
+
def leuqes_ends_like(_jh)
|
147
|
+
"%#{@value.to_s}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def leuqes_substringof_like(_jh)
|
151
|
+
"%#{@value.to_s}%"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Literals are unquoted words
|
156
|
+
class Literal
|
157
|
+
def leuqes(jh)
|
158
|
+
if jh.start_model.db_schema.has_key?(@value.to_sym)
|
159
|
+
Sequel[jh.start_model.table_name][@value.to_sym]
|
160
|
+
else
|
161
|
+
raise OData::Filter::Parser::ErrorWrongColumnName
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# non stantard extensions to support things like
|
166
|
+
# substringof(Rhum, name) ????
|
167
|
+
def leuqes_starts_like(_jh)
|
168
|
+
"#{@value}%"
|
169
|
+
end
|
170
|
+
|
171
|
+
def leuqes_ends_like(_jh)
|
172
|
+
"%#{@value}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def leuqes_substringof_like(_jh)
|
176
|
+
"%#{@value}%"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Qualit (qualified lits) are words separated by /
|
181
|
+
class Qualit
|
182
|
+
def leuqes(jh)
|
183
|
+
jh.add(@path)
|
184
|
+
talias = jh.start_model.get_alias_sym(@path)
|
185
|
+
Sequel[talias][@attrib.to_sym]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Quoted Strings
|
190
|
+
class QString
|
191
|
+
def leuqes(_jh)
|
192
|
+
@value
|
193
|
+
end
|
194
|
+
|
195
|
+
def leuqes_starts_like(_jh)
|
196
|
+
"#{@value}%"
|
197
|
+
end
|
198
|
+
|
199
|
+
def leuqes_ends_like(_jh)
|
200
|
+
"%#{@value}"
|
201
|
+
end
|
202
|
+
|
203
|
+
def leuqes_substringof_like(_jh)
|
204
|
+
"%#{@value}%"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# top level OData namespace
|
2
|
+
module OData
|
3
|
+
module Filter
|
4
|
+
class Parser
|
5
|
+
# Input tokenizer
|
6
|
+
module Token
|
7
|
+
FUNCNAMES = %w[concat substringof endswith startswith length indexof
|
8
|
+
replace substring trim toupper tolower].freeze
|
9
|
+
FUNCRGX = FUNCNAMES.join('|').freeze
|
10
|
+
QSTRINGRGX = /'((?:[^']|(?:\'{2}))*)'/.freeze
|
11
|
+
BINOPSRGX = '[eE][qQ]|[LlgGNn][eETt]|and|AND|or|OR'.freeze
|
12
|
+
NOTRGX = 'not|NOT'.freeze
|
13
|
+
FPRGX = '\d+(?:\.\d+)?(?:e[+-]?\d+)?'.freeze
|
14
|
+
QUALITRGX = '\w+(?:\/\w+)+'.freeze
|
15
|
+
RGX = /(#{FUNCRGX})|([\(\),])|(#{BINOPSRGX})|(#{NOTRGX})|#{QSTRINGRGX}|(#{FPRGX})|(#{QUALITRGX})|(\w+)|(')/.freeze
|
16
|
+
def each_typed_token(inp)
|
17
|
+
typ = nil
|
18
|
+
|
19
|
+
inp.scan(RGX) do |groups|
|
20
|
+
idx = nil
|
21
|
+
found = nil
|
22
|
+
groups.each_with_index do |tok, i|
|
23
|
+
if (found = tok)
|
24
|
+
idx = i
|
25
|
+
break
|
26
|
+
end
|
27
|
+
end
|
28
|
+
typ = case idx
|
29
|
+
when 0
|
30
|
+
:FuncTree
|
31
|
+
when 1
|
32
|
+
case found
|
33
|
+
when '(', ')'
|
34
|
+
:Delimiter
|
35
|
+
when ','
|
36
|
+
:Separator
|
37
|
+
end
|
38
|
+
when 2
|
39
|
+
:BinopTree
|
40
|
+
when 3
|
41
|
+
:UnopTree
|
42
|
+
when 4
|
43
|
+
:QString
|
44
|
+
when 5
|
45
|
+
:FPNumber
|
46
|
+
when 6
|
47
|
+
:Qualit
|
48
|
+
when 7
|
49
|
+
:Literal
|
50
|
+
when 8
|
51
|
+
:unmatchedQuote
|
52
|
+
end
|
53
|
+
yield found, typ
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,368 @@
|
|
1
|
+
require_relative './error.rb'
|
2
|
+
|
3
|
+
module OData
|
4
|
+
module Filter
|
5
|
+
# Base class for Leaves, Trees, RootTrees etc
|
6
|
+
class Node
|
7
|
+
attr_reader :value
|
8
|
+
def initialize(val, &block)
|
9
|
+
@value = val
|
10
|
+
instance_eval(&block) if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
@value == other.value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Leaves are Nodes with a parent but no children
|
19
|
+
class Leave < Node
|
20
|
+
attr_accessor :parent
|
21
|
+
def accept?(tok, typ)
|
22
|
+
[false, Parser::ErrorInvalidToken(tok, typ)]
|
23
|
+
end
|
24
|
+
|
25
|
+
def check_types; end
|
26
|
+
end
|
27
|
+
|
28
|
+
# RootTrees have childrens but no parent
|
29
|
+
class RootTree < Node
|
30
|
+
attr_reader :children
|
31
|
+
attr_accessor :state
|
32
|
+
def initialize(val: :root, &block)
|
33
|
+
@children = []
|
34
|
+
super(val, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def attach(child)
|
38
|
+
child.parent = self
|
39
|
+
@children << child
|
40
|
+
end
|
41
|
+
|
42
|
+
def detach(child)
|
43
|
+
child.parent = nil
|
44
|
+
@children.delete(child)
|
45
|
+
end
|
46
|
+
|
47
|
+
def ==(other)
|
48
|
+
super(other) && (@children == other.children)
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_state(tok, typ) end
|
52
|
+
|
53
|
+
def accept?(tok, typ)
|
54
|
+
case typ
|
55
|
+
when :Literal, :Qualit, :QString, :FuncTree, :ArgTree, :UnopTree, :FPNumber
|
56
|
+
true
|
57
|
+
when :Delimiter
|
58
|
+
if tok == '('
|
59
|
+
true
|
60
|
+
else
|
61
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
62
|
+
end
|
63
|
+
else
|
64
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_types
|
69
|
+
@children.each(&:check_types)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Tree's have Parent and children
|
74
|
+
class Tree < RootTree
|
75
|
+
attr_accessor :parent
|
76
|
+
|
77
|
+
def initialize(val)
|
78
|
+
super(val: val)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# For functions... should have a single child---> the argument list
|
83
|
+
class FuncTree < Tree
|
84
|
+
def initialize(val)
|
85
|
+
super(val.downcase.to_sym)
|
86
|
+
end
|
87
|
+
|
88
|
+
def args
|
89
|
+
@children.first.children
|
90
|
+
end
|
91
|
+
|
92
|
+
def arity_full?(cursize)
|
93
|
+
cursize >= max_arity
|
94
|
+
end
|
95
|
+
|
96
|
+
def max_arity
|
97
|
+
case @value
|
98
|
+
when :replace
|
99
|
+
3
|
100
|
+
when :concat, :substringof, :substring, :endswith, :startswith
|
101
|
+
2
|
102
|
+
else
|
103
|
+
1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def edm_type
|
108
|
+
case @value
|
109
|
+
when :concat, :substring
|
110
|
+
:string
|
111
|
+
when :length
|
112
|
+
:int
|
113
|
+
when :substringof, :endswith, :startswith
|
114
|
+
:bool
|
115
|
+
else
|
116
|
+
:any
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def accept?(tok, typ)
|
121
|
+
case typ
|
122
|
+
when :BinopTree
|
123
|
+
true
|
124
|
+
else
|
125
|
+
super(tok, typ)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def check_types
|
130
|
+
case @value
|
131
|
+
when :length
|
132
|
+
argtyp = args.first.edm_type
|
133
|
+
if (argtyp != :any) && (argtyp != :string)
|
134
|
+
raise Parser::ErrorInvalidArgumentType.new(self,
|
135
|
+
expected: :string,
|
136
|
+
actual: argtyp)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
super
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Indentity Func to use as "parent" func of parenthesis expressions
|
144
|
+
# --> allow to handle generically parenthesis always as argument of
|
145
|
+
# some function
|
146
|
+
class IdentityFuncTree < FuncTree
|
147
|
+
def initialize
|
148
|
+
super(:__indentity)
|
149
|
+
end
|
150
|
+
|
151
|
+
# we can have parenthesis with one expression inside everywhere
|
152
|
+
# only in FuncTree this is redefined for the function's arity
|
153
|
+
def arity_full?(cursize)
|
154
|
+
cursize >= 1
|
155
|
+
end
|
156
|
+
|
157
|
+
def edm_type
|
158
|
+
@children.first.edm_type
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# unary op eg. NOT
|
163
|
+
class UnopTree < Tree
|
164
|
+
def initialize(val)
|
165
|
+
super(val.downcase.to_sym)
|
166
|
+
end
|
167
|
+
|
168
|
+
# reference:
|
169
|
+
# OData v4 par 5.1.1.9 Operator Precedence
|
170
|
+
def precedence
|
171
|
+
case @value
|
172
|
+
when :not
|
173
|
+
7
|
174
|
+
else
|
175
|
+
999
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def edm_type
|
180
|
+
case @value
|
181
|
+
when :not
|
182
|
+
:bool
|
183
|
+
else
|
184
|
+
:any
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Bin ops
|
190
|
+
class BinopTree < Tree
|
191
|
+
def initialize(val)
|
192
|
+
@state = :open
|
193
|
+
super(val.downcase.to_sym)
|
194
|
+
end
|
195
|
+
|
196
|
+
# reference:
|
197
|
+
# OData v4 par 5.1.1.9 Operator Precedence
|
198
|
+
def precedence
|
199
|
+
case @value
|
200
|
+
when :or
|
201
|
+
1
|
202
|
+
when :and
|
203
|
+
2
|
204
|
+
when :eq, :ne
|
205
|
+
3
|
206
|
+
when :gt, :ge, :lt, :le
|
207
|
+
4
|
208
|
+
else
|
209
|
+
999
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def update_state(_tok, typ)
|
214
|
+
case typ
|
215
|
+
when :Literal, :Qualit, :QString, :FuncTree, :BinopTree, :UnopTree, :FPNumber
|
216
|
+
@state = :closed
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def edm_type
|
221
|
+
case @value
|
222
|
+
when :or, :and, :eq, :ne, :gt, :ge, :lt, :le
|
223
|
+
:bool
|
224
|
+
else
|
225
|
+
:any
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Arguments or lists
|
231
|
+
class ArgTree < Tree
|
232
|
+
attr_reader :type
|
233
|
+
def initialize(val)
|
234
|
+
@type = :expression
|
235
|
+
@state = :open
|
236
|
+
super
|
237
|
+
end
|
238
|
+
|
239
|
+
def update_state(_tok, typ)
|
240
|
+
case typ
|
241
|
+
when :Delimiter
|
242
|
+
@state = :closed
|
243
|
+
when :Separator
|
244
|
+
@state = :sep
|
245
|
+
when :Literal, :Qualit, :QString, :FuncTree, :FPNumber
|
246
|
+
@state = :val
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def accept?(tok, typ)
|
251
|
+
case typ
|
252
|
+
when :Delimiter
|
253
|
+
if @value == '(' && tok == ')' && @state != :closed
|
254
|
+
if @parent.arity_full?(@children.size)
|
255
|
+
true
|
256
|
+
else
|
257
|
+
[false, Parser::ErrorInvalidArity.new(tok, typ, self)]
|
258
|
+
end
|
259
|
+
else
|
260
|
+
[false, Parser::ErrorUnmatchedClose.new(tok, typ, self)]
|
261
|
+
end
|
262
|
+
when :Separator
|
263
|
+
if @value == '(' && tok == ',' && @state == :val
|
264
|
+
true
|
265
|
+
else
|
266
|
+
if (@state == :sep)
|
267
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
268
|
+
else
|
269
|
+
true
|
270
|
+
end
|
271
|
+
end
|
272
|
+
when :Literal, :Qualit, :QString, :FuncTree, :FPNumber
|
273
|
+
if (@state == :open) || (@state == :sep)
|
274
|
+
if @parent.arity_full?(@children.size)
|
275
|
+
[false, Parser::ErrorInvalidArity.new(tok, typ, self)]
|
276
|
+
else
|
277
|
+
true
|
278
|
+
end
|
279
|
+
else
|
280
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
281
|
+
end
|
282
|
+
when :BinopTree
|
283
|
+
true
|
284
|
+
else
|
285
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def ==(other)
|
290
|
+
super(other) && @type == other.type && @state == other.state
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Numbers (floating point, ints, dec)
|
295
|
+
class FPNumber < Leave
|
296
|
+
def accept?(tok, typ)
|
297
|
+
case typ
|
298
|
+
when :Delimiter, :Separator, :BinopTree
|
299
|
+
true
|
300
|
+
else
|
301
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# TODO
|
306
|
+
def edm_type
|
307
|
+
:number
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Literals are unquoted words without /
|
312
|
+
class Literal < Leave
|
313
|
+
def accept?(tok, typ)
|
314
|
+
case typ
|
315
|
+
when :Delimiter, :Separator, :BinopTree
|
316
|
+
true
|
317
|
+
else
|
318
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def edm_type
|
323
|
+
:any
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Qualit (qualified lits) are words separated by /
|
328
|
+
# path/path/path/attrib
|
329
|
+
class Qualit < Literal
|
330
|
+
REGEXP = /((?:\w+\/)+)(\w+)/.freeze
|
331
|
+
attr_reader :path
|
332
|
+
attr_reader :attrib
|
333
|
+
def initialize(val)
|
334
|
+
super(val)
|
335
|
+
# split into path + attrib
|
336
|
+
if (md = REGEXP.match(val))
|
337
|
+
@path = md[1].chomp('/')
|
338
|
+
@attrib = md[2]
|
339
|
+
else
|
340
|
+
raise Parser::Error.new(self, Qualit)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Quoted Strings
|
346
|
+
class QString < Leave
|
347
|
+
DoubleQuote = "''".freeze
|
348
|
+
SingleQuote = "'".freeze
|
349
|
+
def initialize(val)
|
350
|
+
# unescape double quotes
|
351
|
+
super(val.gsub(DoubleQuote, SingleQuote))
|
352
|
+
end
|
353
|
+
|
354
|
+
def accept?(tok, typ)
|
355
|
+
case typ
|
356
|
+
when :Delimiter, :Separator, :BinopTree
|
357
|
+
true
|
358
|
+
else
|
359
|
+
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def edm_type
|
364
|
+
:string
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|