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 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_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
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
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
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
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.}") end |
#decode_quoted_literal(token, feature) ⇒ 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.}") end |
#expect_lexer_pattern ⇒ Token
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_value ⇒ Token
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_convert ⇒ AST::Convert
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_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 |
# 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 "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
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_rr ⇒ AST::ExpectRR
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_test ⇒ AST::GrammarTest
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.}") end AST::GrammarTest.new(expectation: expectation.to_sym, source: decoded, loc: keyword.location) end |
#parse_import ⇒ AST::Include
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.}") end AST::Include.new(path: decoded, loc: keyword.location) end |
#parse_lexer ⇒ AST::Lexer
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_entries ⇒ 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_entry ⇒ 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_state ⇒ AST::LexerState
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_reduce ⇒ AST::OnErrorReduce
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_options ⇒ AST::Options
132 133 134 135 136 137 138 |
# File 'lib/ibex/frontend/parser/declarations.rb', line 132 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
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_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] = true @mode = :extended end end |
#parse_precedence ⇒ AST::Precedence
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_printer ⇒ AST::Printer
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_recovery ⇒ AST::Recovery
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_start ⇒ AST::Start
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
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_tokens ⇒ AST::Tokens
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]
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]
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.
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 |