fabulator 0.0.1 → 0.0.2

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