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