fabulator 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|