lunokhod 0.0.1.pre
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/.gitignore +3 -0
- data/.gitmodules +0 -0
- data/.travis.yml +5 -0
- data/CHANGELOG +4 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README +65 -0
- data/Rakefile +8 -0
- data/TODO +6 -0
- data/bin/lunokhod-driver +31 -0
- data/kitchen_sink_survey.rb +282 -0
- data/lib/lunokhod.rb +3 -0
- data/lib/lunokhod/ast.rb +263 -0
- data/lib/lunokhod/backends/debug.rb +137 -0
- data/lib/lunokhod/backends/webpage.rb +191 -0
- data/lib/lunokhod/backends/webpage/evaluator.js +24 -0
- data/lib/lunokhod/backends/webpage/jquery.min.js +5 -0
- data/lib/lunokhod/backends/webpage/page.html.erb +18 -0
- data/lib/lunokhod/compiler.rb +65 -0
- data/lib/lunokhod/condition_parsing.rb +188 -0
- data/lib/lunokhod/error_report.rb +44 -0
- data/lib/lunokhod/parser.rb +208 -0
- data/lib/lunokhod/rule_parser.kpeg +19 -0
- data/lib/lunokhod/rule_parser.rb +638 -0
- data/lib/lunokhod/rule_parsing.rb +41 -0
- data/lib/lunokhod/verifier.rb +155 -0
- data/lib/lunokhod/version.rb +3 -0
- data/lib/lunokhod/visitation.rb +12 -0
- data/lunokhod.gemspec +28 -0
- data/spec/lunokhod/error_report_spec.rb +62 -0
- data/spec/lunokhod/parser_spec.rb +87 -0
- data/spec/spec_helper.rb +6 -0
- metadata +181 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'lunokhod/verifier'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Lunokhod
|
5
|
+
class ErrorReport
|
6
|
+
attr_reader :errors
|
7
|
+
attr_reader :surveys
|
8
|
+
|
9
|
+
def initialize(surveys)
|
10
|
+
@errors = []
|
11
|
+
@surveys = surveys
|
12
|
+
end
|
13
|
+
|
14
|
+
def errors?
|
15
|
+
!errors.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
surveys.each do |s|
|
20
|
+
v = Verifier.new(s)
|
21
|
+
v.run { |error| errors << error }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
io = StringIO.new("")
|
27
|
+
|
28
|
+
errors.each do |e|
|
29
|
+
source = e.survey.source
|
30
|
+
|
31
|
+
if e.bad_tag?
|
32
|
+
io.puts "#{source}:#{e.at_fault.line}: #{e.node_type} #{e.key} is not defined"
|
33
|
+
elsif e.duplicate_question?
|
34
|
+
io.puts "#{source}: question tag #{e.key} is used multiple times"
|
35
|
+
e.at_fault.each do |n|
|
36
|
+
io.puts " at #{file}:#{n.line}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
io.string
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'lunokhod/ast'
|
2
|
+
require 'case'
|
3
|
+
|
4
|
+
module Lunokhod
|
5
|
+
class Parser < BasicObject
|
6
|
+
attr_reader :data
|
7
|
+
attr_reader :source
|
8
|
+
attr_reader :surveys
|
9
|
+
|
10
|
+
def initialize(data, source = '(input)')
|
11
|
+
@data = data
|
12
|
+
@source = source
|
13
|
+
@surveys = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
instance_eval(data, source)
|
18
|
+
end
|
19
|
+
|
20
|
+
def survey(name, options = {}, &block)
|
21
|
+
survey = Ast::Survey.new(sline, name, options)
|
22
|
+
survey.source = source
|
23
|
+
@surveys << survey
|
24
|
+
|
25
|
+
_with_unwind do
|
26
|
+
@current_node = survey
|
27
|
+
instance_eval(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def translations(spec)
|
32
|
+
spec.each do |lang, path|
|
33
|
+
translation = Ast::Translation.new(sline, lang, path)
|
34
|
+
translation.parent = @current_node
|
35
|
+
@current_node.translations << translation
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def dependency(options = {})
|
40
|
+
rule = options[:rule]
|
41
|
+
dependency = Ast::Dependency.new(sline, rule)
|
42
|
+
|
43
|
+
# Does this apply to a question? If not, we'll apply it to the current
|
44
|
+
# node.
|
45
|
+
if @current_question
|
46
|
+
dependency.parent = @current_question
|
47
|
+
@current_question.dependencies << dependency
|
48
|
+
else
|
49
|
+
dependency.parent = @current_node
|
50
|
+
@current_node.dependencies << dependency
|
51
|
+
end
|
52
|
+
|
53
|
+
dependency.parse_rule
|
54
|
+
@current_dependency = dependency
|
55
|
+
end
|
56
|
+
|
57
|
+
def validation(options = {})
|
58
|
+
rule = options[:rule]
|
59
|
+
validation = Ast::Validation.new(sline, rule)
|
60
|
+
validation.parent = @current_dependency
|
61
|
+
validation.parse_rule
|
62
|
+
@current_answer.validations << validation
|
63
|
+
@current_dependency = validation
|
64
|
+
end
|
65
|
+
|
66
|
+
def _grid(tag, text, &block)
|
67
|
+
grid = Ast::Grid.new(sline, tag.to_s, text)
|
68
|
+
grid.parent = @current_node
|
69
|
+
@current_node.questions << grid
|
70
|
+
|
71
|
+
_with_unwind do
|
72
|
+
@current_question = grid
|
73
|
+
@current_node = grid
|
74
|
+
instance_eval(&block)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def _repeater(tag, text, &block)
|
79
|
+
repeater = Ast::Repeater.new(sline, tag.to_s, text)
|
80
|
+
repeater.parent = @current_node
|
81
|
+
@current_node.questions << repeater
|
82
|
+
|
83
|
+
_with_unwind do
|
84
|
+
@current_question = nil
|
85
|
+
@current_node = repeater
|
86
|
+
instance_eval(&block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def _label(tag, text, options = {})
|
91
|
+
question = Ast::Label.new(sline, text, tag.to_s, options)
|
92
|
+
question.parent = @current_node
|
93
|
+
@current_node.questions << question
|
94
|
+
@current_question = question
|
95
|
+
end
|
96
|
+
|
97
|
+
def _question(tag, text, options = {})
|
98
|
+
question = Ast::Question.new(sline, text, tag.to_s, options)
|
99
|
+
question.parent = @current_node
|
100
|
+
@current_node.questions << question
|
101
|
+
@current_question = question
|
102
|
+
end
|
103
|
+
|
104
|
+
def _answer(tag, t1, t2 = nil, options = {})
|
105
|
+
text, type, other, options = _disambiguate_answer(t1, t2, options)
|
106
|
+
answer = Ast::Answer.new(sline, text, type, other, tag.to_s, [], options)
|
107
|
+
|
108
|
+
answer.parent = @current_question
|
109
|
+
@current_question.answers << answer
|
110
|
+
@current_answer = answer
|
111
|
+
end
|
112
|
+
|
113
|
+
def _disambiguate_answer(t1, t2, options)
|
114
|
+
c = ::Case
|
115
|
+
|
116
|
+
case c[t1, t2]
|
117
|
+
when c[::String, ::NilClass]
|
118
|
+
[t1, nil, false, options] # text, type, other, options
|
119
|
+
when c[::String, ::Hash]
|
120
|
+
[t1, nil, false, t2]
|
121
|
+
when c[::String, ::Symbol]
|
122
|
+
[t1, t2, false, options]
|
123
|
+
when c[::Symbol, ::Hash]
|
124
|
+
[nil, t1, false, t2]
|
125
|
+
when c[::Symbol, ::NilClass]
|
126
|
+
[nil, t1, false, options]
|
127
|
+
when c[:other, ::Symbol]
|
128
|
+
[nil, t2, true, options]
|
129
|
+
else ::Kernel.raise "Unknown case #{[t1, t2].inspect}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def _condition(label, *predicate)
|
134
|
+
condition = Ast::Condition.new(sline, label, predicate)
|
135
|
+
condition.parent = @current_dependency
|
136
|
+
condition.parse_condition
|
137
|
+
@current_dependency.conditions << condition
|
138
|
+
end
|
139
|
+
|
140
|
+
def _group(tag, name = nil, options = {}, &block)
|
141
|
+
group = Ast::Group.new(sline, tag.to_s, name, options)
|
142
|
+
group.parent = @current_node
|
143
|
+
@current_node.questions << group
|
144
|
+
|
145
|
+
_with_unwind do
|
146
|
+
@current_question = nil
|
147
|
+
@current_node = group
|
148
|
+
instance_eval(&block)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def _section(tag, name, options = {}, &block)
|
153
|
+
section = Ast::Section.new(sline, tag.to_s, name, options)
|
154
|
+
section.parent = @current_node
|
155
|
+
@current_node.sections << section
|
156
|
+
|
157
|
+
_with_unwind do
|
158
|
+
@current_node = section
|
159
|
+
instance_eval(&block)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def _with_unwind
|
164
|
+
old_dependency = @current_dependency
|
165
|
+
old_node = @current_node
|
166
|
+
old_question = @current_question
|
167
|
+
old_answer = @current_answer
|
168
|
+
|
169
|
+
yield
|
170
|
+
|
171
|
+
@current_dependency = old_dependency
|
172
|
+
@current_node = old_node
|
173
|
+
@current_question = old_question
|
174
|
+
@current_answer = old_answer
|
175
|
+
end
|
176
|
+
|
177
|
+
# Current line in the survey.
|
178
|
+
def sline
|
179
|
+
::Kernel.caller(1).detect { |l| l.include?(source) }.split(':')[1]
|
180
|
+
end
|
181
|
+
|
182
|
+
# Intercept DSL keywords that may be suffixed with tags.
|
183
|
+
def method_missing(m, *args, &block)
|
184
|
+
case m
|
185
|
+
when /^q(?:uestion)?(?:_(.+))?$/
|
186
|
+
_question(*args.unshift($1), &block)
|
187
|
+
when /^a(?:nswer)?(?:_(.+))?$/
|
188
|
+
_answer(*args.unshift($1), &block)
|
189
|
+
when /^l(?:abel)?(?:_(.+))?$/
|
190
|
+
_label(*args.unshift($1), &block)
|
191
|
+
when /^g(?:roup)?(?:_(.+))?$/
|
192
|
+
_group(*args.unshift($1), &block)
|
193
|
+
when /^s(?:ection)?(?:_(.+))?$/
|
194
|
+
_section(*args.unshift($1), &block)
|
195
|
+
when /^grid(?:_(.+))?$/
|
196
|
+
_grid(*args.unshift($1), &block)
|
197
|
+
when /^repeater(?:_(.+))?$/
|
198
|
+
_repeater(*args.unshift($1), &block)
|
199
|
+
when /^dependency_.+$/
|
200
|
+
dependency(*args)
|
201
|
+
when /^condition(?:_(.+))$/
|
202
|
+
_condition(*args.unshift($1), &block)
|
203
|
+
else
|
204
|
+
super
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
%% name = RuleParser
|
2
|
+
%% ctag_node = ast Tag(name)
|
3
|
+
%% conj_node = ast Conj(op)
|
4
|
+
%% phrase_node = ast Phrase(left, conj, right)
|
5
|
+
|
6
|
+
%%{
|
7
|
+
attr_reader :ast
|
8
|
+
}
|
9
|
+
|
10
|
+
lp = "("
|
11
|
+
rp = ")"
|
12
|
+
space = /\s/
|
13
|
+
term = < /\w+/ > ~ctag_node(text)
|
14
|
+
and = "and" ~conj_node("and")
|
15
|
+
or = "or" ~conj_node("or")
|
16
|
+
conj = and | or
|
17
|
+
phrase = (phrase:l space+ conj:c space+ phrase:r) ~phrase_node(l, c, r) | lp phrase rp | term
|
18
|
+
|
19
|
+
root = phrase:t { @ast = t }
|
@@ -0,0 +1,638 @@
|
|
1
|
+
class RuleParser
|
2
|
+
# :stopdoc:
|
3
|
+
|
4
|
+
# This is distinct from setup_parser so that a standalone parser
|
5
|
+
# can redefine #initialize and still have access to the proper
|
6
|
+
# parser setup code.
|
7
|
+
def initialize(str, debug=false)
|
8
|
+
setup_parser(str, debug)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
# Prepares for parsing +str+. If you define a custom initialize you must
|
14
|
+
# call this method before #parse
|
15
|
+
def setup_parser(str, debug=false)
|
16
|
+
@string = str
|
17
|
+
@pos = 0
|
18
|
+
@memoizations = Hash.new { |h,k| h[k] = {} }
|
19
|
+
@result = nil
|
20
|
+
@failed_rule = nil
|
21
|
+
@failing_rule_offset = -1
|
22
|
+
|
23
|
+
setup_foreign_grammar
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :string
|
27
|
+
attr_reader :failing_rule_offset
|
28
|
+
attr_accessor :result, :pos
|
29
|
+
|
30
|
+
|
31
|
+
def current_column(target=pos)
|
32
|
+
if c = string.rindex("\n", target-1)
|
33
|
+
return target - c - 1
|
34
|
+
end
|
35
|
+
|
36
|
+
target + 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def current_line(target=pos)
|
40
|
+
cur_offset = 0
|
41
|
+
cur_line = 0
|
42
|
+
|
43
|
+
string.each_line do |line|
|
44
|
+
cur_line += 1
|
45
|
+
cur_offset += line.size
|
46
|
+
return cur_line if cur_offset >= target
|
47
|
+
end
|
48
|
+
|
49
|
+
-1
|
50
|
+
end
|
51
|
+
|
52
|
+
def lines
|
53
|
+
lines = []
|
54
|
+
string.each_line { |l| lines << l }
|
55
|
+
lines
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
def get_text(start)
|
61
|
+
@string[start..@pos-1]
|
62
|
+
end
|
63
|
+
|
64
|
+
def show_pos
|
65
|
+
width = 10
|
66
|
+
if @pos < width
|
67
|
+
"#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")"
|
68
|
+
else
|
69
|
+
"#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def failure_info
|
74
|
+
l = current_line @failing_rule_offset
|
75
|
+
c = current_column @failing_rule_offset
|
76
|
+
|
77
|
+
if @failed_rule.kind_of? Symbol
|
78
|
+
info = self.class::Rules[@failed_rule]
|
79
|
+
"line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'"
|
80
|
+
else
|
81
|
+
"line #{l}, column #{c}: failed rule '#{@failed_rule}'"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def failure_caret
|
86
|
+
l = current_line @failing_rule_offset
|
87
|
+
c = current_column @failing_rule_offset
|
88
|
+
|
89
|
+
line = lines[l-1]
|
90
|
+
"#{line}\n#{' ' * (c - 1)}^"
|
91
|
+
end
|
92
|
+
|
93
|
+
def failure_character
|
94
|
+
l = current_line @failing_rule_offset
|
95
|
+
c = current_column @failing_rule_offset
|
96
|
+
lines[l-1][c-1, 1]
|
97
|
+
end
|
98
|
+
|
99
|
+
def failure_oneline
|
100
|
+
l = current_line @failing_rule_offset
|
101
|
+
c = current_column @failing_rule_offset
|
102
|
+
|
103
|
+
char = lines[l-1][c-1, 1]
|
104
|
+
|
105
|
+
if @failed_rule.kind_of? Symbol
|
106
|
+
info = self.class::Rules[@failed_rule]
|
107
|
+
"@#{l}:#{c} failed rule '#{info.name}', got '#{char}'"
|
108
|
+
else
|
109
|
+
"@#{l}:#{c} failed rule '#{@failed_rule}', got '#{char}'"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class ParseError < RuntimeError
|
114
|
+
end
|
115
|
+
|
116
|
+
def raise_error
|
117
|
+
raise ParseError, failure_oneline
|
118
|
+
end
|
119
|
+
|
120
|
+
def show_error(io=STDOUT)
|
121
|
+
error_pos = @failing_rule_offset
|
122
|
+
line_no = current_line(error_pos)
|
123
|
+
col_no = current_column(error_pos)
|
124
|
+
|
125
|
+
io.puts "On line #{line_no}, column #{col_no}:"
|
126
|
+
|
127
|
+
if @failed_rule.kind_of? Symbol
|
128
|
+
info = self.class::Rules[@failed_rule]
|
129
|
+
io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')"
|
130
|
+
else
|
131
|
+
io.puts "Failed to match rule '#{@failed_rule}'"
|
132
|
+
end
|
133
|
+
|
134
|
+
io.puts "Got: #{string[error_pos,1].inspect}"
|
135
|
+
line = lines[line_no-1]
|
136
|
+
io.puts "=> #{line}"
|
137
|
+
io.print(" " * (col_no + 3))
|
138
|
+
io.puts "^"
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_failed_rule(name)
|
142
|
+
if @pos > @failing_rule_offset
|
143
|
+
@failed_rule = name
|
144
|
+
@failing_rule_offset = @pos
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
attr_reader :failed_rule
|
149
|
+
|
150
|
+
def match_string(str)
|
151
|
+
len = str.size
|
152
|
+
if @string[pos,len] == str
|
153
|
+
@pos += len
|
154
|
+
return str
|
155
|
+
end
|
156
|
+
|
157
|
+
return nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def scan(reg)
|
161
|
+
if m = reg.match(@string[@pos..-1])
|
162
|
+
width = m.end(0)
|
163
|
+
@pos += width
|
164
|
+
return true
|
165
|
+
end
|
166
|
+
|
167
|
+
return nil
|
168
|
+
end
|
169
|
+
|
170
|
+
if "".respond_to? :getbyte
|
171
|
+
def get_byte
|
172
|
+
if @pos >= @string.size
|
173
|
+
return nil
|
174
|
+
end
|
175
|
+
|
176
|
+
s = @string.getbyte @pos
|
177
|
+
@pos += 1
|
178
|
+
s
|
179
|
+
end
|
180
|
+
else
|
181
|
+
def get_byte
|
182
|
+
if @pos >= @string.size
|
183
|
+
return nil
|
184
|
+
end
|
185
|
+
|
186
|
+
s = @string[@pos]
|
187
|
+
@pos += 1
|
188
|
+
s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def parse(rule=nil)
|
193
|
+
# We invoke the rules indirectly via apply
|
194
|
+
# instead of by just calling them as methods because
|
195
|
+
# if the rules use left recursion, apply needs to
|
196
|
+
# manage that.
|
197
|
+
|
198
|
+
if !rule
|
199
|
+
apply(:_root)
|
200
|
+
else
|
201
|
+
method = rule.gsub("-","_hyphen_")
|
202
|
+
apply :"_#{method}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class MemoEntry
|
207
|
+
def initialize(ans, pos)
|
208
|
+
@ans = ans
|
209
|
+
@pos = pos
|
210
|
+
@result = nil
|
211
|
+
@set = false
|
212
|
+
@left_rec = false
|
213
|
+
end
|
214
|
+
|
215
|
+
attr_reader :ans, :pos, :result, :set
|
216
|
+
attr_accessor :left_rec
|
217
|
+
|
218
|
+
def move!(ans, pos, result)
|
219
|
+
@ans = ans
|
220
|
+
@pos = pos
|
221
|
+
@result = result
|
222
|
+
@set = true
|
223
|
+
@left_rec = false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def external_invoke(other, rule, *args)
|
228
|
+
old_pos = @pos
|
229
|
+
old_string = @string
|
230
|
+
|
231
|
+
@pos = other.pos
|
232
|
+
@string = other.string
|
233
|
+
|
234
|
+
begin
|
235
|
+
if val = __send__(rule, *args)
|
236
|
+
other.pos = @pos
|
237
|
+
other.result = @result
|
238
|
+
else
|
239
|
+
other.set_failed_rule "#{self.class}##{rule}"
|
240
|
+
end
|
241
|
+
val
|
242
|
+
ensure
|
243
|
+
@pos = old_pos
|
244
|
+
@string = old_string
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def apply_with_args(rule, *args)
|
249
|
+
memo_key = [rule, args]
|
250
|
+
if m = @memoizations[memo_key][@pos]
|
251
|
+
@pos = m.pos
|
252
|
+
if !m.set
|
253
|
+
m.left_rec = true
|
254
|
+
return nil
|
255
|
+
end
|
256
|
+
|
257
|
+
@result = m.result
|
258
|
+
|
259
|
+
return m.ans
|
260
|
+
else
|
261
|
+
m = MemoEntry.new(nil, @pos)
|
262
|
+
@memoizations[memo_key][@pos] = m
|
263
|
+
start_pos = @pos
|
264
|
+
|
265
|
+
ans = __send__ rule, *args
|
266
|
+
|
267
|
+
lr = m.left_rec
|
268
|
+
|
269
|
+
m.move! ans, @pos, @result
|
270
|
+
|
271
|
+
# Don't bother trying to grow the left recursion
|
272
|
+
# if it's failing straight away (thus there is no seed)
|
273
|
+
if ans and lr
|
274
|
+
return grow_lr(rule, args, start_pos, m)
|
275
|
+
else
|
276
|
+
return ans
|
277
|
+
end
|
278
|
+
|
279
|
+
return ans
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def apply(rule)
|
284
|
+
if m = @memoizations[rule][@pos]
|
285
|
+
@pos = m.pos
|
286
|
+
if !m.set
|
287
|
+
m.left_rec = true
|
288
|
+
return nil
|
289
|
+
end
|
290
|
+
|
291
|
+
@result = m.result
|
292
|
+
|
293
|
+
return m.ans
|
294
|
+
else
|
295
|
+
m = MemoEntry.new(nil, @pos)
|
296
|
+
@memoizations[rule][@pos] = m
|
297
|
+
start_pos = @pos
|
298
|
+
|
299
|
+
ans = __send__ rule
|
300
|
+
|
301
|
+
lr = m.left_rec
|
302
|
+
|
303
|
+
m.move! ans, @pos, @result
|
304
|
+
|
305
|
+
# Don't bother trying to grow the left recursion
|
306
|
+
# if it's failing straight away (thus there is no seed)
|
307
|
+
if ans and lr
|
308
|
+
return grow_lr(rule, nil, start_pos, m)
|
309
|
+
else
|
310
|
+
return ans
|
311
|
+
end
|
312
|
+
|
313
|
+
return ans
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def grow_lr(rule, args, start_pos, m)
|
318
|
+
while true
|
319
|
+
@pos = start_pos
|
320
|
+
@result = m.result
|
321
|
+
|
322
|
+
if args
|
323
|
+
ans = __send__ rule, *args
|
324
|
+
else
|
325
|
+
ans = __send__ rule
|
326
|
+
end
|
327
|
+
return nil unless ans
|
328
|
+
|
329
|
+
break if @pos <= m.pos
|
330
|
+
|
331
|
+
m.move! ans, @pos, @result
|
332
|
+
end
|
333
|
+
|
334
|
+
@result = m.result
|
335
|
+
@pos = m.pos
|
336
|
+
return m.ans
|
337
|
+
end
|
338
|
+
|
339
|
+
class RuleInfo
|
340
|
+
def initialize(name, rendered)
|
341
|
+
@name = name
|
342
|
+
@rendered = rendered
|
343
|
+
end
|
344
|
+
|
345
|
+
attr_reader :name, :rendered
|
346
|
+
end
|
347
|
+
|
348
|
+
def self.rule_info(name, rendered)
|
349
|
+
RuleInfo.new(name, rendered)
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
# :startdoc:
|
354
|
+
|
355
|
+
|
356
|
+
attr_reader :ast
|
357
|
+
|
358
|
+
|
359
|
+
# :stopdoc:
|
360
|
+
|
361
|
+
module AST
|
362
|
+
class Node; end
|
363
|
+
class Conj < Node
|
364
|
+
def initialize(op)
|
365
|
+
@op = op
|
366
|
+
end
|
367
|
+
attr_reader :op
|
368
|
+
end
|
369
|
+
class Tag < Node
|
370
|
+
def initialize(name)
|
371
|
+
@name = name
|
372
|
+
end
|
373
|
+
attr_reader :name
|
374
|
+
end
|
375
|
+
class Phrase < Node
|
376
|
+
def initialize(left, conj, right)
|
377
|
+
@left = left
|
378
|
+
@conj = conj
|
379
|
+
@right = right
|
380
|
+
end
|
381
|
+
attr_reader :left
|
382
|
+
attr_reader :conj
|
383
|
+
attr_reader :right
|
384
|
+
end
|
385
|
+
end
|
386
|
+
def conj_node(op)
|
387
|
+
AST::Conj.new(op)
|
388
|
+
end
|
389
|
+
def ctag_node(name)
|
390
|
+
AST::Tag.new(name)
|
391
|
+
end
|
392
|
+
def phrase_node(left, conj, right)
|
393
|
+
AST::Phrase.new(left, conj, right)
|
394
|
+
end
|
395
|
+
def setup_foreign_grammar; end
|
396
|
+
|
397
|
+
# lp = "("
|
398
|
+
def _lp
|
399
|
+
_tmp = match_string("(")
|
400
|
+
set_failed_rule :_lp unless _tmp
|
401
|
+
return _tmp
|
402
|
+
end
|
403
|
+
|
404
|
+
# rp = ")"
|
405
|
+
def _rp
|
406
|
+
_tmp = match_string(")")
|
407
|
+
set_failed_rule :_rp unless _tmp
|
408
|
+
return _tmp
|
409
|
+
end
|
410
|
+
|
411
|
+
# space = /\s/
|
412
|
+
def _space
|
413
|
+
_tmp = scan(/\A(?-mix:\s)/)
|
414
|
+
set_failed_rule :_space unless _tmp
|
415
|
+
return _tmp
|
416
|
+
end
|
417
|
+
|
418
|
+
# term = < /\w+/ > {ctag_node(text)}
|
419
|
+
def _term
|
420
|
+
|
421
|
+
_save = self.pos
|
422
|
+
while true # sequence
|
423
|
+
_text_start = self.pos
|
424
|
+
_tmp = scan(/\A(?-mix:\w+)/)
|
425
|
+
if _tmp
|
426
|
+
text = get_text(_text_start)
|
427
|
+
end
|
428
|
+
unless _tmp
|
429
|
+
self.pos = _save
|
430
|
+
break
|
431
|
+
end
|
432
|
+
@result = begin; ctag_node(text); end
|
433
|
+
_tmp = true
|
434
|
+
unless _tmp
|
435
|
+
self.pos = _save
|
436
|
+
end
|
437
|
+
break
|
438
|
+
end # end sequence
|
439
|
+
|
440
|
+
set_failed_rule :_term unless _tmp
|
441
|
+
return _tmp
|
442
|
+
end
|
443
|
+
|
444
|
+
# and = "and" {conj_node("and")}
|
445
|
+
def _and
|
446
|
+
|
447
|
+
_save = self.pos
|
448
|
+
while true # sequence
|
449
|
+
_tmp = match_string("and")
|
450
|
+
unless _tmp
|
451
|
+
self.pos = _save
|
452
|
+
break
|
453
|
+
end
|
454
|
+
@result = begin; conj_node("and"); end
|
455
|
+
_tmp = true
|
456
|
+
unless _tmp
|
457
|
+
self.pos = _save
|
458
|
+
end
|
459
|
+
break
|
460
|
+
end # end sequence
|
461
|
+
|
462
|
+
set_failed_rule :_and unless _tmp
|
463
|
+
return _tmp
|
464
|
+
end
|
465
|
+
|
466
|
+
# or = "or" {conj_node("or")}
|
467
|
+
def _or
|
468
|
+
|
469
|
+
_save = self.pos
|
470
|
+
while true # sequence
|
471
|
+
_tmp = match_string("or")
|
472
|
+
unless _tmp
|
473
|
+
self.pos = _save
|
474
|
+
break
|
475
|
+
end
|
476
|
+
@result = begin; conj_node("or"); end
|
477
|
+
_tmp = true
|
478
|
+
unless _tmp
|
479
|
+
self.pos = _save
|
480
|
+
end
|
481
|
+
break
|
482
|
+
end # end sequence
|
483
|
+
|
484
|
+
set_failed_rule :_or unless _tmp
|
485
|
+
return _tmp
|
486
|
+
end
|
487
|
+
|
488
|
+
# conj = (and | or)
|
489
|
+
def _conj
|
490
|
+
|
491
|
+
_save = self.pos
|
492
|
+
while true # choice
|
493
|
+
_tmp = apply(:_and)
|
494
|
+
break if _tmp
|
495
|
+
self.pos = _save
|
496
|
+
_tmp = apply(:_or)
|
497
|
+
break if _tmp
|
498
|
+
self.pos = _save
|
499
|
+
break
|
500
|
+
end # end choice
|
501
|
+
|
502
|
+
set_failed_rule :_conj unless _tmp
|
503
|
+
return _tmp
|
504
|
+
end
|
505
|
+
|
506
|
+
# phrase = (phrase:l space+ conj:c space+ phrase:r {phrase_node(l, c, r)} | lp phrase rp | term)
|
507
|
+
def _phrase
|
508
|
+
|
509
|
+
_save = self.pos
|
510
|
+
while true # choice
|
511
|
+
|
512
|
+
_save1 = self.pos
|
513
|
+
while true # sequence
|
514
|
+
_tmp = apply(:_phrase)
|
515
|
+
l = @result
|
516
|
+
unless _tmp
|
517
|
+
self.pos = _save1
|
518
|
+
break
|
519
|
+
end
|
520
|
+
_save2 = self.pos
|
521
|
+
_tmp = apply(:_space)
|
522
|
+
if _tmp
|
523
|
+
while true
|
524
|
+
_tmp = apply(:_space)
|
525
|
+
break unless _tmp
|
526
|
+
end
|
527
|
+
_tmp = true
|
528
|
+
else
|
529
|
+
self.pos = _save2
|
530
|
+
end
|
531
|
+
unless _tmp
|
532
|
+
self.pos = _save1
|
533
|
+
break
|
534
|
+
end
|
535
|
+
_tmp = apply(:_conj)
|
536
|
+
c = @result
|
537
|
+
unless _tmp
|
538
|
+
self.pos = _save1
|
539
|
+
break
|
540
|
+
end
|
541
|
+
_save3 = self.pos
|
542
|
+
_tmp = apply(:_space)
|
543
|
+
if _tmp
|
544
|
+
while true
|
545
|
+
_tmp = apply(:_space)
|
546
|
+
break unless _tmp
|
547
|
+
end
|
548
|
+
_tmp = true
|
549
|
+
else
|
550
|
+
self.pos = _save3
|
551
|
+
end
|
552
|
+
unless _tmp
|
553
|
+
self.pos = _save1
|
554
|
+
break
|
555
|
+
end
|
556
|
+
_tmp = apply(:_phrase)
|
557
|
+
r = @result
|
558
|
+
unless _tmp
|
559
|
+
self.pos = _save1
|
560
|
+
break
|
561
|
+
end
|
562
|
+
@result = begin; phrase_node(l, c, r); end
|
563
|
+
_tmp = true
|
564
|
+
unless _tmp
|
565
|
+
self.pos = _save1
|
566
|
+
end
|
567
|
+
break
|
568
|
+
end # end sequence
|
569
|
+
|
570
|
+
break if _tmp
|
571
|
+
self.pos = _save
|
572
|
+
|
573
|
+
_save4 = self.pos
|
574
|
+
while true # sequence
|
575
|
+
_tmp = apply(:_lp)
|
576
|
+
unless _tmp
|
577
|
+
self.pos = _save4
|
578
|
+
break
|
579
|
+
end
|
580
|
+
_tmp = apply(:_phrase)
|
581
|
+
unless _tmp
|
582
|
+
self.pos = _save4
|
583
|
+
break
|
584
|
+
end
|
585
|
+
_tmp = apply(:_rp)
|
586
|
+
unless _tmp
|
587
|
+
self.pos = _save4
|
588
|
+
end
|
589
|
+
break
|
590
|
+
end # end sequence
|
591
|
+
|
592
|
+
break if _tmp
|
593
|
+
self.pos = _save
|
594
|
+
_tmp = apply(:_term)
|
595
|
+
break if _tmp
|
596
|
+
self.pos = _save
|
597
|
+
break
|
598
|
+
end # end choice
|
599
|
+
|
600
|
+
set_failed_rule :_phrase unless _tmp
|
601
|
+
return _tmp
|
602
|
+
end
|
603
|
+
|
604
|
+
# root = phrase:t { @ast = t }
|
605
|
+
def _root
|
606
|
+
|
607
|
+
_save = self.pos
|
608
|
+
while true # sequence
|
609
|
+
_tmp = apply(:_phrase)
|
610
|
+
t = @result
|
611
|
+
unless _tmp
|
612
|
+
self.pos = _save
|
613
|
+
break
|
614
|
+
end
|
615
|
+
@result = begin; @ast = t ; end
|
616
|
+
_tmp = true
|
617
|
+
unless _tmp
|
618
|
+
self.pos = _save
|
619
|
+
end
|
620
|
+
break
|
621
|
+
end # end sequence
|
622
|
+
|
623
|
+
set_failed_rule :_root unless _tmp
|
624
|
+
return _tmp
|
625
|
+
end
|
626
|
+
|
627
|
+
Rules = {}
|
628
|
+
Rules[:_lp] = rule_info("lp", "\"(\"")
|
629
|
+
Rules[:_rp] = rule_info("rp", "\")\"")
|
630
|
+
Rules[:_space] = rule_info("space", "/\\s/")
|
631
|
+
Rules[:_term] = rule_info("term", "< /\\w+/ > {ctag_node(text)}")
|
632
|
+
Rules[:_and] = rule_info("and", "\"and\" {conj_node(\"and\")}")
|
633
|
+
Rules[:_or] = rule_info("or", "\"or\" {conj_node(\"or\")}")
|
634
|
+
Rules[:_conj] = rule_info("conj", "(and | or)")
|
635
|
+
Rules[:_phrase] = rule_info("phrase", "(phrase:l space+ conj:c space+ phrase:r {phrase_node(l, c, r)} | lp phrase rp | term)")
|
636
|
+
Rules[:_root] = rule_info("root", "phrase:t { @ast = t }")
|
637
|
+
# :startdoc:
|
638
|
+
end
|