Module: Ibex::ErrorMessages::ParserV2

Included in:
Parser
Defined in:
lib/ibex/error_messages/parser_v2.rb,
sig/ibex/error_messages/parser_v2.rbs

Overview

Sentence-keyed ibex-messages v2 parsing, mixed into Parser so both versions share UTF-8, escape, and positioned-diagnostic helpers.

Instance Method Summary collapse

Instance Method Details

#append_v2_message_line(message_lines, line, index) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[String] message_lines, String line, Integer index) -> void

Parameters:

  • message_lines (Array[String])
  • line (String)
  • index (Integer)


127
128
129
130
131
# File 'lib/ibex/error_messages/parser_v2.rb', line 127

def append_v2_message_line(message_lines, line, index)
  content = line.delete_prefix("|").delete_prefix(" ")
  content_column = line.start_with?("| ") ? 3 : 2
  message_lines << decode_line(content, index + 1, content_column)
end

#close_v2_entry(opening_index, index, status, sentence, state, error_id, entry_name, message_lines) ⇒ [ Entry, Integer ]

RBS:

  • (Integer opening_index, Integer index, :active | :unreachable | :removed status, Array[String]? sentence, Integer? state, String? error_id, String? entry_name, Array[String] message_lines) -> [Entry, Integer]

Parameters:

  • opening_index (Integer)
  • index (Integer)
  • status (:active, :unreachable, :removed)
  • sentence (Array[String], nil)
  • state (Integer, nil)
  • error_id (String, nil)
  • entry_name (String, nil)
  • message_lines (Array[String])

Returns:



96
97
98
99
100
101
102
103
104
105
# File 'lib/ibex/error_messages/parser_v2.rb', line 96

def close_v2_entry(opening_index, index, status, sentence, state, error_id, entry_name, message_lines)
  fail_at(opening_index + 1, 1, "missing `## E0001` error id") unless error_id
  message = message_lines.empty? ? nil : message_lines.join("\n")
  fail_at(opening_index + 1, 1, "message must not be empty") if message&.strip&.empty?
  entry = Entry.new(
    state: state, status: status, message: message, line: opening_index + 1,
    sentence: sentence, error_id: error_id, entry: entry_name
  )
  [entry, index + 1]
end

#parse_sentence(source, line) ⇒ Array[String]

RBS:

  • (String source, Integer line) -> Array[String]

Parameters:

  • source (String)
  • line (Integer)

Returns:

  • (Array[String])


134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/ibex/error_messages/parser_v2.rb', line 134

def parse_sentence(source, line)
  tokens = [] #: Array[String]
  index = 0
  loop do
    index = skip_sentence_space(source, index)
    break if index == source.length

    token, index = read_sentence_token(source, index, line)
    tokens << token
  end
  fail_at(line, 1, "error sentence must contain at least one token") if tokens.empty?
  tokens
end

#parse_v2Document

RBS:

  • () -> Document

Returns:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/ibex/error_messages/parser_v2.rb', line 17

def parse_v2
  entries = [] #: Array[Entry]
  identifiers = {} #: Hash[String, Integer]
  keys = {} #: Hash[String, Integer]
  index = 1
  while index < @lines.length
    line = @lines.fetch(index)
    if ignorable?(line) && !line.lstrip.start_with?("##")
      index += 1
      next
    end

    entry, index = parse_v2_entry(index)
    record_v2_entry(entry, entries, identifiers, keys)
  end
  Document.new(version: 2, entries: entries)
end

#parse_v2_entry(opening_index) ⇒ [ Entry, Integer ]

RBS:

  • (Integer opening_index) -> [Entry, Integer]

Parameters:

  • opening_index (Integer)

Returns:



48
49
50
51
# File 'lib/ibex/error_messages/parser_v2.rb', line 48

def parse_v2_entry(opening_index)
  status, sentence, state = parse_v2_opening(opening_index)
  read_v2_entry_body(opening_index, status, sentence, state)
end

#parse_v2_opening(opening_index) ⇒ [ :active | :unreachable | :removed, Array[String]?, Integer? ]

RBS:

  • (Integer opening_index) -> [:active | :unreachable | :removed, Array[String]?, Integer?]

Parameters:

  • opening_index (Integer)

Returns:

  • ([ :active | :unreachable | :removed, Array[String]?, Integer? ])


55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/ibex/error_messages/parser_v2.rb', line 55

def parse_v2_opening(opening_index)
  opening = @lines.fetch(opening_index)
  if (legacy = opening.match(/\Alegacy-state:[ \t]*([0-9]+)\z/))
    return [:removed, nil, Integer(legacy[1] || "0", 10)]
  end

  if (sentence = opening.match(/\A(sentence|unreachable):[ \t]*(.*)\z/))
    status = sentence[1] == "unreachable" ? :unreachable : :active #: :unreachable | :active
    return [status, parse_sentence(sentence[2] || "", opening_index + 1), nil]
  end

  fail_at(opening_index + 1, column(opening),
          "expected `sentence: TOKENS`, `unreachable: TOKENS`, or `legacy-state: N`")
end

#quoted_token_end(source, index, quote, line) ⇒ Integer

RBS:

  • (String source, Integer index, String quote, Integer line) -> Integer

Parameters:

  • source (String)
  • index (Integer)
  • quote (String)
  • line (Integer)

Returns:

  • (Integer)


168
169
170
171
172
173
# File 'lib/ibex/error_messages/parser_v2.rb', line 168

def quoted_token_end(source, index, quote, line)
  ending = scan_quoted_token(source, index, quote, line)
  return ending if ending == source.length || source[ending]&.match?(/\s/)

  fail_at(line, ending + 1, "quoted token must be followed by whitespace")
end

#read_sentence_token(source, index, line) ⇒ [ String, Integer ]

RBS:

  • (String source, Integer index, Integer line) -> [String, Integer]

Parameters:

  • source (String)
  • index (Integer)
  • line (Integer)

Returns:

  • ([ String, Integer ])


155
156
157
158
159
160
161
162
163
164
165
# File 'lib/ibex/error_messages/parser_v2.rb', line 155

def read_sentence_token(source, index, line)
  start = index
  quote = source[index]
  if ["'", '"'].include?(quote)
    index = quoted_token_end(source, index, quote || "", line)
  else
    index += 1 while index < source.length && !source[index]&.match?(/\s/)
  end
  token = source[start...index] || raise(Ibex::Error, "missing sentence token")
  [token, index]
end

#read_v2_entry_body(opening_index, status, sentence, state) ⇒ [ Entry, Integer ]

RBS:

  • (Integer opening_index, :active | :unreachable | :removed status, Array[String]? sentence, Integer? state) -> [Entry, Integer]

Parameters:

  • opening_index (Integer)
  • status (:active, :unreachable, :removed)
  • sentence (Array[String], nil)
  • state (Integer, nil)

Returns:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/ibex/error_messages/parser_v2.rb', line 72

def read_v2_entry_body(opening_index, status, sentence, state)
  error_id = nil #: String?
  entry_name = nil #: String?
  message_lines = [] #: Array[String]
  index = opening_index + 1
  while index < @lines.length
    line = @lines.fetch(index)
    if line.strip == "end"
      return close_v2_entry(
        opening_index, index, status, sentence, state, error_id, entry_name, message_lines
      )
    end

    error_id, state, entry_name = (
      line, index, error_id, state, entry_name, message_lines
    )
    index += 1
  end
  fail_at(opening_index + 1, 1, "unterminated error-sentence entry")
end

#read_v2_metadata(line, index, error_id, state, entry_name, message_lines) ⇒ [ String?, Integer?, String? ]

RBS:

  • (String line, Integer index, String? error_id, Integer? state, String? entry_name, Array[String] message_lines) -> [String?, Integer?, String?]

Parameters:

  • line (String)
  • index (Integer)
  • error_id (String, nil)
  • state (Integer, nil)
  • entry_name (String, nil)
  • message_lines (Array[String])

Returns:

  • ([ String?, Integer?, String? ])


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/ibex/error_messages/parser_v2.rb', line 109

def (line, index, error_id, state, entry_name, message_lines)
  stripped = line.strip
  if (match = stripped.match(/\A##[ \t]+(E[0-9]{4,})\z/))
    fail_at(index + 1, column(line), "duplicate error id") if error_id
    error_id = match[1]
  elsif (match = stripped.match(/\A# state:[ \t]*([0-9]+)\z/))
    state = Integer(match[1] || "0", 10)
  elsif (match = stripped.match(/\A# entry:[ \t]*(\S+)\z/))
    entry_name = match[1]
  elsif line.start_with?("|")
    append_v2_message_line(message_lines, line, index)
  elsif !ignorable?(line)
    fail_at(index + 1, column(line), "expected metadata, a `| ` message line, a comment, or `end`")
  end
  [error_id, state, entry_name]
end

#record_v2_entry(entry, entries, identifiers, keys) ⇒ void

This method returns an undefined value.

RBS:

  • (Entry entry, Array[Entry] entries, Hash[String, Integer] identifiers, Hash[String, Integer] keys) -> void

Parameters:

  • entry (Entry)
  • entries (Array[Entry])
  • identifiers (Hash[String, Integer])
  • keys (Hash[String, Integer])


37
38
39
40
41
42
43
44
45
# File 'lib/ibex/error_messages/parser_v2.rb', line 37

def record_v2_entry(entry, entries, identifiers, keys)
  error_id = entry.error_id || raise(Ibex::Error, "missing error id")
  reject_v2_duplicate(identifiers, error_id, entry.line, "error id")
  key = entry.sentence ? "#{entry.entry}\0#{entry.sentence.join("\0")}" : "legacy\0#{entry.state}"
  reject_v2_duplicate(keys, key, entry.line, "sentence")
  identifiers[error_id] = entry.line
  keys[key] = entry.line
  entries << entry
end

#reject_v2_duplicate(declarations, key, line, label) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, Integer] declarations, String key, Integer line, String label) -> void

Parameters:

  • declarations (Hash[String, Integer])
  • key (String)
  • line (Integer)
  • label (String)


192
193
194
195
# File 'lib/ibex/error_messages/parser_v2.rb', line 192

def reject_v2_duplicate(declarations, key, line, label)
  previous = declarations[key]
  fail_at(line, 1, "duplicate #{label}; first declared at line #{previous}") if previous
end

#scan_quoted_token(source, index, quote, line) ⇒ Integer

RBS:

  • (String source, Integer index, String quote, Integer line) -> Integer

Parameters:

  • source (String)
  • index (Integer)
  • quote (String)
  • line (Integer)

Returns:

  • (Integer)


176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ibex/error_messages/parser_v2.rb', line 176

def scan_quoted_token(source, index, quote, line)
  index += 1
  while index < source.length
    character = source[index]
    if character == "\\"
      index += 2
    elsif character == quote
      return index + 1
    else
      index += 1
    end
  end
  fail_at(line, source.length + 1, "unterminated quoted token")
end

#skip_sentence_space(source, index) ⇒ Integer

RBS:

  • (String source, Integer index) -> Integer

Parameters:

  • source (String)
  • index (Integer)

Returns:

  • (Integer)


149
150
151
152
# File 'lib/ibex/error_messages/parser_v2.rb', line 149

def skip_sentence_space(source, index)
  index += 1 while index < source.length && source[index]&.match?(/\s/)
  index
end