Module: Ibex::Frontend::BootstrapParserDeclarations

Included in:
BootstrapParser
Defined in:
lib/ibex/frontend/parser/declarations.rb,
sig/ibex/frontend/parser/declarations.rbs

Overview

Parses the declaration section of a grammar. rubocop:disable Metrics/ModuleLength, Metrics/CyclomaticComplexity Bootstrap declarations intentionally mirror the generated frontend's complete declaration vocabulary.

Constant Summary collapse

DECLARATIONS =

Returns:

  • (Array[String])
%w[
  include import token prechigh preclow options expect expect_rr start recover on_error_reduce
  test lexer convert display type param printer pragma rule
].freeze
ASSOCIATIVITIES =

Signature:

  • Array[String]

Returns:

  • (Array[String])
%w[left right nonassoc precedence].freeze

Instance Method Summary collapse

Instance Method Details

#association_start?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


392
393
394
395
# File 'lib/ibex/frontend/parser/declarations.rb', line 392

def association_start?
  # @type self: BootstrapParser
  current.type == :identifier && ASSOCIATIVITIES.include?(current.value)
end

#build_bootstrap_lexer_rule(kind, marker, pattern, action) ⇒ AST::LexerRule

RBS:

  • (Symbol kind, Token marker, Token pattern, Token? action) -> AST::LexerRule

Parameters:

Returns:



288
289
290
291
292
293
# File 'lib/ibex/frontend/parser/declarations.rb', line 288

def build_bootstrap_lexer_rule(kind, marker, pattern, action)
  # @type self: BootstrapParser
  token = kind == :token ? token_string(marker) : nil
  AST::LexerRule.new(kind: kind, token: token, pattern: token_string(pattern),
                     pattern_kind: pattern.type, action: action && token_string(action), loc: marker.location)
end

#declaration_start?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


382
383
384
385
386
387
388
389
# File 'lib/ibex/frontend/parser/declarations.rb', line 382

def declaration_start?
  # @type self: BootstrapParser
  return true if current.type == :eof
  return false unless current.type == :identifier && DECLARATIONS.include?(current.value)
  return false if %w[display type param printer lexer].include?(current.value) && @mode != :extended

  true
end

#decode_conversion(tokens, location) ⇒ String

RBS:

  • (Array[Token] tokens, Location location) -> String

Parameters:

Returns:

  • (String)


340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/ibex/frontend/parser/declarations.rb', line 340

def decode_conversion(tokens, location)
  # @type self: BootstrapParser
  unless tokens.length == 1 && tokens.first.type == :literal
    fail_at(location, "expected a quoted Ruby conversion expression")
  end

  literal = token_string(tokens.first)
  return literal.undump if literal.start_with?('"')

  (literal[1...-1] || "").gsub("\\'", "'").gsub("\\\\", "\\")
rescue RuntimeError => e
  fail_at(location, "invalid conversion expression: #{e.message}")
end

#decode_quoted_literal(token, feature) ⇒ String

RBS:

  • (Token token, String feature) -> String

Parameters:

  • token (Token)
  • feature (String)

Returns:

  • (String)


363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/ibex/frontend/parser/declarations.rb', line 363

def decode_quoted_literal(token, feature)
  # @type self: BootstrapParser
  literal = token_string(token)
  decoded = if literal.start_with?('"')
              literal.undump
            else
              (literal[1...-1] || "").gsub("\\'", "'").gsub("\\\\", "\\")
            end
  fail_at(token.location, "#{feature} value must not be empty") if decoded.strip.empty?
  fail_at(token.location, "#{feature} value must be a single line") if decoded.match?(/[\r\n]/)
  fail_at(token.location, "#{feature} value must not contain control characters") if
    decoded.match?(/[[:cntrl:]]/)

  decoded
rescue RuntimeError => e
  fail_at(token.location, "invalid #{feature} value: #{e.message}")
end

#expect_lexer_patternToken

RBS:

  • () -> Token

Returns:



280
281
282
283
284
285
# File 'lib/ibex/frontend/parser/declarations.rb', line 280

def expect_lexer_pattern
  # @type self: BootstrapParser
  return advance if %i[regexp literal].include?(current.type)

  fail_expected("a regular expression or quoted literal")
end

#expect_metadata_valueToken

RBS:

  • () -> Token

Returns:



324
325
326
327
328
329
# File 'lib/ibex/frontend/parser/declarations.rb', line 324

def 
  # @type self: BootstrapParser
  return advance if current.type == :literal

  fail_expected("a quoted string")
end

#parse_convertAST::Convert

RBS:

  • () -> AST::Convert

Returns:



296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/ibex/frontend/parser/declarations.rb', line 296

def parse_convert
  # @type self: BootstrapParser
  location = advance.location
  pairs = [] #: Array[AST::Conversion]
  until keyword?("end") || current.type == :eof
    name_token = current
    name = parse_symbol_name
    expression = decode_conversion(tokens_on_line(name_token.location.line), name_token.location)
    pairs << AST::Conversion.new(name: name, expression: expression, loc: name_token.location)
  end
  expect_keyword("end")
  AST::Convert.new(pairs: pairs, loc: location)
end

#parse_declarationAST::declaration

RBS:

  • () -> AST::declaration

Returns:

  • (AST::declaration)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ibex/frontend/parser/declarations.rb', line 42

def parse_declaration
  # @type self: BootstrapParser
  case current.value
  when "include", "import" then parse_import
  when "token" then parse_tokens
  when "prechigh", "preclow" then parse_precedence
  when "options" then parse_options
  when "expect" then parse_expect
  when "expect_rr" then parse_expect_rr
  when "start" then parse_start
  when "recover" then parse_recovery
  when "on_error_reduce" then parse_on_error_reduce
  when "test" then parse_grammar_test
  when "lexer" then parse_lexer
  when "convert" then parse_convert
  when "display" then (AST::DisplayName, "display")
  when "type" then (AST::SemanticType, "type")
  when "param" then parse_parameter
  when "printer" then parse_printer
  when "pragma" then fail_at(current.location, "expected rule, got pragma")
  else fail_expected("a declaration or rule")
  end
end

#parse_declarationsArray[AST::declaration]

RBS:

  • () -> Array[AST::declaration]

Returns:

  • (Array[AST::declaration])


18
19
20
21
22
23
# File 'lib/ibex/frontend/parser/declarations.rb', line 18

def parse_declarations
  # @type self: BootstrapParser
  declarations = [] #: Array[AST::declaration]
  declarations << parse_declaration until keyword?("rule") || current.type == :eof
  declarations
end

#parse_expectAST::Expect

RBS:

  • () -> AST::Expect

Returns:



141
142
143
144
145
# File 'lib/ibex/frontend/parser/declarations.rb', line 141

def parse_expect
  # @type self: BootstrapParser
  location = advance.location
  AST::Expect.new(conflicts: token_integer(expect(:integer)), loc: location)
end

#parse_expect_rrAST::ExpectRR

RBS:

  • () -> AST::ExpectRR

Returns:



148
149
150
151
152
153
# File 'lib/ibex/frontend/parser/declarations.rb', line 148

def parse_expect_rr
  # @type self: BootstrapParser
  location = advance.location
  extended_only!(location, "expect-rr declarations")
  AST::ExpectRR.new(conflicts: token_integer(expect(:integer)), loc: location)
end

#parse_grammar_testAST::GrammarTest

RBS:

  • () -> AST::GrammarTest

Returns:



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/ibex/frontend/parser/declarations.rb', line 214

def parse_grammar_test
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "%test")
  expectation = token_string(expect(:identifier))
  fail_at(keyword.location, "expected accept or reject, got #{expectation}") unless
    %w[accept reject].include?(expectation)
  source = expect(:literal)
  literal = token_string(source)
  fail_at(source.location, "%test source must use a double-quoted string") unless literal.start_with?('"')
  decoded = begin
    literal.undump
  rescue RuntimeError => e
    fail_at(source.location, "invalid %test source: #{e.message}")
  end

  AST::GrammarTest.new(expectation: expectation.to_sym, source: decoded, loc: keyword.location)
end

#parse_importAST::Include

RBS:

  • () -> AST::Include

Returns:



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ibex/frontend/parser/declarations.rb', line 67

def parse_import
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "#{keyword.value}s")
  path = expect(:literal)
  value = token_string(path)
  unless value.start_with?('"') && value.end_with?('"')
    fail_at(path.location, "#{keyword.value} path must use a double-quoted string")
  end

  decoded = begin
    value.undump
  rescue RuntimeError => e
    fail_at(path.location, "invalid #{keyword.value} path: #{e.message}")
  end
  AST::Include.new(path: decoded, loc: keyword.location)
end

#parse_lexerAST::Lexer

RBS:

  • () -> AST::Lexer

Returns:



234
235
236
237
238
239
240
241
# File 'lib/ibex/frontend/parser/declarations.rb', line 234

def parse_lexer
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "lexer declarations")
  entries = parse_lexer_entries
  expect_keyword("end")
  AST::Lexer.new(definitions: entries, loc: keyword.location)
end

#parse_lexer_entriesArray[AST::lexer_entry]

RBS:

  • () -> Array[AST::lexer_entry]

Returns:

  • (Array[AST::lexer_entry])


244
245
246
247
248
249
# File 'lib/ibex/frontend/parser/declarations.rb', line 244

def parse_lexer_entries
  # @type self: BootstrapParser
  entries = [] #: Array[AST::lexer_entry]
  entries << parse_lexer_entry until keyword?("end") || current.type == :eof
  entries
end

#parse_lexer_entryAST::lexer_entry

RBS:

  • () -> AST::lexer_entry

Returns:

  • (AST::lexer_entry)


252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/ibex/frontend/parser/declarations.rb', line 252

def parse_lexer_entry
  # @type self: BootstrapParser
  return parse_lexer_state if keyword?("state")

  marker = expect(:identifier)
  kind = case token_string(marker)
         when "skip" then :skip
         when "on" then :on
         else :token
         end
  pattern = expect_lexer_pattern
  action = current.type == :action ? advance : nil
  fail_at(marker.location, "on lexer rules require an action") if kind == :on && !action
  build_bootstrap_lexer_rule(kind, marker, pattern, action)
end

#parse_lexer_stateAST::LexerState

RBS:

  • () -> AST::LexerState

Returns:



269
270
271
272
273
274
275
276
277
# File 'lib/ibex/frontend/parser/declarations.rb', line 269

def parse_lexer_state
  # @type self: BootstrapParser
  marker = advance
  name = expect(:identifier)
  expect_keyword("do")
  entries = parse_lexer_entries
  expect_keyword("end")
  AST::LexerState.new(name: token_string(name), definitions: entries, loc: marker.location)
end

#parse_on_error_reduceAST::OnErrorReduce

RBS:

  • () -> AST::OnErrorReduce

Returns:



204
205
206
207
208
209
210
211
# File 'lib/ibex/frontend/parser/declarations.rb', line 204

def parse_on_error_reduce
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "%on_error_reduce")
  names = [parse_symbol_name]
  names << parse_symbol_name until declaration_start?
  AST::OnErrorReduce.new(names: names, loc: keyword.location)
end

#parse_optionsAST::Options

RBS:

  • () -> AST::Options

Returns:



132
133
134
135
136
137
138
# File 'lib/ibex/frontend/parser/declarations.rb', line 132

def parse_options
  # @type self: BootstrapParser
  location = advance.location
  names = [] #: Array[String]
  names << token_string(expect(:identifier)) until declaration_start?
  AST::Options.new(names: names, loc: location)
end

#parse_parameterAST::Parameter

RBS:

  • () -> AST::Parameter

Returns:



156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ibex/frontend/parser/declarations.rb', line 156

def parse_parameter
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "%param")
  name = expect(:identifier)
  type = current.type == :literal ? advance : nil
  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_quoted_literal(type, "%param")
  AST::Parameter.new(name: token_string(name), semantic_type: semantic_type, loc: keyword.location)
end

#parse_pragmasvoid

This method returns an undefined value.

RBS:

  • () -> void



26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/ibex/frontend/parser/declarations.rb', line 26

def parse_pragmas
  # @type self: BootstrapParser
  while keyword?("pragma")
    keyword = current
    advance
    value = expect(:identifier)
    name = token_string(value)
    fail_at(value.location, "unknown pragma #{name}") unless %w[extended cst].include?(name)
    fail_at(keyword.location, "duplicate pragma #{name}") if @pragmas[name]

    @pragmas[name] = true
    @mode = :extended
  end
end

#parse_precedenceAST::Precedence

RBS:

  • () -> AST::Precedence

Returns:



104
105
106
107
108
109
110
111
112
113
# File 'lib/ibex/frontend/parser/declarations.rb', line 104

def parse_precedence
  # @type self: BootstrapParser
  opening = advance
  opening_name = token_string(opening)
  closing = opening_name == "prechigh" ? "preclow" : "prechigh"
  levels = precedence_levels(closing)
  expect_keyword(closing)
  AST::Precedence.new(direction: opening_name == "prechigh" ? :high_to_low : :low_to_high,
                      levels: levels, loc: opening.location)
end

#parse_printerAST::Printer

RBS:

  • () -> AST::Printer

Returns:



171
172
173
174
175
176
177
178
# File 'lib/ibex/frontend/parser/declarations.rb', line 171

def parse_printer
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "%printer")
  name = parse_symbol_name
  action = expect(:action)
  AST::Printer.new(name: name, code: token_string(action), loc: keyword.location)
end

#parse_recoveryAST::Recovery

RBS:

  • () -> AST::Recovery

Returns:



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/ibex/frontend/parser/declarations.rb', line 191

def parse_recovery
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "%recover")
  kind = expect(:identifier)
  fail_at(kind.location, "expected sync, got #{kind.value}") unless token_string(kind) == "sync"
  expect(:":")
  tokens = [parse_symbol_name]
  tokens << parse_symbol_name until declaration_start?
  AST::Recovery.new(sync_tokens: tokens, loc: keyword.location)
end

#parse_startAST::Start

RBS:

  • () -> AST::Start

Returns:



181
182
183
184
185
186
187
188
# File 'lib/ibex/frontend/parser/declarations.rb', line 181

def parse_start
  # @type self: BootstrapParser
  location = advance.location
  names = [parse_symbol_name]
  names << parse_symbol_name until declaration_start?
  extended_only!(location, "multiple start symbols") if names.length > 1
  AST::Start.new(names: names, loc: location)
end

#parse_symbol_metadata(node_class, feature) ⇒ AST::DisplayName, AST::SemanticType

RBS:

  • (singleton(AST::DisplayName) | singleton(AST::SemanticType) node_class, String feature) -> (AST::DisplayName | AST::SemanticType)

Parameters:

Returns:



312
313
314
315
316
317
318
319
320
321
# File 'lib/ibex/frontend/parser/declarations.rb', line 312

def (node_class, feature)
  # @type self: BootstrapParser
  keyword = advance
  extended_only!(keyword.location, "#{feature} declarations")
  name = expect_symbol
  value = 
  (keyword, name, value, feature)
  decoded = decode_quoted_literal(value, feature)
  node_class.new(name: token_string(name), value: decoded, loc: keyword.location)
end

#parse_tokensAST::Tokens

RBS:

  • () -> AST::Tokens

Returns:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ibex/frontend/parser/declarations.rb', line 86

def parse_tokens
  # @type self: BootstrapParser
  location = advance.location
  names = [] #: Array[String]
  aliases = {} #: Hash[String, String]
  until declaration_start?
    name_token = expect_symbol
    name = token_string(name_token)
    names << name
    next unless @mode == :extended && name_token.type == :identifier &&
                current.type == :literal && current.location.line == name_token.location.line

    aliases[name] = decode_quoted_literal(advance, "token alias")
  end
  AST::Tokens.new(names: names, aliases: aliases.empty? ? nil : aliases, loc: location)
end

#precedence_levels(closing) ⇒ Array[AST::PrecedenceLevel]

RBS:

  • (String closing) -> Array[AST::PrecedenceLevel]

Parameters:

  • closing (String)

Returns:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/ibex/frontend/parser/declarations.rb', line 116

def precedence_levels(closing)
  # @type self: BootstrapParser
  levels = [] #: Array[AST::PrecedenceLevel]
  until keyword?(closing) || current.type == :eof
    association = expect_one_of(ASSOCIATIVITIES)
    extended_only!(association.location, "%precedence") if token_string(association) == "precedence"
    symbols = [] #: Array[String]
    symbols << parse_symbol_name until association_start? || keyword?(closing) || current.type == :eof
    fail_at(association.location, "expected at least one precedence symbol") if symbols.empty?
    levels << AST::PrecedenceLevel.new(associativity: token_string(association).to_sym, symbols: symbols,
                                       loc: association.location)
  end
  levels
end

#tokens_on_line(line) ⇒ Array[Token]

RBS:

  • (Integer line) -> Array[Token]

Parameters:

  • line (Integer)

Returns:



332
333
334
335
336
337
# File 'lib/ibex/frontend/parser/declarations.rb', line 332

def tokens_on_line(line)
  # @type self: BootstrapParser
  tokens = [] #: Array[Token]
  tokens << advance while current.type != :eof && current.location.line == line && !keyword?("end")
  tokens
end

#validate_metadata_line(keyword, name, value, feature) ⇒ void

This method returns an undefined value.

RBS:

  • (Token keyword, Token name, Token value, String feature) -> void

Parameters:



355
356
357
358
359
360
# File 'lib/ibex/frontend/parser/declarations.rb', line 355

def (keyword, name, value, feature)
  # @type self: BootstrapParser
  return if keyword.location.line == name.location.line && name.location.line == value.location.line

  fail_at(keyword.location, "#{feature} declaration must be written on one line")
end