fabulator-grammar 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/features/library.feature +133 -0
- data/features/step_definitions/xml_steps.rb +39 -1
- data/features/support/env.rb +1 -0
- data/lib/fabulator/grammar/actions/grammar.rb +61 -5
- data/lib/fabulator/grammar/actions/result.rb +23 -0
- data/lib/fabulator/grammar/actions/rule.rb +16 -16
- data/lib/fabulator/grammar/actions/when.rb +1 -3
- data/lib/fabulator/grammar/actions.rb +2 -1
- data/lib/fabulator/grammar/cursor.rb +60 -10
- data/lib/fabulator/grammar/expr/anchor.rb +4 -4
- data/lib/fabulator/grammar/expr/look_ahead.rb +6 -6
- data/lib/fabulator/grammar/expr/rule.rb +12 -4
- data/lib/fabulator/grammar/expr/rule_alternative.rb +17 -22
- data/lib/fabulator/grammar/expr/rule_mode.rb +4 -1
- data/lib/fabulator/grammar/expr/rule_ref.rb +6 -2
- data/lib/fabulator/grammar/expr/rule_sequence.rb +41 -32
- data/lib/fabulator/grammar/expr/set_skip.rb +0 -1
- data/lib/fabulator/grammar/expr/token.rb +1 -6
- data/lib/fabulator/grammar.rb +2 -0
- metadata +9 -7
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ begin
|
|
7
7
|
gem.email = "jgsmith@tamu.edu"
|
8
8
|
gem.homepage = "http://github.com/jgsmith/ruby-fabulator-grammar"
|
9
9
|
gem.authors = ["James Smith"]
|
10
|
-
gem.add_dependency(%q<fabulator>, [">= 0.0.
|
10
|
+
gem.add_dependency(%q<fabulator>, [">= 0.0.8"])
|
11
11
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
12
12
|
# not sure how to add dependency of a library that's not a gem
|
13
13
|
gem.requirements << 'bitset, 1.0 or greater'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
@@ -0,0 +1,133 @@
|
|
1
|
+
@library
|
2
|
+
Feature: Grammars embedded in libraries
|
3
|
+
|
4
|
+
Scenario: Parsing a grammar xml definition
|
5
|
+
Given a context
|
6
|
+
And the prefix m as "http://example.com/ns/grammar"
|
7
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
8
|
+
Given the library
|
9
|
+
"""
|
10
|
+
<l:library
|
11
|
+
xmlns:l="http://dh.tamu.edu/ns/fabulator/library/1.0#"
|
12
|
+
xmlns:g="http://dh.tamu.edu/ns/fabulator/grammar/1.0#"
|
13
|
+
xmlns:f="http://dh.tamu.edu/ns/fabulator/1.0#"
|
14
|
+
l:ns="http://example.com/ns/grammar"
|
15
|
+
>
|
16
|
+
<g:grammar>
|
17
|
+
<g:context g:mode="normal">
|
18
|
+
<g:token g:name="LETTER" g:matches="[:alpha:]" />
|
19
|
+
</g:context>
|
20
|
+
<g:token g:name="NUMBER" g:matches="[:digit:]" />
|
21
|
+
<g:token g:name="LETTER" g:matches="[:upper:]" g:mode="upper"/>
|
22
|
+
<g:token g:name="LETTER" g:matches="[:lower:]" g:mode="lower"/>
|
23
|
+
<g:rule g:name="something">
|
24
|
+
<g:when g:matches="^^ [mode normal] LETTER NUMBER [mode upper] LETTER" />
|
25
|
+
</g:rule>
|
26
|
+
<g:rule g:name="something2">
|
27
|
+
<g:when g:matches="^^ [mode normal] LETTER NUMBER [mode upper] LETTER">
|
28
|
+
<g:result g:path="foo" f:select="NUMBER" />
|
29
|
+
</g:when>
|
30
|
+
</g:rule>
|
31
|
+
</g:grammar>
|
32
|
+
</l:library>
|
33
|
+
"""
|
34
|
+
Then the expression (m:something?('a0A')) should equal [f:true()]
|
35
|
+
And the expression (m:something?('a0a')) should equal [f:false()]
|
36
|
+
And the expression (m:something('a0A')/NUMBER) should equal ['0']
|
37
|
+
And the expression (m:something2('a0A')/foo) should equal ['0']
|
38
|
+
|
39
|
+
@library
|
40
|
+
Scenario: Using a grammar for filters and constraints
|
41
|
+
Given a context
|
42
|
+
And the prefix m as "http://example.com/ns/grammar"
|
43
|
+
And the prefix f as "http://dh.tamu.edu/ns/fabulator/1.0#"
|
44
|
+
Given the library
|
45
|
+
"""
|
46
|
+
<l:library
|
47
|
+
xmlns:l="http://dh.tamu.edu/ns/fabulator/library/1.0#"
|
48
|
+
xmlns:g="http://dh.tamu.edu/ns/fabulator/grammar/1.0#"
|
49
|
+
xmlns:f="http://dh.tamu.edu/ns/fabulator/1.0#"
|
50
|
+
l:ns="http://example.com/ns/grammar"
|
51
|
+
>
|
52
|
+
<g:grammar>
|
53
|
+
<g:context g:mode="normal">
|
54
|
+
<g:token g:name="LETTER" g:matches="[:alpha:]" />
|
55
|
+
</g:context>
|
56
|
+
<g:token g:name="NUMBER" g:matches="[:digit:]" />
|
57
|
+
<g:token g:name="LETTER" g:matches="[:upper:]" g:mode="upper"/>
|
58
|
+
<g:token g:name="LETTER" g:matches="[:lower:]" g:mode="lower"/>
|
59
|
+
<g:rule g:name="something">
|
60
|
+
<g:when g:matches="[mode normal] LETTER NUMBER [mode upper] LETTER" />
|
61
|
+
</g:rule>
|
62
|
+
<g:rule g:name="something2">
|
63
|
+
<g:when g:matches="[mode normal] LETTER NUMBER [mode upper] LETTER">
|
64
|
+
<g:result g:path="foo" f:select="NUMBER" />
|
65
|
+
</g:when>
|
66
|
+
</g:rule>
|
67
|
+
</g:grammar>
|
68
|
+
<l:mapping l:name="double">
|
69
|
+
<f:value-of f:select=". * 2" />
|
70
|
+
</l:mapping>
|
71
|
+
<l:function l:name="fctn">
|
72
|
+
<f:value-of f:select="$1 - $2" />
|
73
|
+
</l:function>
|
74
|
+
<l:action l:name="actn" l:has-actions="true">
|
75
|
+
<f:value-of f:select="f:eval($actions) * 3" />
|
76
|
+
</l:action>
|
77
|
+
</l:library>
|
78
|
+
"""
|
79
|
+
And the statemachine
|
80
|
+
"""
|
81
|
+
<f:application xmlns:f="http://dh.tamu.edu/ns/fabulator/1.0#"
|
82
|
+
xmlns:m="http://example.com/ns/grammar"
|
83
|
+
>
|
84
|
+
<f:view f:name="start">
|
85
|
+
<f:goes-to f:view="step1">
|
86
|
+
<f:params>
|
87
|
+
<f:param f:name="foo">
|
88
|
+
<f:filter f:name="m:something" />
|
89
|
+
<f:value>a0A</f:value>
|
90
|
+
</f:param>
|
91
|
+
</f:params>
|
92
|
+
<f:value f:path="barbell" f:select="m:double(3)" />
|
93
|
+
<f:value f:path="barboil">
|
94
|
+
<m:actn><f:value-of f:select="7" /></m:actn>
|
95
|
+
</f:value>
|
96
|
+
</f:goes-to>
|
97
|
+
</f:view>
|
98
|
+
<f:view f:name="step1">
|
99
|
+
<f:goes-to f:view="stop">
|
100
|
+
<f:params>
|
101
|
+
<f:param f:name="bar">
|
102
|
+
<f:filter f:name="trim" />
|
103
|
+
<f:constraint f:name="m:something" />
|
104
|
+
</f:param>
|
105
|
+
</f:params>
|
106
|
+
</f:goes-to>
|
107
|
+
</f:view>
|
108
|
+
<f:view f:name="stop" />
|
109
|
+
</f:application>
|
110
|
+
"""
|
111
|
+
Then it should be in the 'start' state
|
112
|
+
When I run it with the following params:
|
113
|
+
| key | value |
|
114
|
+
| foo | bar a0a que |
|
115
|
+
Then it should be in the 'start' state
|
116
|
+
And the expression (/foo) should be nil
|
117
|
+
When I run it with the following params:
|
118
|
+
| key | value |
|
119
|
+
| foo | bara0Aque |
|
120
|
+
Then it should be in the 'step1' state
|
121
|
+
And the expression (/foo) should equal ['a0A']
|
122
|
+
And the expression (/barbell) should equal [6]
|
123
|
+
And the expression (/barboil) should equal [21]
|
124
|
+
When I run it with the following params:
|
125
|
+
| key | value |
|
126
|
+
| bar | a0a |
|
127
|
+
Then it should be in the 'step1' state
|
128
|
+
When I run it with the following params:
|
129
|
+
| key | value |
|
130
|
+
| bar | a0B |
|
131
|
+
Then it should be in the 'stop' state
|
132
|
+
And the expression (/bar) should equal ['a0B']
|
133
|
+
And the expression (m:fctn(3,2)) should equal [1]
|
@@ -2,7 +2,8 @@ Given /the grammar/ do |doc_xml|
|
|
2
2
|
@context ||= Fabulator::Expr::Context.new
|
3
3
|
|
4
4
|
if @grammar.nil?
|
5
|
-
@grammar = Fabulator::Grammar::Actions::Grammar.new
|
5
|
+
@grammar = Fabulator::Grammar::Actions::Grammar.new
|
6
|
+
@grammar.compile_xml(doc_xml, @context)
|
6
7
|
else
|
7
8
|
@grammar.compile_xml(doc_xml, @context)
|
8
9
|
end
|
@@ -10,3 +11,40 @@ Given /the grammar/ do |doc_xml|
|
|
10
11
|
# puts YAML::dump(@grammar)
|
11
12
|
end
|
12
13
|
|
14
|
+
Given /the library/ do |doc_xml|
|
15
|
+
@context ||= Fabulator::Expr::Context.new
|
16
|
+
|
17
|
+
if @library.nil?
|
18
|
+
@library = Fabulator::Lib::Lib.new
|
19
|
+
@library.compile_xml(doc_xml, @context)
|
20
|
+
else
|
21
|
+
@library.compile_xml(doc_xml, @context)
|
22
|
+
end
|
23
|
+
|
24
|
+
@library.register_library
|
25
|
+
end
|
26
|
+
|
27
|
+
Given /the statemachine/ do |doc_xml|
|
28
|
+
@context ||= Fabulator::Expr::Context.new
|
29
|
+
|
30
|
+
if @sm.nil?
|
31
|
+
@sm = Fabulator::Core::StateMachine.new
|
32
|
+
@sm.compile_xml(doc_xml)
|
33
|
+
else
|
34
|
+
@sm.compile_xml(doc_xml)
|
35
|
+
end
|
36
|
+
@sm.init_context(@context)
|
37
|
+
end
|
38
|
+
|
39
|
+
When /I run it with the following params:/ do |param_table|
|
40
|
+
params = { }
|
41
|
+
param_table.hashes.each do |hash|
|
42
|
+
params[hash['key']] = hash['value']
|
43
|
+
end
|
44
|
+
@sm.run(params)
|
45
|
+
end
|
46
|
+
|
47
|
+
Then /it should be in the '(.*)' state/ do |s|
|
48
|
+
@sm.state.should == s
|
49
|
+
end
|
50
|
+
|
data/features/support/env.rb
CHANGED
@@ -5,14 +5,18 @@ module Fabulator
|
|
5
5
|
|
6
6
|
namespace GRAMMAR_NS
|
7
7
|
|
8
|
+
element :grammar
|
9
|
+
|
8
10
|
contains :rule
|
9
11
|
contains :token
|
10
12
|
contains :context
|
11
13
|
|
14
|
+
contained_in Fabulator::FAB_LIB_NS, :library
|
15
|
+
|
12
16
|
has_actions
|
13
17
|
|
14
18
|
def compile_xml(xml, context = nil)
|
15
|
-
|
19
|
+
super
|
16
20
|
|
17
21
|
@modes = { :default => { } }
|
18
22
|
|
@@ -33,8 +37,6 @@ module Fabulator
|
|
33
37
|
|
34
38
|
@tokens = nil
|
35
39
|
@rules = nil
|
36
|
-
|
37
|
-
self
|
38
40
|
end
|
39
41
|
|
40
42
|
def add_rule(r, m = :default)
|
@@ -53,7 +55,7 @@ module Fabulator
|
|
53
55
|
cursor.anchored = true
|
54
56
|
ret = do_parse(nom, cursor)
|
55
57
|
cursor.do_skip
|
56
|
-
cursor.eof ? ret : nil
|
58
|
+
cursor.eof? ? ret : nil
|
57
59
|
end
|
58
60
|
|
59
61
|
def match(ctx, nom, s)
|
@@ -61,13 +63,67 @@ module Fabulator
|
|
61
63
|
!do_parse(nom, cursor).nil?
|
62
64
|
end
|
63
65
|
|
66
|
+
def run_function(context, nom, args)
|
67
|
+
# treat these as mappings
|
68
|
+
strings = args.collect{ |a| a.to_s }
|
69
|
+
matching = false
|
70
|
+
if nom =~ /\?$/
|
71
|
+
matching = true
|
72
|
+
end
|
73
|
+
nom.gsub!(/\?$/, '')
|
74
|
+
ret = matching ? strings.collect{ |s| self.match(context, nom, s) } :
|
75
|
+
strings.collect{ |s| self.parse(context, nom, s) }
|
76
|
+
#ret -= [ nil ]
|
77
|
+
if matching
|
78
|
+
ret = ret.collect{ |r| context.root.anon_node(!!r, [FAB_NS, 'boolean']) }
|
79
|
+
else
|
80
|
+
while ret.select{ |r| r.name.nil? && r.value.nil? }.size > 0
|
81
|
+
ret = ret.collect{ |r|
|
82
|
+
if r.name.nil? && r.value.nil?
|
83
|
+
r.children
|
84
|
+
else
|
85
|
+
r
|
86
|
+
end
|
87
|
+
}.flatten - [ nil ]
|
88
|
+
end
|
89
|
+
if !ret.empty?
|
90
|
+
new_ret = ret.first.roots['data'].anon_node(nil)
|
91
|
+
ret.each do |r|
|
92
|
+
new_ret.add_child(r)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
ret = [ new_ret ]
|
96
|
+
end
|
97
|
+
ret
|
98
|
+
end
|
99
|
+
|
100
|
+
def run_filter(ctx, nom)
|
101
|
+
# runs as a parser and replaces the string with the resulting
|
102
|
+
# content that matched
|
103
|
+
source = ctx.root.to_s
|
104
|
+
cursor = Fabulator::Grammar::Cursor.new(self, ctx, source)
|
105
|
+
ret = do_parse(nom, cursor)
|
106
|
+
ctx.root.value = ret.nil? ? '' : source[cursor.start .. cursor.pos-1]
|
107
|
+
end
|
108
|
+
|
109
|
+
def run_constraint(ctx, nom)
|
110
|
+
# runs as a match and requires full anchored matching
|
111
|
+
!self.parse(ctx, nom, ctx.root.to_s).nil?
|
112
|
+
end
|
113
|
+
|
64
114
|
protected
|
65
115
|
|
66
116
|
def do_parse(nom, cursor)
|
67
117
|
obj = get_rule(:default, nom)
|
68
118
|
return nil if obj.nil?
|
69
119
|
|
70
|
-
|
120
|
+
begin
|
121
|
+
obj.parse(cursor)
|
122
|
+
rescue Fabulator::Grammar::RejectParse
|
123
|
+
return nil
|
124
|
+
else
|
125
|
+
return cursor.context.root
|
126
|
+
end
|
71
127
|
end
|
72
128
|
|
73
129
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module Grammar
|
3
|
+
module Actions
|
4
|
+
class Result < Fabulator::Action
|
5
|
+
attr_accessor :select, :name
|
6
|
+
|
7
|
+
namespace Fabulator::GRAMMAR_NS
|
8
|
+
attribute :path, :static => true
|
9
|
+
has_select
|
10
|
+
has_actions
|
11
|
+
|
12
|
+
def run(context, autovivify = false)
|
13
|
+
@context.with(context) do |ctx|
|
14
|
+
values = self.has_actions? ? self.run_actions(ctx) : @select.run(ctx,false)
|
15
|
+
if !values.nil?
|
16
|
+
ctx.with_root(ctx.root.roots['result']).set_value(self.path, values)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -21,29 +21,29 @@ module Fabulator
|
|
21
21
|
# try each when...
|
22
22
|
best_attempt = nil
|
23
23
|
@choices.each do |choice|
|
24
|
-
cursor.attempt
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
}
|
34
|
-
end
|
24
|
+
ret = cursor.attempt { |c| choice.parse(c) }
|
25
|
+
if !ret.nil?
|
26
|
+
score = choice.score(cursor.context, ret)
|
27
|
+
if best_attempt.nil? || best_attempt[:score] < score
|
28
|
+
best_attempt = {
|
29
|
+
:score => score,
|
30
|
+
:choice => choice,
|
31
|
+
:ret => ret
|
32
|
+
}
|
35
33
|
end
|
36
34
|
end
|
37
35
|
end
|
38
|
-
|
36
|
+
raise Fabulator::Grammar::RejectParse if best_attempt.nil?
|
39
37
|
choice = best_attempt[:choice]
|
40
38
|
ret = best_attempt[:ret]
|
41
39
|
if choice.has_actions?
|
42
|
-
|
43
|
-
|
44
|
-
choice.run(
|
40
|
+
cursor.context.root.roots['result'] = cursor.context.root.roots['result'].anon_node(nil)
|
41
|
+
new_ret = cursor.context.root.roots['result']
|
42
|
+
choice.run(cursor.context.with_root(ret))
|
43
|
+
cursor.set_result(new_ret)
|
44
|
+
else
|
45
|
+
cursor.set_result(ret)
|
45
46
|
end
|
46
|
-
return ret
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
@@ -25,9 +25,7 @@ module Fabulator
|
|
25
25
|
|
26
26
|
def score(context, data)
|
27
27
|
return 0 if @score.nil?
|
28
|
-
|
29
|
-
ctx.merge_data(data)
|
30
|
-
(self.score(ctx).value rescue 0)
|
28
|
+
(@score.run(context.with_root(context.root.roots['result'])).value rescue 0)
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
@@ -7,6 +7,7 @@ module Fabulator
|
|
7
7
|
require 'fabulator/grammar/actions/rule'
|
8
8
|
require 'fabulator/grammar/actions/token'
|
9
9
|
require 'fabulator/grammar/actions/when'
|
10
|
+
require 'fabulator/grammar/actions/result'
|
10
11
|
|
11
12
|
module Grammar
|
12
13
|
module Actions
|
@@ -19,7 +20,7 @@ module Fabulator
|
|
19
20
|
structural 'token', Token
|
20
21
|
structural 'when', When
|
21
22
|
|
22
|
-
|
23
|
+
action 'result', Result
|
23
24
|
|
24
25
|
## reference a grammar name
|
25
26
|
function 'match' do |ctx, args|
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Fabulator
|
2
2
|
module Grammar
|
3
3
|
class Cursor
|
4
|
-
attr_accessor :mode, :skip
|
4
|
+
attr_accessor :mode, :skip, :start
|
5
5
|
|
6
6
|
def initialize(g,ctx,s)
|
7
7
|
@source = s
|
8
8
|
@grammar = g
|
9
|
+
@start = 0
|
9
10
|
@curpos = 0
|
10
11
|
@end = @source.length-1
|
11
12
|
@line = 0
|
@@ -14,6 +15,8 @@ module Fabulator
|
|
14
15
|
@mode = :default
|
15
16
|
@skip = nil
|
16
17
|
@context = ctx.with_root(ctx.root.anon_node(nil))
|
18
|
+
@context.root.roots['result'] = @context.root
|
19
|
+
@context.root.axis = 'result'
|
17
20
|
end
|
18
21
|
|
19
22
|
def context
|
@@ -30,7 +33,7 @@ module Fabulator
|
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
33
|
-
def eof
|
36
|
+
def eof?
|
34
37
|
@curpos > @end
|
35
38
|
end
|
36
39
|
|
@@ -51,7 +54,10 @@ module Fabulator
|
|
51
54
|
end
|
52
55
|
|
53
56
|
def point
|
54
|
-
{ :curpos => @curpos, :line => @line, :col => @col, :root => @context.root, :mode => @mode, :anchored => @anchored, :skip => @skip }
|
57
|
+
r = { :curpos => @curpos, :line => @line, :col => @col, :root => @context.root, :mode => @mode, :anchored => @anchored, :skip => @skip, :result => @context.root.roots['result'] }
|
58
|
+
@context.root.roots['result'] = r[:result].anon_node(nil)
|
59
|
+
@context.root = @context.root.roots['result']
|
60
|
+
r
|
55
61
|
end
|
56
62
|
|
57
63
|
def point=(p)
|
@@ -62,19 +68,63 @@ module Fabulator
|
|
62
68
|
@anchored = p[:anchored]
|
63
69
|
@skip = p[:skip]
|
64
70
|
@context.root = p[:root]
|
71
|
+
@context.root.roots['result'] = p[:result]
|
65
72
|
end
|
66
73
|
|
67
74
|
def attempt(&block)
|
68
75
|
saved = self.point
|
69
|
-
|
70
|
-
|
76
|
+
begin
|
77
|
+
yield self
|
78
|
+
rescue Fabulator::Grammar::RejectParse
|
71
79
|
self.point = saved
|
72
80
|
return nil
|
73
81
|
end
|
74
|
-
|
82
|
+
|
83
|
+
ret = @context.root.roots['result']
|
84
|
+
@context.root = saved[:root]
|
85
|
+
@context.root.roots['result'] = saved[:result]
|
75
86
|
return ret
|
76
87
|
end
|
77
88
|
|
89
|
+
def set_result(r)
|
90
|
+
r = [ r ] unless r.is_a?(Array)
|
91
|
+
if r.size > 1
|
92
|
+
@context.root = @context.root.anon_node(nil)
|
93
|
+
r.each { |rr| @context.root.add_child(rr) }
|
94
|
+
elsif !r.empty?
|
95
|
+
@context.root = r.first
|
96
|
+
end
|
97
|
+
@context.root.roots['result'] = @context.root
|
98
|
+
end
|
99
|
+
|
100
|
+
def name_result(nom = nil)
|
101
|
+
return if nom.nil?
|
102
|
+
return if @context.root.value.nil? && @context.root.children.empty?
|
103
|
+
if @context.root.value.nil?
|
104
|
+
@context.root.name = nom
|
105
|
+
else
|
106
|
+
if @context.root.name.nil?
|
107
|
+
@context.root.name = nom
|
108
|
+
else
|
109
|
+
n = @context.root.anon_node(nil)
|
110
|
+
n.name = nom
|
111
|
+
n.add_child(@context.root)
|
112
|
+
@context.root = n
|
113
|
+
@context.root.roots['result'] = @context.root
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def rename_result(nom = nil)
|
119
|
+
return if nom.nil?
|
120
|
+
return if @context.root.value.nil? && @context.root.children.empty?
|
121
|
+
# if @context.root.children.select{ |c| !c.name.nil? }.empty?
|
122
|
+
# @context.root.children.each { |c| c.name = nom }
|
123
|
+
# else
|
124
|
+
@context.root.name = nom
|
125
|
+
# end
|
126
|
+
end
|
127
|
+
|
78
128
|
def find_rule(nom)
|
79
129
|
r = @grammar.get_rule(@mode, nom)
|
80
130
|
if r.nil? && @mode.to_s != 'default'
|
@@ -105,13 +155,13 @@ module Fabulator
|
|
105
155
|
end
|
106
156
|
|
107
157
|
def match_token(regex)
|
108
|
-
res = nil
|
109
158
|
do_skip
|
110
159
|
if @source[@curpos .. @end] =~ %r{^(#{regex})}
|
111
|
-
|
112
|
-
@curpos +=
|
160
|
+
@context.root.value = $1.to_s
|
161
|
+
@curpos += @context.root.value.length
|
162
|
+
else
|
163
|
+
raise Fabulator::Grammar::RejectParse
|
113
164
|
end
|
114
|
-
res
|
115
165
|
end
|
116
166
|
end
|
117
167
|
end
|
@@ -11,16 +11,16 @@ module Fabulator
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def parse(source)
|
14
|
-
ret =
|
14
|
+
ret = false
|
15
15
|
case @anchor
|
16
16
|
when :start_of_string:
|
17
|
-
ret = source.pos == 0
|
17
|
+
ret = source.pos == 0
|
18
18
|
when :start_of_line:
|
19
19
|
when :end_of_string:
|
20
|
-
ret = source.eof
|
20
|
+
ret = source.eof?
|
21
21
|
when :end_of_line:
|
22
22
|
end
|
23
|
-
ret
|
23
|
+
raise Fabulator::Grammar::RejectParse unless ret
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -11,12 +11,12 @@ module Fabulator
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def parse(source)
|
14
|
-
ret =
|
14
|
+
ret = false
|
15
15
|
source.attempt do |c|
|
16
16
|
ret = @sequence.parse(c)
|
17
|
-
|
17
|
+
raise Fabulator::Grammar::RejectParse
|
18
18
|
end
|
19
|
-
|
19
|
+
raise Fabulator::Grammar::RejectParse unless ret
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -30,12 +30,12 @@ module Fabulator
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def parse(source)
|
33
|
-
ret =
|
33
|
+
ret = false
|
34
34
|
source.attempt do |c|
|
35
35
|
ret = @sequence.parse(c)
|
36
|
-
|
36
|
+
raise Fabulator::Grammar::RejectParse
|
37
37
|
end
|
38
|
-
|
38
|
+
raise Fabulator::Grammar::RejectParse if ret
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -19,21 +19,29 @@ module Fabulator
|
|
19
19
|
cursor.anchored = true
|
20
20
|
alternative.parse(cursor)
|
21
21
|
}
|
22
|
-
|
22
|
+
if !ret.nil?
|
23
|
+
s.set_result(ret)
|
24
|
+
return
|
25
|
+
end
|
23
26
|
end
|
24
27
|
else
|
25
|
-
while !s.eof
|
28
|
+
while !s.eof?
|
29
|
+
start = s.pos
|
26
30
|
@alternatives.each do |alternative|
|
27
31
|
ret = s.attempt { |cursor|
|
28
32
|
cursor.anchored = true
|
29
33
|
alternative.parse(cursor)
|
30
34
|
}
|
31
|
-
|
35
|
+
if !ret.nil?
|
36
|
+
s.start = start
|
37
|
+
s.set_result(ret)
|
38
|
+
return
|
39
|
+
end
|
32
40
|
end
|
33
41
|
s.advance_position(1)
|
34
42
|
end
|
35
43
|
end
|
36
|
-
|
44
|
+
raise Fabulator::Grammar::RejectParse
|
37
45
|
end
|
38
46
|
end
|
39
47
|
end
|
@@ -12,32 +12,27 @@ module Fabulator
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def parse(source)
|
15
|
-
results =
|
15
|
+
results = [ ]
|
16
16
|
@sequences.each do |sequence|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
if !(r.is_a?(Hash) || r.is_a?(Array)) || !r.empty?
|
25
|
-
rr[nom] = r
|
26
|
-
end
|
27
|
-
end
|
28
|
-
if rr.is_a?(Hash) && !rr.empty?
|
29
|
-
rr.each_pair do |k,v|
|
30
|
-
if results[k].nil?
|
31
|
-
results[k] = v
|
32
|
-
elsif results[k].is_a?(Array)
|
33
|
-
results[k] << v
|
34
|
-
else
|
35
|
-
results[k] = [ results[k], v ]
|
36
|
-
end
|
17
|
+
res = source.attempt{ |s| sequence.parse(s) }
|
18
|
+
raise Fabulator::Grammar::RejectParse if res.nil?
|
19
|
+
res.children.each do |c|
|
20
|
+
if c.name.nil?
|
21
|
+
c.name = sequence.name if c.name.nil? && !sequence.name.nil?
|
22
|
+
res.prune(c)
|
23
|
+
results << c if !c.children.empty? || !c.value.nil?
|
37
24
|
end
|
38
25
|
end
|
26
|
+
res.name = sequence.name unless sequence.name.nil?
|
27
|
+
results << res if !res.children.empty? || !res.value.nil?
|
28
|
+
end
|
29
|
+
# now we want to merge all of the results
|
30
|
+
if results.size > 1
|
31
|
+
results = results.select{ |r| !r.name.nil? }
|
32
|
+
end
|
33
|
+
if !results.empty?
|
34
|
+
source.set_result(results)
|
39
35
|
end
|
40
|
-
results
|
41
36
|
end
|
42
37
|
end
|
43
38
|
end
|
@@ -16,9 +16,13 @@ module Fabulator::Grammar::Expr
|
|
16
16
|
|
17
17
|
def parse(cursor)
|
18
18
|
rule = cursor.find_rule(@name)
|
19
|
-
|
19
|
+
raise Fabulator::Grammar::RejectParse if rule.nil?
|
20
20
|
# we have @name as the path prefix for this part?
|
21
|
-
cursor.attempt { |c| rule.parse(c) }
|
21
|
+
ret = cursor.attempt { |c| rule.parse(c) }
|
22
|
+
|
23
|
+
raise Fabulator::Grammar::RejectParse if ret.nil?
|
24
|
+
|
25
|
+
cursor.set_result(ret)
|
22
26
|
end
|
23
27
|
end
|
24
28
|
end
|
@@ -2,55 +2,64 @@ module Fabulator
|
|
2
2
|
module Grammar
|
3
3
|
module Expr
|
4
4
|
class RuleSequence
|
5
|
+
|
6
|
+
## TODO: allow lookahead here so we can bound the greediness
|
7
|
+
## we also need to do non-greedy versions
|
8
|
+
## we don't have reasonable backtracking yet
|
9
|
+
|
5
10
|
def initialize(hypo, atom, quant = nil)
|
6
11
|
@name = hypo
|
7
12
|
@atom = atom
|
8
13
|
@quantifier = quant
|
9
14
|
end
|
10
15
|
|
11
|
-
#
|
16
|
+
# this is the "hypothetical" name in the grammar
|
12
17
|
def name
|
13
18
|
@name.nil? ? (@atom.name rescue nil) : @name
|
14
19
|
end
|
15
20
|
|
16
21
|
def parse(source)
|
17
22
|
if @quantifier.nil?
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
r
|
23
|
+
@atom.parse(source)
|
24
|
+
else
|
25
|
+
case @quantifier.first
|
26
|
+
when '?'.to_sym:
|
27
|
+
r = source.attempt{ |c| @atom.parse(source) }
|
28
|
+
source.set_result(r)
|
29
|
+
when :s:
|
30
|
+
ret = [ ]
|
31
|
+
r = source.attempt { |c| @atom.parse(c) }
|
32
|
+
while !r.nil?
|
33
|
+
ret << r
|
34
|
+
if @quantifier[1].nil?
|
35
|
+
r = source.attempt { |c| @atom.parse(c) }
|
36
|
+
else
|
37
|
+
r = source.attempt{ |c| @quantifier[1].parse(c) }
|
38
|
+
if !r.nil?
|
39
|
+
r = source.attempt{ |c| @atom.parse(c) }
|
40
|
+
end
|
34
41
|
end
|
35
42
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
raise Fabulator::Grammar::RejectParse if ret.empty?
|
44
|
+
source.set_result(ret)
|
45
|
+
when 's?'.to_sym:
|
46
|
+
ret = [ ]
|
47
|
+
r = source.attempt{ |c| @atom.parse(c) }
|
48
|
+
while !r.nil?
|
49
|
+
ret << r
|
50
|
+
if @quantifier[1].nil?
|
51
|
+
r = source.attempt{ |c| @atom.parse(c) }
|
52
|
+
else
|
53
|
+
r = source.attempt{ |c| @quantifier[1].parse(c) }
|
54
|
+
if !r.nil?
|
55
|
+
r = source.attempt{ |c| @atom.parse(c) }
|
56
|
+
end
|
49
57
|
end
|
50
58
|
end
|
51
|
-
|
52
|
-
|
59
|
+
source.set_result(ret) unless ret.empty?
|
60
|
+
end
|
53
61
|
end
|
62
|
+
source.name_result(@atom.name)
|
54
63
|
end
|
55
64
|
end
|
56
65
|
end
|
data/lib/fabulator/grammar.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fabulator-grammar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- James Smith
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-09-
|
18
|
+
date: 2010-09-11 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -26,12 +26,12 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 15
|
30
30
|
segments:
|
31
31
|
- 0
|
32
32
|
- 0
|
33
|
-
-
|
34
|
-
version: 0.0.
|
33
|
+
- 8
|
34
|
+
version: 0.0.8
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
description: The grammar Fabulator extension provides regular expression support.
|
@@ -48,6 +48,7 @@ files:
|
|
48
48
|
- Rakefile
|
49
49
|
- VERSION
|
50
50
|
- features/grammar.feature
|
51
|
+
- features/library.feature
|
51
52
|
- features/step_definitions/expression_steps.rb
|
52
53
|
- features/step_definitions/grammar_steps.rb
|
53
54
|
- features/step_definitions/template_steps.rb
|
@@ -58,6 +59,7 @@ files:
|
|
58
59
|
- lib/fabulator/grammar/actions.rb
|
59
60
|
- lib/fabulator/grammar/actions/context.rb
|
60
61
|
- lib/fabulator/grammar/actions/grammar.rb
|
62
|
+
- lib/fabulator/grammar/actions/result.rb
|
61
63
|
- lib/fabulator/grammar/actions/rule.rb
|
62
64
|
- lib/fabulator/grammar/actions/token.rb
|
63
65
|
- lib/fabulator/grammar/actions/when.rb
|