Module: Ibex::NormalizeLexer
- Included in:
- Normalizer
- Defined in:
- lib/ibex/normalize/lexer.rb,
sig/ibex/normalize/lexer.rbs
Overview
Lexer declaration normalization and static safety checks.
Instance Method Summary collapse
- #decode_lexer_literal(raw, location) ⇒ String
- #normalize_lexer ⇒ IR::Lexer?
- #normalize_lexer_pattern(definition) ⇒ [ String, String ]
- #normalize_lexer_rule(definition, state, id, warnings) ⇒ IR::LexerRule
- #normalize_lexer_state(definition, states, rules, warnings) ⇒ void
- #risky_lexer_pattern?(source) ⇒ Boolean
- #validate_lexer_action(definition) ⇒ void
- #validate_lexer_regexp(definition, source, options) ⇒ void
- #validate_lexer_token(definition) ⇒ void
Instance Method Details
#decode_lexer_literal(raw, location) ⇒ String
104 105 106 107 108 109 110 111 |
# File 'lib/ibex/normalize/lexer.rb', line 104 def decode_lexer_literal(raw, location) # @type self: Normalizer return raw.undump if raw.start_with?('"') (raw[1...-1] || "").gsub("\\'", "'").gsub("\\\\", "\\") rescue RuntimeError => e fail_at(location, "invalid lexer literal: #{e.}") end |
#normalize_lexer ⇒ IR::Lexer?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/ibex/normalize/lexer.rb', line 9 def normalize_lexer # @type self: Normalizer declaration = @lexer_declaration return unless declaration states = ["INITIAL"] #: Array[String] rules = [] #: Array[IR::LexerRule] warnings = [] #: Array[Hash[Symbol, untyped]] declaration.definitions.each do |definition| if definition.is_a?(Frontend::AST::LexerState) normalize_lexer_state(definition, states, rules, warnings) else rules << normalize_lexer_rule(definition, "INITIAL", rules.length, warnings) end end fail_at(declaration.loc, "lexer declaration requires at least one rule") if rules.empty? IR::Lexer.new( states: states, rules: rules, warnings: warnings, source_provenance: { file: declaration.loc.file, root: @resolution&.root_directory, byte_span: nil } ) end |
#normalize_lexer_pattern(definition) ⇒ [ String, String ]
89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/ibex/normalize/lexer.rb', line 89 def normalize_lexer_pattern(definition) # @type self: Normalizer raw = definition.pattern if definition.pattern_kind == :literal decoded = decode_lexer_literal(raw, definition.loc) fail_at(definition.loc, "lexer pattern must not be empty") if decoded.empty? return [Regexp.escape(decoded), ""] end closing = raw.rindex("/") fail_at(definition.loc, "invalid lexer regular expression") unless closing&.positive? [raw[1...closing] || "", raw[(closing + 1)..] || ""] end |
#normalize_lexer_rule(definition, state, id, warnings) ⇒ IR::LexerRule
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/ibex/normalize/lexer.rb', line 47 def normalize_lexer_rule(definition, state, id, warnings) # @type self: Normalizer validate_lexer_token(definition) validate_lexer_action(definition) source, = normalize_lexer_pattern(definition) validate_lexer_regexp(definition, source, ) if risky_lexer_pattern?(source) warning = { type: :lexer_redos, loc: definition.loc.to_h } #: IR::grammar_warning warning[:symbol] = definition.token if definition.token @warnings << warning warnings << { type: :redos, rule: id, loc: definition.loc.to_h } end IR::LexerRule.new( id: id, state: state, kind: definition.kind, token: definition.token, pattern: source, pattern_kind: definition.pattern_kind, options: , action: definition.action, location: definition.loc.to_h ) end |
#normalize_lexer_state(definition, states, rules, warnings) ⇒ void
This method returns an undefined value.
33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/ibex/normalize/lexer.rb', line 33 def normalize_lexer_state(definition, states, rules, warnings) # @type self: Normalizer fail_at(definition.loc, "lexer state INITIAL is reserved") if definition.name == "INITIAL" fail_at(definition.loc, "duplicate lexer state #{definition.name}") if states.include?(definition.name) states << definition.name definition.definitions.each do |entry| fail_at(entry.loc, "nested lexer states are not supported") if entry.is_a?(Frontend::AST::LexerState) rules << normalize_lexer_rule(entry, definition.name, rules.length, warnings) end end |
#risky_lexer_pattern?(source) ⇒ Boolean
127 128 129 |
# File 'lib/ibex/normalize/lexer.rb', line 127 def risky_lexer_pattern?(source) source.match?(/\([^)]*[+*][^)]*\)[+*{]/) || source.match?(/\.\*[+*{]/) end |
#validate_lexer_action(definition) ⇒ void
This method returns an undefined value.
76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/ibex/normalize/lexer.rb', line 76 def validate_lexer_action(definition) # @type self: Normalizer action = definition.action return unless action&.lstrip&.start_with?("|") match = action.match(/\A\s*\|([a-z_][a-zA-Z0-9_]*)\|/m) fail_at(definition.loc, "lexer action accepts exactly one local identifier") unless match name = match[1] fail_at(definition.loc, "lexer action parameter #{name.inspect} is a Ruby keyword") if Normalizer::RUBY_KEYWORDS.include?(name) end |
#validate_lexer_regexp(definition, source, options) ⇒ void
This method returns an undefined value.
114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/ibex/normalize/lexer.rb', line 114 def validate_lexer_regexp(definition, source, ) # @type self: Normalizer flags = 0 flags |= Regexp::IGNORECASE if .include?("i") flags |= Regexp::MULTILINE if .include?("m") flags |= Regexp::EXTENDED if .include?("x") regexp = Regexp.new("\\A(?:#{source})", flags) fail_at(definition.loc, "lexer pattern must not match an empty string") if regexp.match?("") rescue RegexpError => e fail_at(definition.loc, "invalid lexer regular expression: #{e.}") end |
#validate_lexer_token(definition) ⇒ void
This method returns an undefined value.
67 68 69 70 71 72 73 |
# File 'lib/ibex/normalize/lexer.rb', line 67 def validate_lexer_token(definition) # @type self: Normalizer return unless definition.kind == :token return if definition.token && @declared_tokens.key?(definition.token) fail_at(definition.loc, "lexer rule references undeclared terminal #{definition.token}") end |