fabulator 0.0.1 → 0.0.2
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.
- data/History.txt +10 -0
- data/Rakefile +50 -39
- data/VERSION +1 -0
- data/features/functions.feature +164 -0
- data/features/group-params.feature +86 -0
- data/features/loops.feature +55 -0
- data/features/paths.feature +57 -0
- data/features/primitives.feature +9 -0
- data/features/simple-control.feature +13 -0
- data/features/simple-math.feature +18 -0
- data/features/simple-statemachine.feature +192 -0
- data/features/step_definitions/expression_steps.rb +102 -0
- data/features/step_definitions/template_steps.rb +25 -0
- data/features/step_definitions/xml_steps.rb +23 -0
- data/features/support/env.rb +7 -0
- data/features/templates.feature +100 -0
- data/features/types.feature +64 -0
- data/features/xsm-inheritance.feature +46 -0
- data/init.rb +6 -0
- data/lib/fabulator/action_lib.rb +31 -1
- data/lib/fabulator/core/actions.rb +5 -5
- data/lib/fabulator/core/state_machine.rb +7 -4
- data/lib/fabulator/expr/function.rb +3 -0
- data/lib/fabulator/expr/function.rb.old +85 -0
- data/lib/fabulator/expr/parser.rb +7 -3
- data/lib/fabulator/template/parse_result.rb +8 -10
- data/xsm_expression_parser.racc +462 -0
- metadata +32 -48
@@ -0,0 +1,64 @@
|
|
1
|
+
@types
|
2
|
+
Feature: Type unification
|
3
|
+
|
4
|
+
Scenario: A single type
|
5
|
+
Given the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
6
|
+
When I unify the type f:numeric
|
7
|
+
Then I should get the type f:numeric
|
8
|
+
|
9
|
+
Scenario: A single type multiple times
|
10
|
+
Given the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
11
|
+
When I unify the type f:numeric, f:numeric, f:numeric
|
12
|
+
Then I should get the type f:numeric
|
13
|
+
|
14
|
+
Scenario: Two types sharing a possible target type
|
15
|
+
Given the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
16
|
+
When I unify the type f:numeric, f:numeric, f:boolean
|
17
|
+
Then I should get the type f:numeric
|
18
|
+
|
19
|
+
Scenario: Three types sharing a possible target type
|
20
|
+
Given the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
21
|
+
When I unify the type f:numeric, f:numeric, f:boolean, f:string
|
22
|
+
Then I should get the type f:string
|
23
|
+
|
24
|
+
Scenario: Converting boolean to a string
|
25
|
+
Given a context
|
26
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
27
|
+
When I run the expression (f:string(f:true()))
|
28
|
+
Then I should get 1 item
|
29
|
+
And item 0 should be ['true']
|
30
|
+
|
31
|
+
Scenario: Use uri-prefix
|
32
|
+
Given a context
|
33
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
34
|
+
When I run the expression (f:uri-prefix('f'))
|
35
|
+
Then I should get 1 item
|
36
|
+
And item 0 should be ["http://dh.tamu.edu/ns/fabulator/1.0#"]
|
37
|
+
|
38
|
+
Scenario: Get the type of a boolean value
|
39
|
+
Given a context
|
40
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
41
|
+
When I run the expression (f:true()/@type)
|
42
|
+
Then I should get 1 item
|
43
|
+
And item 0 should be [f:uri-prefix('f') + 'boolean']
|
44
|
+
|
45
|
+
Scenario: Get the type of a string value
|
46
|
+
Given a context
|
47
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
48
|
+
When I run the expression ("foo"/@type)
|
49
|
+
Then I should get 1 item
|
50
|
+
And item 0 should be [f:uri-prefix('f') + 'string']
|
51
|
+
|
52
|
+
Scenario: Get the type of a numeric value
|
53
|
+
Given a context
|
54
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
55
|
+
When I run the expression (3/@type)
|
56
|
+
Then I should get 1 item
|
57
|
+
And item 0 should be [f:uri-prefix('f') + 'numeric']
|
58
|
+
|
59
|
+
Scenario: Get the type of a uri value
|
60
|
+
Given a context
|
61
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
62
|
+
When I run the expression (3/@type/@type)
|
63
|
+
Then I should get 1 item
|
64
|
+
And item 0 should be [f:uri-prefix('f') + 'uri']
|
@@ -0,0 +1,46 @@
|
|
1
|
+
@isa
|
2
|
+
Feature: IS-A State machine inheritance
|
3
|
+
|
4
|
+
Scenario: simple machine with just one state and no transitions
|
5
|
+
Given the statemachine
|
6
|
+
"""
|
7
|
+
<f:application xmlns:f="http://dh.tamu.edu/ns/fabulator/1.0#" />
|
8
|
+
"""
|
9
|
+
And the statemachine
|
10
|
+
"""
|
11
|
+
<f:application xmlns:f="http://dh.tamu.edu/ns/fabulator/1.0#">
|
12
|
+
<f:view f:name="start">
|
13
|
+
<f:goes-to f:view="stop">
|
14
|
+
<f:params>
|
15
|
+
<f:param f:name="foo"/>
|
16
|
+
</f:params>
|
17
|
+
</f:goes-to>
|
18
|
+
</f:view>
|
19
|
+
<f:view f:name="stop" />
|
20
|
+
</f:application>
|
21
|
+
"""
|
22
|
+
Then it should be in the 'start' state
|
23
|
+
When I run it with the following params:
|
24
|
+
| key | value |
|
25
|
+
| foo | bar |
|
26
|
+
Then it should be in the 'stop' state
|
27
|
+
And the expression (/foo) should equal ['bar']
|
28
|
+
When I run it with the following params:
|
29
|
+
| key | value |
|
30
|
+
| foo | bar |
|
31
|
+
Then it should be in the 'stop' state
|
32
|
+
And the expression (/foo) should equal ['bar']
|
33
|
+
|
34
|
+
Scenario: simple machine with just one state and no transitions
|
35
|
+
Given the statemachine
|
36
|
+
"""
|
37
|
+
<f:application xmlns:f="http://dh.tamu.edu/ns/fabulator/1.0#">
|
38
|
+
<f:value f:path="/foo" f:select="'bar'" />
|
39
|
+
</f:application>
|
40
|
+
"""
|
41
|
+
And the statemachine
|
42
|
+
"""
|
43
|
+
<f:application xmlns:f="http://dh.tamu.edu/ns/fabulator/1.0#"/>
|
44
|
+
"""
|
45
|
+
Then it should be in the 'start' state
|
46
|
+
And the expression (/foo) should equal ['bar']
|
data/init.rb
ADDED
data/lib/fabulator/action_lib.rb
CHANGED
@@ -218,6 +218,14 @@ module Fabulator
|
|
218
218
|
ret = args.flatten.collect { |a| send "fctn:#{nom}", context, a }
|
219
219
|
when :reduction
|
220
220
|
ret = send "fctn:#{nom}", context, args.flatten
|
221
|
+
when :consolidation
|
222
|
+
if respond_to?("fctn:#{nom}")
|
223
|
+
ret = send "fctn:#{nom}", context, args.flatten
|
224
|
+
elsif nom =~ /^consolidation:(.*)$/
|
225
|
+
ret = send "fctn:#{$1}", context, args.flatten
|
226
|
+
else
|
227
|
+
ret = [ ]
|
228
|
+
end
|
221
229
|
else
|
222
230
|
ret = send "fctn:#{nom}", context, args
|
223
231
|
end
|
@@ -252,7 +260,15 @@ module Fabulator
|
|
252
260
|
end
|
253
261
|
|
254
262
|
def function_run_type(name)
|
255
|
-
(self.function_descriptions[name][:type] rescue nil)
|
263
|
+
r = (self.function_descriptions[name][:type] rescue nil)
|
264
|
+
if r.nil? && !self.function_descriptions.has_key?(name)
|
265
|
+
if name =~ /^consolidation:(.*)/
|
266
|
+
if function_run_scaling($1) != :flat
|
267
|
+
return :consolidation
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
r
|
256
272
|
end
|
257
273
|
|
258
274
|
def function_args
|
@@ -369,6 +385,20 @@ module Fabulator
|
|
369
385
|
self.function_descriptions[name][:description] = Fabulator::ActionLib.last_description if Fabulator::ActionLib.last_description
|
370
386
|
Fabulator::ActionLib.last_description = nil
|
371
387
|
define_method("fctn:#{name}", &block)
|
388
|
+
cons = self.function_descriptions[name][:consolidation]
|
389
|
+
if !cons.nil?
|
390
|
+
Fabulator::ActionLib.last_description = self.function_descriptions[name][:description]
|
391
|
+
consolidation name do |ctx, args|
|
392
|
+
send "fctn:#{cons}", ctx, args
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def consolidation(name, opts = {}, &block)
|
398
|
+
self.function_descriptions[name] = { :type => :consolidation }.merge(opts)
|
399
|
+
self.function_descriptions[name][:description] = Fabulator::ActionLib.last_description if Fabulator::ActionLib.last_description
|
400
|
+
Fabulator::ActionLib.last_description = nil
|
401
|
+
define_method("fctn:consolidation:#{name}", &block)
|
372
402
|
end
|
373
403
|
|
374
404
|
def mapping(name, opts = {}, &block)
|
@@ -143,7 +143,7 @@ module Fabulator
|
|
143
143
|
[ res ]
|
144
144
|
end
|
145
145
|
|
146
|
-
reduction 'avg' do |ctx, args|
|
146
|
+
reduction 'avg', { :scaling => :flat } do |ctx, args|
|
147
147
|
res = 0.0
|
148
148
|
n = 0.0
|
149
149
|
args.each do |a|
|
@@ -176,7 +176,7 @@ module Fabulator
|
|
176
176
|
[ctx.root.anon_node(res, NUMERIC)]
|
177
177
|
end
|
178
178
|
|
179
|
-
|
179
|
+
reduction 'histogram', { :scaling => :log } do |ctx, args|
|
180
180
|
acc = { }
|
181
181
|
args.flatten.each do |a|
|
182
182
|
acc[a.to_s] ||= 0
|
@@ -191,11 +191,11 @@ module Fabulator
|
|
191
191
|
# the code here is the consolidation function for histogram
|
192
192
|
# f:sum is the consolidation function for f:sum
|
193
193
|
#
|
194
|
-
|
194
|
+
consolidation 'histogram', { :scaling => :log } do |ctx, args|
|
195
195
|
acc = { }
|
196
196
|
attrs = { }
|
197
197
|
children = { }
|
198
|
-
args.each do |a|
|
198
|
+
args.flatten.each do |a|
|
199
199
|
a.children.each do |c|
|
200
200
|
acc[c.name] ||= 0
|
201
201
|
acc[c.name] = acc[c.name] + c.value
|
@@ -421,7 +421,7 @@ module Fabulator
|
|
421
421
|
arg.is_a?(Array) && arg.size == 1
|
422
422
|
end
|
423
423
|
|
424
|
-
reduction 'count' do |ctx, args|
|
424
|
+
reduction 'count', { :consolidation => :sum } do |ctx, args|
|
425
425
|
args.size
|
426
426
|
end
|
427
427
|
|
@@ -16,17 +16,20 @@ module Fabulator
|
|
16
16
|
attr_accessor :states, :missing_params, :errors, :namespaces, :updated_at
|
17
17
|
attr_accessor :state
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
@
|
19
|
+
def initialize
|
20
|
+
@states = { }
|
21
|
+
@context = Fabulator::Expr::Context.new
|
22
22
|
@state = 'start'
|
23
|
+
end
|
23
24
|
|
25
|
+
def compile_xml(xml, context = nil, callbacks = { })
|
26
|
+
# /statemachine/states
|
24
27
|
if xml.is_a?(String)
|
25
28
|
xml = LibXML::XML::Document.string xml
|
26
29
|
end
|
27
30
|
|
28
31
|
if context.nil?
|
29
|
-
@context =
|
32
|
+
@context = @context.merge(xml.root)
|
30
33
|
else
|
31
34
|
@context = context.merge(xml.root)
|
32
35
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module XSM
|
3
|
+
class Function
|
4
|
+
@@function_handlers = {
|
5
|
+
# 'http://dh.tamu.edu/ns/fn#' => Fabulator::XSM::StdFunctions
|
6
|
+
}
|
7
|
+
|
8
|
+
def initialize(fname, args)
|
9
|
+
@function_name = fname.split(':',2)
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.register_function_handler(ns, c)
|
14
|
+
@@function_handlers[ns] = c
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(context)
|
18
|
+
# we want to look up the function name ([namespace, local_name])
|
19
|
+
# and run it, or raise an error
|
20
|
+
fn_obj = @@function_handlers[@function_name[0]]
|
21
|
+
return nil if fn_obj.nil?
|
22
|
+
|
23
|
+
return fn_obj.run(@function_name[1], @args.collect{ |a| a.run(context) })
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class StdFunctions
|
28
|
+
|
29
|
+
Fabulator::XSM::Function.register_function_handler(
|
30
|
+
'http://dh.tamu.edu/ns/fn#', self
|
31
|
+
)
|
32
|
+
|
33
|
+
def self.run(fn, args)
|
34
|
+
case fn
|
35
|
+
when 'node-name':
|
36
|
+
return args[0].select {|a| a.is_a?(Fabulator::XSM::Context) }.collect { |a| a.name }
|
37
|
+
when 'abs':
|
38
|
+
return args[0].select {|a| a.is_a?(Fabulator::XSM::Context) }.collect { |a| ac = a.clone; ac.value = Math.abs(ac.value) }
|
39
|
+
when 'ceiling':
|
40
|
+
when 'floor':
|
41
|
+
when 'round':
|
42
|
+
when 'round-half-to-even':
|
43
|
+
when 'round-half-to-odd':
|
44
|
+
when 'concat':
|
45
|
+
when 'string-join':
|
46
|
+
when 'substring':
|
47
|
+
when 'string-length':
|
48
|
+
when 'normalize-space':
|
49
|
+
when 'upper-case':
|
50
|
+
when 'lower-case':
|
51
|
+
when 'translate':
|
52
|
+
when 'escape-html-uri':
|
53
|
+
when 'contains':
|
54
|
+
when 'starts-with':
|
55
|
+
when 'ends-with':
|
56
|
+
when 'substring-before':
|
57
|
+
when 'substring-after':
|
58
|
+
when 'matches':
|
59
|
+
when 'replace':
|
60
|
+
when 'tokenize':
|
61
|
+
when 'resolve-uri':
|
62
|
+
when 'true':
|
63
|
+
when 'false':
|
64
|
+
when 'empty':
|
65
|
+
when 'not':
|
66
|
+
when 'reverse':
|
67
|
+
when 'zero-or-one':
|
68
|
+
when 'one-or-more':
|
69
|
+
when 'exactly-one':
|
70
|
+
when 'count':
|
71
|
+
when 'avg':
|
72
|
+
when 'max':
|
73
|
+
when 'min':
|
74
|
+
when 'sum':
|
75
|
+
when 'position':
|
76
|
+
when 'last':
|
77
|
+
when 'create-url-for':
|
78
|
+
when 'review-url-for':
|
79
|
+
when 'update-url-for':
|
80
|
+
when 'delete-url-for':
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -18,7 +18,7 @@ module_eval(<<'...end xsm_expression_parser.racc/module_eval...', 'xsm_expressio
|
|
18
18
|
def parse(t, ctx)
|
19
19
|
@source = t
|
20
20
|
@curpos = 0
|
21
|
-
@context = ctx
|
21
|
+
@context = ctx
|
22
22
|
@line = 0
|
23
23
|
@col = 0
|
24
24
|
|
@@ -48,7 +48,7 @@ module_eval(<<'...end xsm_expression_parser.racc/module_eval...', 'xsm_expressio
|
|
48
48
|
@@regex[:qname] = %r{((?:#{@@regex[:ncname]}:)?#{@@regex[:ncname]})}
|
49
49
|
@@regex[:dollar_qname] = %r{\$#{@@regex[:qname]}}
|
50
50
|
@@regex[:dollar_int] = %r{\$([0-9]+)}
|
51
|
-
@@regex[:function_name] = %r{#{@@regex[:qname]}
|
51
|
+
@@regex[:function_name] = %r{#{@@regex[:qname]}\*?\s*(?=\([^:])}
|
52
52
|
|
53
53
|
@@ops = {
|
54
54
|
'..' => :DOT_DOT,
|
@@ -221,7 +221,11 @@ module_eval(<<'...end xsm_expression_parser.racc/module_eval...', 'xsm_expressio
|
|
221
221
|
if res[1] == 'if'
|
222
222
|
@token = [ :IF, 'if' ]
|
223
223
|
else
|
224
|
-
@
|
224
|
+
if @source[@curpos+res[1].length .. @curpos+res[1].length] == '*'
|
225
|
+
@token = [ :FUNCTION_NAME, res[1]+'*' ]
|
226
|
+
else
|
227
|
+
@token = [ :FUNCTION_NAME, res[1] ]
|
228
|
+
end
|
225
229
|
end
|
226
230
|
elsif !res[2].nil?
|
227
231
|
@token = [ res[2] == 'method' ? :AXIS_METHOD : :AXIS_NAME, res[2] ]
|
@@ -1,13 +1,15 @@
|
|
1
1
|
require 'xml/libxml'
|
2
|
-
require '
|
2
|
+
require 'libxslt'
|
3
3
|
|
4
|
-
|
4
|
+
module Fabulator::Template
|
5
|
+
class ParseResult
|
5
6
|
|
6
|
-
@@
|
7
|
+
@@fabulator_xslt_file = File.join(File.dirname(__FILE__), "..", "..", "..", "xslt", "form.xsl")
|
8
|
+
|
9
|
+
@@fabulator_xslt_doc = LibXML::XML::Document.file(@@fabulator_xslt_file)
|
10
|
+
@@fabulator_xslt = LibXSLT::XSLT::Stylesheet.new(@@fabulator_xslt_doc)
|
7
11
|
|
8
12
|
|
9
|
-
module Fabulator::Template
|
10
|
-
class ParseResult
|
11
13
|
def initialize(text)
|
12
14
|
@doc = LibXML::XML::Document.string text
|
13
15
|
end
|
@@ -110,11 +112,7 @@ module Fabulator::Template
|
|
110
112
|
end
|
111
113
|
|
112
114
|
def to_html
|
113
|
-
|
114
|
-
xslt.parameters = { }
|
115
|
-
xslt.xml = @doc
|
116
|
-
xslt.xsl = @@fabulator_xslt
|
117
|
-
xslt.serve
|
115
|
+
@@fabulator_xslt.apply(@doc).to_s
|
118
116
|
end
|
119
117
|
|
120
118
|
protected
|
@@ -0,0 +1,462 @@
|
|
1
|
+
class Fabulator::Expr::Parser
|
2
|
+
# based on XSM expression grammer from
|
3
|
+
# http://cpansearch.perl.org/src/JSMITH/Gestinanna-0.02/parser.PL
|
4
|
+
|
5
|
+
# instead of compiling ruby code, we'll instantiate objects to handle the
|
6
|
+
# run-time performance
|
7
|
+
|
8
|
+
start statements
|
9
|
+
|
10
|
+
rule
|
11
|
+
statements: statement { result = Fabulator::Expr::StatementList.new; result.add_statement(val[0]) }
|
12
|
+
| statements SEMI statement { result = val[0]; result.add_statement(val[2]) }
|
13
|
+
|
14
|
+
statement:
|
15
|
+
| expr
|
16
|
+
| let_expr
|
17
|
+
|
18
|
+
expr: or_expr
|
19
|
+
| range_expr
|
20
|
+
| if_expr
|
21
|
+
| for_expr
|
22
|
+
| quant_expr
|
23
|
+
| with_expr
|
24
|
+
|
25
|
+
with_expr: expr WITH expr_set_list { result = Fabulator::Expr::WithExpr.new(val[0], val[2]) }
|
26
|
+
|
27
|
+
expr_set_list: expr_set { result = Fabulator::Expr::StatementList.new; result.add_statement(val[0]) }
|
28
|
+
| expr_set_list COMMA expr_set { result = val[0]; result.add_statement(val[2]) }
|
29
|
+
|
30
|
+
expr_set: path_expr COLON_EQUAL expr { result = Fabulator::Expr::DataSet.new(val[0], val[2]) }
|
31
|
+
|
32
|
+
#| absolute_location_path COLON_EQUAL expr { result = Fabulator::Expr::DataSet.new(val[0], val[2]) }
|
33
|
+
|
34
|
+
num_expr: additive_expr
|
35
|
+
| range_expr
|
36
|
+
|
37
|
+
num_list: num_expr { result = [ val[0] ] }
|
38
|
+
| num_list COMMA num_expr { result = val[0] + [ val[2] ] }
|
39
|
+
|
40
|
+
let_expr: LET DOLLAR_QNAME COLON_EQUAL expr { result = Fabulator::Expr::LetExpr.new(val[1], val[3]) }
|
41
|
+
|
42
|
+
if_expr: IF LP expr RP THEN expr ELSE expr { result = Fabulator::Expr::IfExpr.new(val[2], val[5], val[7]) }
|
43
|
+
| IF LP expr RP THEN expr { result = Fabulator::Expr::IfExpr.new(val[2], val[5], nil) }
|
44
|
+
|
45
|
+
for_expr: FOR for_vars RETURN expr { result = Fabulator::Expr::ForExpr.new(val[1], val[3]) }
|
46
|
+
|
47
|
+
for_vars: for_var { result = [ val[0] ] }
|
48
|
+
| for_vars COMMA for_var { result = val[0] + [ val[2] ] }
|
49
|
+
|
50
|
+
for_var: DOLLAR_QNAME IN expr { result = Fabulator::Expr::ForVar.new(val[0], val[2]) }
|
51
|
+
|
52
|
+
quant_expr: SOME for_vars SATISFIES expr { result = Fabulator::Expr::SomeExpr.new(val[1], val[3]) }
|
53
|
+
| EVERY for_vars SATISFIES expr { result = Fabulator::Expr::EveryExpr.new(val[1], val[3]) }
|
54
|
+
|
55
|
+
or_expr: and_expr
|
56
|
+
| or_expr OR and_expr { result = Fabulator::Expr::OrExpr.new(val[0], val[2]) }
|
57
|
+
|
58
|
+
and_expr: equality_expr
|
59
|
+
| and_expr AND equality_expr { result = Fabulator::Expr::AndExpr.new(val[0], val[2]) }
|
60
|
+
| and_expr EXCEPT equality_expr { result = Fabulator::Expr::ExceptExpr.new(val[0], val[2]) }
|
61
|
+
|
62
|
+
equality_expr: relational_expr
|
63
|
+
| additive_expr EQ additive_expr { result = Fabulator::Expr::EqExpr.new(val[0], val[2]) }
|
64
|
+
| additive_expr NEQ additive_expr { result = Fabulator::Expr::NeqExpr.new(val[0], val[2]) }
|
65
|
+
|
66
|
+
tuple: LP LB args RB RP { result = Fabulator::Expr::Tuple.new(val[2]) }
|
67
|
+
|
68
|
+
# | LT args GT { result = Fabulator::Expr::Tuple.new(val[1]) }
|
69
|
+
|
70
|
+
relational_expr: additive_expr
|
71
|
+
| additive_expr LT additive_expr { result = Fabulator::Expr::LtExpr.new(val[0], val[2]) }
|
72
|
+
| additive_expr GT additive_expr { result = Fabulator::Expr::LtExpr.new(val[2], val[0]) }
|
73
|
+
| additive_expr LTE additive_expr { result = Fabulator::Expr::LteExpr.new(val[0], val[2]) }
|
74
|
+
| additive_expr GTE additive_expr { result = Fabulator::Expr::LteExpr.new(val[2], val[0]) }
|
75
|
+
|
76
|
+
# | additive_expr LT additive_expr LT additive_expr { result = Fabulator::Expr::Between.new(val[2], val[0], val[4]) }
|
77
|
+
# | additive_expr LT additive_expr LTE additive_expr { }
|
78
|
+
# | additive_expr LTE additive_expr LT additive_expr { }
|
79
|
+
# | additive_expr LTE additive_expr LTE additive_expr { }
|
80
|
+
# | additive_expr GT additive_expr GT additive_expr { result = Fabulator::Expr::Between.new(val[2], val[4], val[0]) }
|
81
|
+
# | additive_expr GT additive_expr GTE additive_expr { }
|
82
|
+
# | additive_expr GTE additive_expr GT additive_expr { }
|
83
|
+
# | additive_expr GTE additive_expr GTE additive_expr { }
|
84
|
+
|
85
|
+
range_expr: additive_expr DOT_DOT additive_expr { result = Fabulator::Expr::RangeExpr.new(val[0], val[2]) }
|
86
|
+
| additive_expr TO additive_expr { result = Fabulator::Expr::RangeExpr.new(val[0], val[2]) }
|
87
|
+
|
88
|
+
additive_expr: multiplicative_expr
|
89
|
+
| additive_expr PLUS multiplicative_expr { result = Fabulator::Expr::AddExpr.new(val[0], val[2]) }
|
90
|
+
| additive_expr MINUS multiplicative_expr { result = Fabulator::Expr::SubExpr.new(val[0], val[2]) }
|
91
|
+
|
92
|
+
multiplicative_expr: unary_expr
|
93
|
+
| multiplicative_expr STAR unary_expr { result = Fabulator::Expr::MpyExpr.new(val[0], val[2]) }
|
94
|
+
| multiplicative_expr DIV unary_expr { result = Fabulator::Expr::DivExpr.new(val[0], val[2]) }
|
95
|
+
| multiplicative_expr MOD unary_expr { result = Fabulator::Expr::ModExpr.new(val[0], val[2]) }
|
96
|
+
|
97
|
+
unary_expr: union_expr
|
98
|
+
| MINUS unary_expr { result = Fabulator::Expr::NegExpr.new(val[1]) }
|
99
|
+
|
100
|
+
union_expr: path_expr
|
101
|
+
| union_expr_x { result = Fabulator::Expr::UnionExpr.new(val[0]) }
|
102
|
+
|
103
|
+
union_expr_x: path_expr PIPE path_expr { result = [ val[0], val[2] ] }
|
104
|
+
| union_expr_x PIPE path_expr { result = val[0] + [ val[2] ] }
|
105
|
+
|
106
|
+
path_expr: location_path { result = Fabulator::Expr::PathExpr.new(nil, [], val[0]) } #result = Fabulator::Expr::PathExpr.new(nil, [], val[0]) }
|
107
|
+
| primary_expr predicates segment { result = Fabulator::Expr::PathExpr.new(val[0], val[1], val[2]) }
|
108
|
+
|
109
|
+
segment:
|
110
|
+
| SLASH relative_location_path { result = val[1] }
|
111
|
+
| SLASH_SLASH relative_location_path { result = [ Fabulator::Expr::AxisDescendentOrSelf.new ] + val[1] }
|
112
|
+
|
113
|
+
location_path: relative_location_path
|
114
|
+
| absolute_location_path
|
115
|
+
|
116
|
+
# / => local data root
|
117
|
+
# // => local data descendent or self
|
118
|
+
|
119
|
+
absolute_location_path: SLASH { result = Fabulator::Expr::RootContext.new }
|
120
|
+
| SLASH relative_location_path { result = Fabulator::Expr::PathExpr.new(Fabulator::Expr::RootContext.new, [], val[1]) }
|
121
|
+
| SLASH_SLASH relative_location_path { result = [ Fabulator::Expr::RootContext.new, Fabulator::Expr::AxisDescendentOrSelf.new(val[1][0]) ] + val[1][1..val[1].size-1] }
|
122
|
+
| axis_name SLASH relative_location_path { result = [ Fabulator::Expr::RootContext.new(val[0]) ] + val[2] }
|
123
|
+
| axis_name SLASH_SLASH relative_location_path { result = [ Fabulator::Expr::RootContext.new(val[0]), Fabulator::Expr::AxisDescendentOrSelf.new(val[2][0]) ] + val[2][1..val[2].size-1] }
|
124
|
+
|
125
|
+
relative_location_path: step { result = [ val[0] ] }
|
126
|
+
| relative_location_path SLASH step { result = val[0] + [ val[2] ] }
|
127
|
+
| relative_location_path SLASH_SLASH step { result = val[0] + [ Fabulator::Expr::AxisDescendentOrSelf.new(val[2]) ] }
|
128
|
+
|
129
|
+
step: axis predicates { result = val[1].nil? || val[1].empty? ? val[0] : Fabulator::Expr::Predicates.new(val[0], val[1]) }
|
130
|
+
| DOT { result = Fabulator::Expr::CurrentContext.new }
|
131
|
+
| DOT_DOT { result = Fabulator::Expr::AxisParent.new }
|
132
|
+
|
133
|
+
#| AXIS_METHOD COLON_COLON node_test predicates { result = Fabulator::Expr::Predicates.new(Fabulator::Expr::Function.new(@context, val[2], []), val[3]) }
|
134
|
+
#| AXIS_METHOD COLON_COLON FUNCTION_NAME list predicates { result = Fabulator::Expr::Predicates.new(Fabulator::Expr::Function.new(@context, val[2], val[3]), val[4]) }
|
135
|
+
#| axis LC DOLLAR_QNAME RC predicates { result = Fabulator::Expr::Predicates.new(Fabulator::Expr::Step.new(val[0], Fabulator::Expr::QName.new(val[3])), val[4]) }
|
136
|
+
|
137
|
+
axis: node_test { result = Fabulator::Expr::AxisChild.new(val[0]) }
|
138
|
+
| axis_name node_test { result = Fabulator::Expr::Axis.new(val[0], val[1]) }
|
139
|
+
| AT node_test { result = Fabulator::Expr::AxisAttribute.new(val[1]) }
|
140
|
+
|
141
|
+
axis_name: AXIS_NAME COLON_COLON { result = Fabulator::Expr::Axis.new(val[0]) }
|
142
|
+
|
143
|
+
predicates: { result = [ ] }
|
144
|
+
| predicates predicate { result = val[0] + [ val[1] ] }
|
145
|
+
|
146
|
+
predicate: LB expr RB { result = val[1] }
|
147
|
+
| LB num_list RB { result = Fabulator::Expr::IndexPredicate.new(val[1]) }
|
148
|
+
|
149
|
+
#| '<' expr '>'
|
150
|
+
primary_expr:
|
151
|
+
DOLLAR_QNAME { result = Fabulator::Expr::Var.new(val[0]) }
|
152
|
+
| LP expr RP { result = val[1] }
|
153
|
+
| list
|
154
|
+
| tuple
|
155
|
+
| LITERAL { result = Fabulator::Expr::Literal.new(val[0], [ Fabulator::FAB_NS, 'string' ]) }
|
156
|
+
| NUMBER { result = Fabulator::Expr::Literal.new(val[0] =~ /\./ ? val[0].to_d.to_r : val[0].to_i.to_r, [ Fabulator::FAB_NS, 'numeric' ]) }
|
157
|
+
| FUNCTION_NAME list { result = Fabulator::Expr::Function.new(@context, val[0], val[1]) }
|
158
|
+
|
159
|
+
list: LP opt_args RP { result = Fabulator::Expr::List.new(val[1]) }
|
160
|
+
|
161
|
+
|
162
|
+
opt_args: { result = [ ] }
|
163
|
+
| args
|
164
|
+
|
165
|
+
args: expr { result = [ val[0] ] }
|
166
|
+
| args COMMA expr { result = val[0] + [ val[2] ] }
|
167
|
+
|
168
|
+
node_test: QNAME
|
169
|
+
| NUMBER { result = val[0].to_s }
|
170
|
+
| LC expr RC { result = val[1] }
|
171
|
+
| STAR
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
---- inner
|
176
|
+
require 'fabulator/expr'
|
177
|
+
require 'rational'
|
178
|
+
require 'bigdecimal'
|
179
|
+
require 'bigdecimal/util'
|
180
|
+
|
181
|
+
def parse(t, ctx)
|
182
|
+
@source = t
|
183
|
+
@curpos = 0
|
184
|
+
@context = ctx
|
185
|
+
@line = 0
|
186
|
+
@col = 0
|
187
|
+
|
188
|
+
@yydebug = true
|
189
|
+
|
190
|
+
@last_token = nil
|
191
|
+
|
192
|
+
do_parse
|
193
|
+
end
|
194
|
+
|
195
|
+
def on_error(*args)
|
196
|
+
raise Fabulator::Expr::ParserError.new("unable to parse '#{args[1]}' near line #{@line + 1}, column #{@col}")
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
@@regex = {
|
201
|
+
:simple_tokens => %r{\.\.|::|!=|>=|<=|\/\/|:=|\.|@|[*]|\(|\)|\[|\]|\{|\}|\/|\||\+|-|=|>|<|&|,|;},
|
202
|
+
:ncname => %r{(?:[a-zA-Z_][-a-zA-Z0-9_.]*)},
|
203
|
+
:event_type => %r{(?:processing-instruction|comment|text|node)},
|
204
|
+
:axis_name => %r{(?:attribute|child|child-or-self|descendant|descendant-or-self|method|self)},
|
205
|
+
:namespace_name => %r{(?:context|global|local|session|universal)},
|
206
|
+
:number => %r{(-?\d+(?:\.\d+)?|\.\d+)},
|
207
|
+
}
|
208
|
+
|
209
|
+
@@regex[:axis] = %r{(#{@@regex[:ncname]})\s*(?=::)}
|
210
|
+
@@regex[:name_colon_star] = %r{(#{@@regex[:ncname]}:\*)}
|
211
|
+
@@regex[:qname] = %r{((?:#{@@regex[:ncname]}:)?#{@@regex[:ncname]})}
|
212
|
+
@@regex[:dollar_qname] = %r{\$#{@@regex[:qname]}}
|
213
|
+
@@regex[:dollar_int] = %r{\$([0-9]+)}
|
214
|
+
@@regex[:function_name] = %r{#{@@regex[:qname]}\*?\s*(?=\([^:])}
|
215
|
+
|
216
|
+
@@ops = {
|
217
|
+
'..' => :DOT_DOT,
|
218
|
+
'::' => :COLON_COLON,
|
219
|
+
'!=' => :NEQ,
|
220
|
+
'>=' => :GTE,
|
221
|
+
'<=' => :LTE,
|
222
|
+
'//' => :SLASH_SLASH,
|
223
|
+
':=' => :COLON_EQUAL,
|
224
|
+
'.' => :DOT,
|
225
|
+
'@' => :AT,
|
226
|
+
'*' => :STAR,
|
227
|
+
'(' => :LP,
|
228
|
+
')' => :RP,
|
229
|
+
'[' => :LB,
|
230
|
+
']' => :RB,
|
231
|
+
'{' => :LC,
|
232
|
+
'}' => :RC,
|
233
|
+
'/' => :SLASH,
|
234
|
+
'|' => :PIPE,
|
235
|
+
'+' => :PLUS,
|
236
|
+
'-' => :MINUS,
|
237
|
+
'=' => :EQ,
|
238
|
+
'>' => :GT,
|
239
|
+
'<' => :LT,
|
240
|
+
'&' => :AMP,
|
241
|
+
',' => :COMMA,
|
242
|
+
';' => :SEMI
|
243
|
+
}
|
244
|
+
|
245
|
+
@@preceding_tokens = { }
|
246
|
+
[%{
|
247
|
+
@ :: (
|
248
|
+
and or mod div
|
249
|
+
*
|
250
|
+
/ // | + - = != < <= > >=
|
251
|
+
== & && ||
|
252
|
+
}.split(/\s*/), '[', ',', '$' ].each { |t| @@preceding_tokens[t] = true }
|
253
|
+
|
254
|
+
@@regex[:general] = Regexp.compile(%{^(?:#{@@regex[:function_name]}|#{@@regex[:axis]}|#{@@regex[:name_colon_star]}|#{@@regex[:qname]}|('[^']*'|"[^"]*")|#{@@regex[:number]}|#{@@regex[:dollar_qname]}|#{@@regex[:dollar_int]}|(#{@@regex[:simple_tokens]}))})
|
255
|
+
|
256
|
+
def next_token
|
257
|
+
@token = nil
|
258
|
+
white_space = 0
|
259
|
+
new_line = 0
|
260
|
+
while @curpos < @source.length && @source[@curpos..@curpos] =~ /\s/ do
|
261
|
+
if @source[@curpos..@curpos] =~ /\n/
|
262
|
+
new_line = new_line + 1
|
263
|
+
@line = @line + 1
|
264
|
+
@col = 0
|
265
|
+
else
|
266
|
+
@col = @col + 1
|
267
|
+
end
|
268
|
+
@curpos = @curpos + 1
|
269
|
+
white_space = white_space + 1
|
270
|
+
end
|
271
|
+
|
272
|
+
# skip comments delimited by (: :)
|
273
|
+
# comments can be nested
|
274
|
+
# these are XPath 2.0 comments
|
275
|
+
#
|
276
|
+
if @curpos < @source.length && @source[@curpos..@curpos+1] == '(:'
|
277
|
+
comment_depth = 1
|
278
|
+
@curpos = @curpos + 2
|
279
|
+
@col = @col + 2
|
280
|
+
while comment_depth > 0 && @curpos < @source.length
|
281
|
+
if @source[@curpos..@curpos+1] == '(:'
|
282
|
+
comment_depth = comment_depth + 1
|
283
|
+
@curpos = @curpos + 1
|
284
|
+
@col = @col + 1
|
285
|
+
end
|
286
|
+
if @source[@curpos..@curpos+1] == ':)'
|
287
|
+
comment_depth = comment_depth - 1
|
288
|
+
@curpos = @curpos + 1
|
289
|
+
@col = @col + 1
|
290
|
+
end
|
291
|
+
@curpos = @curpos + 1
|
292
|
+
@col = @col + 1
|
293
|
+
end
|
294
|
+
white_space = white_space + 1
|
295
|
+
end
|
296
|
+
|
297
|
+
while @curpos < @source.length && @source[@curpos..@curpos] =~ /\s/ do
|
298
|
+
if @source[@curpos..@curpos] =~ /\n/
|
299
|
+
new_line = new_line + 1
|
300
|
+
@line = @line + 1
|
301
|
+
@col = 0
|
302
|
+
else
|
303
|
+
@col = @col + 1
|
304
|
+
end
|
305
|
+
@curpos = @curpos + 1
|
306
|
+
white_space = white_space + 1
|
307
|
+
end
|
308
|
+
|
309
|
+
if @curpos >= @source.length
|
310
|
+
@last_token = nil
|
311
|
+
return [ false, false ]
|
312
|
+
end
|
313
|
+
|
314
|
+
#if new_line > 0 || white_space > 0
|
315
|
+
# @token = [ :SP, '' ]
|
316
|
+
#end
|
317
|
+
|
318
|
+
if @token.nil? && @last_token && ! @@preceding_tokens[@last_token[1]]
|
319
|
+
if @source[@curpos..@curpos] == '*'
|
320
|
+
@token = [ :STAR, '*' ]
|
321
|
+
else
|
322
|
+
if @source[@curpos..@source.length-1] =~ /^(#{@@regex[:ncname]})/
|
323
|
+
ncname = $1
|
324
|
+
case ncname
|
325
|
+
when 'for':
|
326
|
+
@token = [ :FOR, 'for' ]
|
327
|
+
when 'return':
|
328
|
+
@token = [ :RETURN, 'return' ]
|
329
|
+
when 'in':
|
330
|
+
@token = [ :IN, 'in' ]
|
331
|
+
when 'let':
|
332
|
+
@token = [ :LET, 'let' ]
|
333
|
+
when 'except':
|
334
|
+
@token = [ :EXCEPT, 'except' ]
|
335
|
+
when 'every':
|
336
|
+
@token = [ :EVERY, 'every' ]
|
337
|
+
when 'some':
|
338
|
+
@token = [ :SOME, 'some' ]
|
339
|
+
when 'satisfies':
|
340
|
+
@token = [ :SATISFIES, 'satisfies' ]
|
341
|
+
when 'if':
|
342
|
+
@token = [ :IF, 'if' ]
|
343
|
+
when 'then':
|
344
|
+
@token = [ :THEN, 'then' ]
|
345
|
+
when 'else':
|
346
|
+
@token = [ :ELSE, 'else' ]
|
347
|
+
when 'to':
|
348
|
+
@token = [ :TO, 'to' ]
|
349
|
+
when 'and':
|
350
|
+
@token = [ :AND, 'and' ]
|
351
|
+
when 'or':
|
352
|
+
@token = [ :OR, 'or' ]
|
353
|
+
when 'mod':
|
354
|
+
@token = [ :MOD, 'mod' ]
|
355
|
+
when 'div':
|
356
|
+
@token = [ :DIV, 'div' ]
|
357
|
+
#when '*doh*':
|
358
|
+
# # do nothing
|
359
|
+
# @token = nil
|
360
|
+
else
|
361
|
+
@token = nil
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
if @token.nil? && @source[@curpos..@curpos+1] == '..'
|
368
|
+
@token = [ :DOT_DOT, '..' ]
|
369
|
+
end
|
370
|
+
|
371
|
+
if @token.nil?
|
372
|
+
if @curpos >= @source.length
|
373
|
+
@token = [false, false]
|
374
|
+
return @token
|
375
|
+
end
|
376
|
+
|
377
|
+
res = @@regex[:general].match(@source[@curpos..@source.length-1])
|
378
|
+
#@source[@curpos..@source.length-1] =~ @@regex[:general]
|
379
|
+
#res = [ nil, $1, $2, $3, $4, $5, $6, $7, $8 ]
|
380
|
+
if res.nil?
|
381
|
+
raise "Failed to parse '#{@source}' at #{@curpos}': #{@source[@curpos..@source.length-1]}"
|
382
|
+
else
|
383
|
+
if !res[1].nil?
|
384
|
+
if res[1] == 'if'
|
385
|
+
@token = [ :IF, 'if' ]
|
386
|
+
else
|
387
|
+
if @source[@curpos+res[1].length .. @curpos+res[1].length] == '*'
|
388
|
+
@token = [ :FUNCTION_NAME, res[1]+'*' ]
|
389
|
+
else
|
390
|
+
@token = [ :FUNCTION_NAME, res[1] ]
|
391
|
+
end
|
392
|
+
end
|
393
|
+
elsif !res[2].nil?
|
394
|
+
@token = [ res[2] == 'method' ? :AXIS_METHOD : :AXIS_NAME, res[2] ]
|
395
|
+
elsif !res[3].nil?
|
396
|
+
@token = [ :NAME_COLON_STAR, res[3] ]
|
397
|
+
elsif !res[4].nil?
|
398
|
+
qname = res[4]
|
399
|
+
case qname
|
400
|
+
when 'for':
|
401
|
+
@token = [ :FOR, 'for' ]
|
402
|
+
when 'return':
|
403
|
+
@token = [ :RETURN, 'return' ]
|
404
|
+
when 'in':
|
405
|
+
@token = [ :IN, 'in' ]
|
406
|
+
when 'let':
|
407
|
+
@token = [ :LET, 'let' ]
|
408
|
+
when 'except':
|
409
|
+
@token = [ :EXCEPT, 'except' ]
|
410
|
+
when 'every':
|
411
|
+
@token = [ :EVERY, 'every' ]
|
412
|
+
when 'some':
|
413
|
+
@token = [ :SOME, 'some' ]
|
414
|
+
when 'satisfies':
|
415
|
+
@token = [ :SATISFIES, 'satisfies' ]
|
416
|
+
when 'if':
|
417
|
+
@token = [ :IF, 'if' ]
|
418
|
+
when 'then':
|
419
|
+
@token = [ :THEN, 'then' ]
|
420
|
+
when 'else':
|
421
|
+
@token = [ :ELSE, 'else' ]
|
422
|
+
when 'with':
|
423
|
+
@token = [ :WITH, 'with' ]
|
424
|
+
else
|
425
|
+
@token = [ :QNAME, res[4] ]
|
426
|
+
end
|
427
|
+
elsif !res[5].nil?
|
428
|
+
s = res[5]
|
429
|
+
s = s[1..s.length-2]
|
430
|
+
@curpos = @curpos + s.length
|
431
|
+
@col = @col + s.length
|
432
|
+
s.gsub!(/\\n/, "\n")
|
433
|
+
@curpos = @curpos - s.length
|
434
|
+
@col = @col - s.length
|
435
|
+
@token = [ :LITERAL, s ]
|
436
|
+
@curpos = @curpos + 2 # the quotes
|
437
|
+
@col = @col + 2
|
438
|
+
elsif !res[6].nil?
|
439
|
+
@token = [ :NUMBER, res[6] ]
|
440
|
+
elsif !res[7].nil?
|
441
|
+
@curpos = @curpos + 1
|
442
|
+
@col = @col + 1
|
443
|
+
@token = [ :DOLLAR_QNAME, res[7] ]
|
444
|
+
elsif !res[8].nil?
|
445
|
+
@curpos = @curpos + 1
|
446
|
+
@col = @col + 1
|
447
|
+
@token = [ :DOLLAR_QNAME, res[8] ]
|
448
|
+
elsif !res[9].nil?
|
449
|
+
@token = [ @@ops[res[9]] || res[9], res[9] ]
|
450
|
+
else
|
451
|
+
raise "Failed to parse '#{@source}' at #{@curpos}: #{@source[@curpos..@source.length-1]}"
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
if !@token[1].nil?
|
457
|
+
@curpos = @curpos + @token[1].length
|
458
|
+
@col = @col + @token[1].length
|
459
|
+
end
|
460
|
+
@last_token = @token
|
461
|
+
return @token
|
462
|
+
end
|