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 =
%w[ include import token prechigh preclow options expect expect_rr start recover on_error_reduce test lexer convert display type param printer parser pragma rule ].freeze
- ASSOCIATIVITIES =
%w[left right nonassoc precedence].freeze
Instance Method Summary collapse
- #association_start? ⇒ Boolean
- #build_bootstrap_lexer_rule(kind, marker, pattern, action) ⇒ AST::LexerRule
- #declaration_start? ⇒ Boolean
- #decode_conversion(tokens, location) ⇒ String
- #decode_quoted_literal(token, feature) ⇒ String
- #expect_lexer_pattern ⇒ Token
- #expect_metadata_value ⇒ Token
- #parse_convert ⇒ AST::Convert
- #parse_declaration ⇒ AST::declaration
- #parse_declarations ⇒ Array[AST::declaration]
- #parse_expect ⇒ AST::Expect
- #parse_expect_rr ⇒ AST::ExpectRR
- #parse_grammar_test ⇒ AST::GrammarTest
- #parse_import ⇒ AST::Include
- #parse_lexer ⇒ AST::Lexer
- #parse_lexer_entries ⇒ Array[AST::lexer_entry]
- #parse_lexer_entry ⇒ AST::lexer_entry
- #parse_lexer_state ⇒ AST::LexerState
- #parse_on_error_reduce ⇒ AST::OnErrorReduce
- #parse_options ⇒ AST::Options
- #parse_parameter ⇒ AST::Parameter
- #parse_parser_configuration ⇒ AST::ParserConfiguration
- #parse_pragmas ⇒ void
- #parse_precedence ⇒ AST::Precedence
- #parse_printer ⇒ AST::Printer
- #parse_recovery ⇒ AST::Recovery
- #parse_start ⇒ AST::Start
- #parse_symbol_metadata(node_class, feature) ⇒ AST::DisplayName, AST::SemanticType
- #parse_tokens ⇒ AST::Tokens
- #precedence_levels(closing) ⇒ Array[AST::PrecedenceLevel]
- #tokens_on_line(line) ⇒ Array[Token]
- #validate_metadata_line(keyword, name, value, feature) ⇒ void
Instance Method Details
#association_start? ⇒ Boolean
407 408 409 410 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 407 def association_start? # @type self: BootstrapParser current.type == :identifier && ASSOCIATIVITIES.include?(current.value) end |
#build_bootstrap_lexer_rule(kind, marker, pattern, action) ⇒ AST::LexerRule
303 304 305 306 307 308 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 303 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
397 398 399 400 401 402 403 404 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 397 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
355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 355 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.}") end |
#decode_quoted_literal(token, feature) ⇒ String
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 378 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.}") end |
#expect_lexer_pattern ⇒ Token
295 296 297 298 299 300 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 295 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_value ⇒ Token
339 340 341 342 343 344 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 339 def # @type self: BootstrapParser return advance if current.type == :literal fail_expected("a quoted string") end |
#parse_convert ⇒ AST::Convert
311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 311 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_declaration ⇒ 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 65 |
# 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 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 "parser" then parse_parser_configuration when "pragma" then fail_at(current.location, "expected rule, got pragma") else fail_expected("a declaration or rule") end end |
#parse_declarations ⇒ 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_expect ⇒ AST::Expect
156 157 158 159 160 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 156 def parse_expect # @type self: BootstrapParser location = advance.location AST::Expect.new(conflicts: token_integer(expect(:integer)), loc: location) end |
#parse_expect_rr ⇒ AST::ExpectRR
163 164 165 166 167 168 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 163 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_test ⇒ AST::GrammarTest
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 229 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.}") end AST::GrammarTest.new(expectation: expectation.to_sym, source: decoded, loc: keyword.location) end |
#parse_import ⇒ AST::Include
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 82 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.}") end AST::Include.new(path: decoded, loc: keyword.location) end |
#parse_lexer ⇒ AST::Lexer
249 250 251 252 253 254 255 256 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 249 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_entries ⇒ Array[AST::lexer_entry]
259 260 261 262 263 264 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 259 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_entry ⇒ AST::lexer_entry
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 267 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_state ⇒ AST::LexerState
284 285 286 287 288 289 290 291 292 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 284 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_reduce ⇒ AST::OnErrorReduce
219 220 221 222 223 224 225 226 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 219 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_options ⇒ AST::Options
147 148 149 150 151 152 153 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 147 def # @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_parameter ⇒ AST::Parameter
171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 171 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_parser_configuration ⇒ AST::ParserConfiguration
68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 68 def parse_parser_configuration # @type self: BootstrapParser keyword = advance settings = [] #: Array[AST::ParserSetting] until keyword?("end") || current.type == :eof key = expect(:identifier) value = expect(:identifier) settings << build_parser_setting(key, value) end expect_keyword("end") build_parser_configuration(keyword, settings) end |
#parse_pragmas ⇒ void
This method returns an undefined value.
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] = keyword.location @mode = :extended end end |
#parse_precedence ⇒ AST::Precedence
119 120 121 122 123 124 125 126 127 128 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 119 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_printer ⇒ AST::Printer
186 187 188 189 190 191 192 193 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 186 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_recovery ⇒ AST::Recovery
206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 206 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_start ⇒ AST::Start
196 197 198 199 200 201 202 203 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 196 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
327 328 329 330 331 332 333 334 335 336 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 327 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_tokens ⇒ AST::Tokens
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 101 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]
131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 131 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]
347 348 349 350 351 352 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 347 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.
370 371 372 373 374 375 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 370 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 |