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.
@@ -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
@@ -0,0 +1,6 @@
1
+ # This file makes it possible to install Fabulator as a Rails plugin.
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__))+'/lib'
4
+
5
+ require 'fabulator'
6
+
@@ -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
- function 'histogram' do |ctx, args|
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
- reduction 'consolidate', { :scaling => :log } do |ctx, args|
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 compile_xml(xml, context = nil, callbacks = { })
20
- # /statemachine/states
21
- @states ||= { }
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 = Fabulator::Expr::Context.new.merge(xml.root)
32
+ @context = @context.merge(xml.root)
30
33
  else
31
34
  @context = context.merge(xml.root)
32
35
  end
@@ -5,6 +5,9 @@ module Fabulator
5
5
  bits = nom.split(/:/, 2)
6
6
  @ns = ctx.get_ns(bits[0])
7
7
  @name = bits[1]
8
+ if @name =~ /^(.+)\*$/
9
+ @name = "consolidation:#{$1}"
10
+ end
8
11
  @args = args
9
12
  @ctx = ctx
10
13
  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 #.class.new(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]}\s*(?=\([^:])}
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
- @token = [ :FUNCTION_NAME, res[1] ]
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 'xml/xslt'
2
+ require 'libxslt'
3
3
 
4
- @@fabulator_xslt_file = File.join(File.dirname(__FILE__), "..", "..", "..", "xslt", "form.xsl")
4
+ module Fabulator::Template
5
+ class ParseResult
5
6
 
6
- @@fabulator_xmlt = LibXML::XML::Document.file(@@fabulator_xslt_file)
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
- xslt = XML::XSLT.new
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