Class: Ibex::Frontend::GeneratedParserBase
Overview
Semantic support and token delivery for the generated grammar parser.
rubocop:disable Metrics/ClassLength -- generated grammar semantics share one parser support surface.
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#append_user_code(blocks, token) ⇒ AST::user_code
-
#apply_suffixes(item, suffixes) ⇒ AST::item
-
#build_action(token) ⇒ AST::InlineAction
-
#build_alternative(items, precedence, node_annotation) ⇒ AST::Alternative
-
#build_conversion(name_token, literal_token) ⇒ AST::Conversion
-
#build_convert(keyword, pairs) ⇒ AST::Convert
-
#build_empty(token) ⇒ AST::Empty
-
#build_expect(keyword, integer) ⇒ AST::Expect
-
#build_expect_rr(keyword, integer) ⇒ AST::ExpectRR
-
#build_grammar_test(keyword, expectation, source) ⇒ AST::GrammarTest
-
#build_group(opening, alternatives, suffixes) ⇒ AST::item
-
#build_lexer(keyword, entries) ⇒ AST::Lexer
-
#build_lexer_rule(kind, name, pattern, action, marker) ⇒ AST::LexerRule
-
#build_lexer_state(marker, name, entries) ⇒ AST::LexerState
-
#build_node_annotation(marker, name, fields) ⇒ AST::NodeAnnotation
-
#build_on_error_reduce(keyword, names) ⇒ AST::OnErrorReduce
-
#build_options(keyword, names) ⇒ AST::Options
-
#build_parameter(keyword, name, type) ⇒ AST::Parameter
-
#build_precedence(keyword, direction, levels) ⇒ AST::Precedence
-
#build_precedence_level(association, symbols) ⇒ AST::PrecedenceLevel
-
#build_printer(keyword, symbol, action) ⇒ AST::Printer
-
#build_recovery(keyword, kind, sync_tokens) ⇒ AST::Recovery
-
#build_root(class_token, class_parts, superclass, declarations, rules, user_code) ⇒ AST::Root
-
#build_separated_list(function, item, separator) ⇒ AST::SeparatedList
-
#build_start(keyword, names) ⇒ AST::Start
-
#build_symbol_reference(token, named_reference, suffixes) ⇒ AST::item
-
#build_token_alias(name, value) ⇒ [ String, String ]
-
#build_token_entry(token) ⇒ [ String, nil ]
-
#build_tokens(keyword, entries) ⇒ AST::Tokens
-
#empty_user_code ⇒ AST::user_code
-
#expected_description(token) ⇒ String
-
#extended_only!(location, feature) ⇒ void
-
#fail_at(location, message) ⇒ bot
-
#initialize(tokens, mode: :default) ⇒ GeneratedParserBase
constructor
A new instance of GeneratedParserBase.
-
#item_start_location(item) ⇒ Location?
-
#named_reference_value(named_reference) ⇒ String?
-
#next_token ⇒ [ external_token, Token ], false
-
#on_error(_token_id, value, _value_stack) ⇒ bot
-
#parse ⇒ AST::Root, AST::Fragment
-
#prepend_user_code(token, blocks) ⇒ AST::user_code
-
#raise_contextual_error(token) ⇒ void
-
#raise_conversion_contextual_error ⇒ void
-
#raise_group_contextual_error(token) ⇒ void
-
#structural_expectation(token) ⇒ String
-
#token_integer(token) ⇒ Integer
-
#token_string(token) ⇒ String
-
#token_user_code(token) ⇒ user_code_token
#build_parameterized_reference, #build_rule, #build_rule_parameters, #require_adjacency!
#build_fragment, #build_include, #fragment_root_declaration_name, #reject_fragment_root_declarations
#build_display_name, #build_semantic_type, #build_symbol_metadata, #decode_metadata_value
Constructor Details
Returns a new instance of GeneratedParserBase.
22
23
24
25
26
27
28
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 22
def initialize(tokens, mode: :default)
raise ArgumentError, "mode must be :default or :extended" unless %i[default extended].include?(mode)
super()
@adapter = TokenAdapter.new(tokens, extended: mode == :extended)
@mode = mode
end
|
Instance Attribute Details
#diagnostic_token ⇒ Token?
16
17
18
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 16
def diagnostic_token
@diagnostic_token
end
|
Instance Method Details
#append_user_code(blocks, token) ⇒ AST::user_code
351
352
353
354
355
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 351
def append_user_code(blocks, token)
value = token_user_code(token)
blocks[value[:name]] << AST::UserCode.new(name: value[:name], code: value[:code], loc: token.location)
blocks
end
|
#apply_suffixes(item, suffixes) ⇒ AST::item
338
339
340
341
342
343
344
345
346
347
348
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 338
def apply_suffixes(item, suffixes)
suffixes.reduce(item) do |wrapped, suffix|
extended_only!(suffix.location, "EBNF suffixes")
case token_string(suffix)
when "?" then AST::Optional.new(item: wrapped, loc: suffix.location)
when "*" then AST::Star.new(item: wrapped, loc: suffix.location)
when "+" then AST::Plus.new(item: wrapped, loc: suffix.location)
else raise Ibex::Error, "#{suffix.location}: unknown EBNF suffix #{suffix.value.inspect}"
end
end
end
|
320
321
322
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 320
def build_action(token)
AST::InlineAction.new(code: token_string(token), loc: token.location)
end
|
#build_alternative(items, precedence, node_annotation) ⇒ AST::Alternative
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 260
def build_alternative(items, precedence, node_annotation)
last_token = @adapter.last_token
location = item_start_location(items.first) || precedence&.location || last_token&.location
raise Ibex::Error, "missing alternative location" unless location
action = nil last_item = items.last
if last_item.is_a?(AST::InlineAction)
items.pop
action = last_item
end
AST::Alternative.new(
items: items, action: action, precedence: precedence && token_string(precedence),
node_annotation: node_annotation, loc: location
)
end
|
#build_conversion(name_token, literal_token) ⇒ AST::Conversion
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 243
def build_conversion(name_token, literal_token)
unless name_token.location.line == literal_token.location.line
fail_at(name_token.location, "expected a quoted Ruby conversion expression")
end
literal = token_string(literal_token)
expression = if literal.start_with?('"')
literal.undump
else
(literal[1...-1] || "").gsub("\\'", "'").gsub("\\\\", "\\")
end
AST::Conversion.new(name: token_string(name_token), expression: expression, loc: name_token.location)
rescue RuntimeError => e
fail_at(name_token.location, "invalid conversion expression: #{e.message}")
end
|
#build_convert(keyword, pairs) ⇒ AST::Convert
238
239
240
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 238
def build_convert(keyword, pairs)
AST::Convert.new(pairs: pairs, loc: keyword.location)
end
|
#build_empty(token) ⇒ AST::Empty
286
287
288
289
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 286
def build_empty(token)
extended_only!(token.location, "%empty")
AST::Empty.new(loc: token.location)
end
|
#build_expect(keyword, integer) ⇒ AST::Expect
154
155
156
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 154
def build_expect(keyword, integer)
AST::Expect.new(conflicts: token_integer(integer), loc: keyword.location)
end
|
#build_expect_rr(keyword, integer) ⇒ AST::ExpectRR
159
160
161
162
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 159
def build_expect_rr(keyword, integer)
extended_only!(keyword.location, "expect-rr declarations")
AST::ExpectRR.new(conflicts: token_integer(integer), loc: keyword.location)
end
|
#build_grammar_test(keyword, expectation, source) ⇒ AST::GrammarTest
204
205
206
207
208
209
210
211
212
213
214
215
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 204
def build_grammar_test(keyword, expectation, source)
extended_only!(keyword.location, "%test")
expected = token_string(expectation)
fail_at(expectation.location, "expected accept or reject, got #{expected}") unless
%w[accept reject].include?(expected)
literal = token_string(source)
fail_at(source.location, "%test source must use a double-quoted string") unless literal.start_with?('"')
AST::GrammarTest.new(expectation: expected.to_sym, source: literal.undump, loc: keyword.location)
rescue RuntimeError => e
fail_at(source.location, "invalid %test source: #{e.message}")
end
|
#build_group(opening, alternatives, suffixes) ⇒ AST::item
325
326
327
328
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 325
def build_group(opening, alternatives, suffixes)
extended_only!(opening.location, "EBNF groups")
apply_suffixes(AST::Group.new(alternatives: alternatives, loc: opening.location), suffixes)
end
|
#build_lexer(keyword, entries) ⇒ AST::Lexer
218
219
220
221
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 218
def build_lexer(keyword, entries)
extended_only!(keyword.location, "lexer declarations")
AST::Lexer.new(definitions: entries, loc: keyword.location)
end
|
#build_lexer_rule(kind, name, pattern, action, marker) ⇒ AST::LexerRule
224
225
226
227
228
229
230
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 224
def build_lexer_rule(kind, name, pattern, action, marker)
fail_at(marker.location, "on lexer rules require an action") if kind == :on && !action
AST::LexerRule.new(
kind: kind, token: name && token_string(name), pattern: token_string(pattern),
pattern_kind: pattern.type, action: action && token_string(action), loc: marker.location
)
end
|
#build_lexer_state(marker, name, entries) ⇒ AST::LexerState
233
234
235
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 233
def build_lexer_state(marker, name, entries)
AST::LexerState.new(name: token_string(name), definitions: entries, loc: marker.location)
end
|
#build_node_annotation(marker, name, fields) ⇒ AST::NodeAnnotation
278
279
280
281
282
283
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 278
def build_node_annotation(marker, name, fields)
extended_only!(marker.location, "@node")
AST::NodeAnnotation.new(
name: token_string(name), fields: fields.map { |field| token_string(field) }, loc: marker.location
)
end
|
#build_on_error_reduce(keyword, names) ⇒ AST::OnErrorReduce
197
198
199
200
201
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 197
def build_on_error_reduce(keyword, names)
extended_only!(keyword.location, "%on_error_reduce")
fail_at(keyword.location, "%on_error_reduce requires at least one nonterminal") if names.empty?
AST::OnErrorReduce.new(names: names, loc: keyword.location)
end
|
#build_options(keyword, names) ⇒ AST::Options
149
150
151
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 149
def build_options(keyword, names)
AST::Options.new(names: names, loc: keyword.location)
end
|
#build_parameter(keyword, name, type) ⇒ AST::Parameter
165
166
167
168
169
170
171
172
173
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 165
def build_parameter(keyword, name, type)
extended_only!(keyword.location, "%param")
if type && (keyword.location.line != name.location.line || name.location.line != type.location.line)
fail_at(keyword.location, "%param declaration must be written on one line")
end
semantic_type = type && decode_metadata_value(type, "%param")
AST::Parameter.new(name: token_string(name), semantic_type: semantic_type, loc: keyword.location)
end
|
#build_precedence(keyword, direction, levels) ⇒ AST::Precedence
135
136
137
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 135
def build_precedence(keyword, direction, levels)
AST::Precedence.new(direction: direction, levels: levels, loc: keyword.location)
end
|
#build_precedence_level(association, symbols) ⇒ AST::PrecedenceLevel
140
141
142
143
144
145
146
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 140
def build_precedence_level(association, symbols)
fail_at(association.location, "expected at least one precedence symbol") if symbols.empty?
extended_only!(association.location, "%precedence") if token_string(association) == "precedence"
AST::PrecedenceLevel.new(associativity: token_string(association).to_sym, symbols: symbols,
loc: association.location)
end
|
#build_printer(keyword, symbol, action) ⇒ AST::Printer
176
177
178
179
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 176
def build_printer(keyword, symbol, action)
extended_only!(keyword.location, "%printer")
AST::Printer.new(name: token_string(symbol), code: token_string(action), loc: keyword.location)
end
|
#build_recovery(keyword, kind, sync_tokens) ⇒ AST::Recovery
189
190
191
192
193
194
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 189
def build_recovery(keyword, kind, sync_tokens)
extended_only!(keyword.location, "%recover")
fail_at(kind.location, "expected sync, got #{kind.value}") unless token_string(kind) == "sync"
fail_at(keyword.location, "%recover sync requires at least one token") if sync_tokens.empty?
AST::Recovery.new(sync_tokens: sync_tokens, loc: keyword.location)
end
|
#build_root(class_token, class_parts, superclass, declarations, rules, user_code) ⇒ AST::Root
106
107
108
109
110
111
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 106
def build_root(class_token, class_parts, superclass, declarations, rules, user_code)
AST::Root.new(class_name: class_parts.join("::"), superclass: superclass&.join("::"),
declarations: declarations, rules: rules, user_code: user_code, loc: class_token.location,
extended: @mode == :extended || @adapter.extended_pragma? || @adapter.cst_pragma?,
cst: @adapter.cst_pragma?)
end
|
#build_separated_list(function, item, separator) ⇒ AST::SeparatedList
331
332
333
334
335
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 331
def build_separated_list(function, item, separator)
extended_only!(function.location, "separated lists")
AST::SeparatedList.new(item: item, separator: separator,
nonempty: function.value == "separated_nonempty_list", loc: function.location)
end
|
#build_start(keyword, names) ⇒ AST::Start
182
183
184
185
186
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 182
def build_start(keyword, names)
fail_at(keyword.location, "start declaration requires at least one symbol") if names.empty?
extended_only!(keyword.location, "multiple start symbols") if names.length > 1
AST::Start.new(names: names, loc: keyword.location)
end
|
#build_symbol_reference(token, named_reference, suffixes) ⇒ AST::item
303
304
305
306
307
308
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 303
def build_symbol_reference(token, named_reference, suffixes)
item = AST::SymbolReference.new(
name: token_string(token), named_reference: named_reference_value(named_reference), loc: token.location
)
apply_suffixes(item, suffixes)
end
|
#build_token_alias(name, value) ⇒ [ String, String ]
125
126
127
128
129
130
131
132
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 125
def build_token_alias(name, value)
extended_only!(name.location, "token aliases")
unless name.location.line == value.location.line
fail_at(name.location, "token alias must be written on one line")
end
[token_string(name), decode_metadata_value(value, "token alias")]
end
|
#build_token_entry(token) ⇒ [ String, nil ]
120
121
122
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 120
def build_token_entry(token)
[token_string(token), nil]
end
|
#build_tokens(keyword, entries) ⇒ AST::Tokens
114
115
116
117
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 114
def build_tokens(keyword, entries)
aliases = entries.filter_map { |name, value| [name, value] if value }.to_h
AST::Tokens.new(names: entries.map(&:first), aliases: aliases.empty? ? nil : aliases, loc: keyword.location)
end
|
#empty_user_code ⇒ AST::user_code
365
366
367
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 365
def empty_user_code
Hash.new { |hash, key| hash[key] = [] } end
|
#expected_description(token) ⇒ String
86
87
88
89
90
91
92
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 86
def expected_description(token)
expectation = @adapter.expectation(token)
return expectation if expectation
return ")" if %i[separated parameter].include?(@adapter.open_delimiter_kind)
structural_expectation(token)
end
|
#extended_only!(location, feature) ⇒ void
This method returns an undefined value.
370
371
372
373
374
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 370
def extended_only!(location, feature)
return if @mode == :extended || @adapter.extended_pragma? || @adapter.cst_pragma?
fail_at(location, "#{feature} require extended mode")
end
|
#fail_at(location, message) ⇒ bot
377
378
379
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 377
def fail_at(location, message)
raise Ibex::Error, "#{location}: #{message}"
end
|
#item_start_location(item) ⇒ Location?
292
293
294
295
296
297
298
299
300
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 292
def item_start_location(item)
return unless item
if item.is_a?(AST::Optional) || item.is_a?(AST::Star) || item.is_a?(AST::Plus)
return item_start_location(item.item)
end
item.loc
end
|
#named_reference_value(named_reference) ⇒ String?
311
312
313
314
315
316
317
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 311
def named_reference_value(named_reference)
return unless named_reference
colon, name = named_reference
extended_only!(colon.location, "named references")
token_string(name)
end
|
#next_token ⇒ [ external_token, Token ], false
36
37
38
39
40
41
42
43
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 36
def next_token
delivered = @adapter.next_token
@diagnostic_token = delivered ? delivered.fetch(1) : @adapter.eof_token
delivered
rescue Ibex::Error
@diagnostic_token = @adapter.last_token || @adapter.eof_token
raise
end
|
#on_error(_token_id, value, _value_stack) ⇒ bot
46
47
48
49
50
51
52
53
54
55
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 46
def on_error(_token_id, value, _value_stack)
token = value || @adapter.eof_token
@diagnostic_token = token
raise_contextual_error(token)
received = token&.value || token&.type || :eof
location = token&.location || Location.new(file: "(grammar)", line: 1, column: 1)
expected = expected_description(token)
raise Ibex::Error, "#{location}: expected #{expected}, got #{received}"
end
|
31
32
33
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 31
def parse
do_parse
end
|
#prepend_user_code(token, blocks) ⇒ AST::user_code
358
359
360
361
362
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 358
def prepend_user_code(token, blocks)
result = append_user_code(empty_user_code, token)
blocks.each { |name, chunks| result[name].concat(chunks) }
result
end
|
#raise_contextual_error(token) ⇒ void
This method returns an undefined value.
60
61
62
63
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 60
def raise_contextual_error(token)
raise_group_contextual_error(token)
raise_conversion_contextual_error
end
|
#raise_conversion_contextual_error ⇒ void
This method returns an undefined value.
77
78
79
80
81
82
83
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 77
def raise_conversion_contextual_error
conversion_name = @adapter.conversion_name
return unless @adapter.declaration == :convert
return unless conversion_name
fail_at(conversion_name.location, "expected a quoted Ruby conversion expression")
end
|
#raise_group_contextual_error(token) ⇒ void
This method returns an undefined value.
66
67
68
69
70
71
72
73
74
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 66
def raise_group_contextual_error(token)
group_opening = @adapter.group_opening
if group_opening && token && token.type == :action
fail_at(token.location, "actions inside EBNF groups are not supported")
end
group_unterminated = @adapter.open_delimiter_kind == :group &&
(token.nil? || token.type == :eof || token.value == "end")
fail_at(group_opening.location, "unterminated EBNF group") if group_unterminated && group_opening
end
|
#structural_expectation(token) ⇒ String
95
96
97
98
99
100
101
102
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 95
def structural_expectation(token)
return "rule" if @adapter.section == :declarations
return "at least one rule" if @adapter.section == :user_code && token&.value == "end"
return "eof" if @adapter.section == :user_code
return "end" if @adapter.section == :rules && @adapter.eof_token
"grammar syntax"
end
|
#token_integer(token) ⇒ Integer
390
391
392
393
394
395
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 390
def token_integer(token)
value = token.value
return value if value.is_a?(Integer)
fail_at(token.location, "expected integer token")
end
|
#token_string(token) ⇒ String
382
383
384
385
386
387
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 382
def token_string(token)
value = token.value
return value if value.is_a?(String)
fail_at(token.location, "expected text token")
end
|
#token_user_code(token) ⇒ user_code_token
398
399
400
401
402
403
|
# File 'lib/ibex/frontend/generated_parser_base.rb', line 398
def token_user_code(token)
value = token.value
return value if value.is_a?(Hash)
fail_at(token.location, "expected user-code token")
end
|