Class: Ibex::ErrorMessages::Parser
- Inherits:
-
Object
- Object
- Ibex::ErrorMessages::Parser
show all
- Includes:
- ParserV2
- Defined in:
- lib/ibex/error_messages/parser.rb,
sig/ibex/error_messages/parser.rbs
Overview
Strict line-oriented parser for the ibex-messages v1 and v2 formats.
Constant Summary
collapse
- ESCAPES =
{ "\\" => "\\", "n" => "\n", "t" => "\t", "r" => "\r" }.freeze
Instance Method Summary
collapse
-
#close_entry(message_lines, opening_index, index, state, status) ⇒ [ Entry, Integer ]
-
#column(line) ⇒ Integer
-
#decode_line(content, line, content_column) ⇒ String
-
#fail_at(line, column, message) ⇒ bot
-
#ignorable?(line) ⇒ Boolean
-
#initialize(source, file:) ⇒ Parser
constructor
A new instance of Parser.
-
#parse ⇒ Document
-
#parse_entry(opening_index, state, status) ⇒ [ Entry, Integer ]
-
#parse_v1 ⇒ Document
-
#read_entry_line(message_lines, line, index) ⇒ void
-
#reject_duplicate(declarations, state, index, line) ⇒ void
-
#top_level_expectation ⇒ String
-
#validate_header ⇒ Integer
Methods included from ParserV2
#append_v2_message_line, #close_v2_entry, #parse_sentence, #parse_v2, #parse_v2_entry, #parse_v2_opening, #quoted_token_end, #read_sentence_token, #read_v2_entry_body, #read_v2_metadata, #record_v2_entry, #reject_v2_duplicate, #scan_quoted_token, #skip_sentence_space
Constructor Details
#initialize(source, file:) ⇒ Parser
Returns a new instance of Parser.
17
18
19
20
21
22
23
|
# File 'lib/ibex/error_messages/parser.rb', line 17
def initialize(source, file:)
@file = file
text = source.dup.force_encoding(Encoding::UTF_8)
fail_at(1, 1, "messages file must be valid UTF-8") unless text.valid_encoding?
@lines = text.lines(chomp: true)
end
|
Instance Method Details
#close_entry(message_lines, opening_index, index, state, status) ⇒ [ Entry, Integer ]
93
94
95
96
97
98
99
|
# File 'lib/ibex/error_messages/parser.rb', line 93
def close_entry(message_lines, opening_index, index, state, status)
message = message_lines.empty? ? nil : message_lines.join("\n")
fail_at(opening_index + 1, 1, "message for state #{state} must not be empty") if message&.strip&.empty?
entry = Entry.new(state: state, status: status, message: message, line: opening_index + 1)
[entry, index + 1]
end
|
#column(line) ⇒ Integer
149
150
151
|
# File 'lib/ibex/error_messages/parser.rb', line 149
def column(line)
(line.index(/\S/) || 0) + 1
end
|
#decode_line(content, line, content_column) ⇒ String
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
# File 'lib/ibex/error_messages/parser.rb', line 116
def decode_line(content, line, content_column)
decoded = +""
index = 0
while index < content.length
character = content[index] || raise(Ibex::Error, "missing message character")
unless character == "\\"
decoded << character
index += 1
next
end
escaped = content[index + 1]
fail_at(line, content_column + index, "trailing backslash in message line") unless escaped
replacement = ESCAPES[escaped]
fail_at(line, content_column + index, "unknown escape \\#{escaped}") unless replacement
decoded << replacement
index += 2
end
decoded
end
|
#fail_at(line, column, message) ⇒ bot
154
155
156
|
# File 'lib/ibex/error_messages/parser.rb', line 154
def fail_at(line, column, message)
raise Ibex::Error, "#{@file}:#{line}:#{column}: #{message}"
end
|
#ignorable?(line) ⇒ Boolean
139
140
141
|
# File 'lib/ibex/error_messages/parser.rb', line 139
def ignorable?(line)
line.strip.empty? || line.lstrip.start_with?("#")
end
|
26
27
28
29
30
31
|
# File 'lib/ibex/error_messages/parser.rb', line 26
def parse
version =
return parse_v2 if version == 2
parse_v1
end
|
#parse_entry(opening_index, state, status) ⇒ [ Entry, Integer ]
77
78
79
80
81
82
83
84
85
86
87
88
89
|
# File 'lib/ibex/error_messages/parser.rb', line 77
def parse_entry(opening_index, state, status)
message_lines = [] index = opening_index + 1
while index < @lines.length
line = @lines.fetch(index)
return close_entry(message_lines, opening_index, index, state, status) if line.strip == "end"
read_entry_line(message_lines, line, index)
index += 1
end
label = status == :active ? "state" : "removed"
fail_at(opening_index + 1, 1, "unterminated #{label} #{state} entry")
end
|
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
# File 'lib/ibex/error_messages/parser.rb', line 36
def parse_v1
entries = [] declarations = {} index = 1
while index < @lines.length
line = @lines.fetch(index)
if ignorable?(line)
index += 1
next
end
match = line.strip.match(/\A(state|removed)\s+([0-9]+)\z/)
fail_at(index + 1, column(line), top_level_expectation) unless match
state_text = match[2] || raise(Ibex::Error, "missing state number")
state = Integer(state_text, 10)
reject_duplicate(declarations, state, index, line)
declarations[state] = index + 1
entry, index = parse_entry(index, state, match[1] == "state" ? :active : :removed)
entries << entry
end
Document.new(version: 1, entries: entries)
end
|
#read_entry_line(message_lines, line, index) ⇒ void
This method returns an undefined value.
102
103
104
105
106
107
108
109
110
111
112
113
|
# File 'lib/ibex/error_messages/parser.rb', line 102
def read_entry_line(message_lines, line, index)
return if ignorable?(line)
unless line.start_with?("|")
fail_at(index + 1, column(line), "expected a `| ` message line, a comment, a blank line, or `end`")
end
content = line.delete_prefix("|")
content = content.delete_prefix(" ")
content_column = line.start_with?("| ") ? 3 : 2
message_lines << decode_line(content, index + 1, content_column)
end
|
#reject_duplicate(declarations, state, index, line) ⇒ void
This method returns an undefined value.
69
70
71
72
73
74
|
# File 'lib/ibex/error_messages/parser.rb', line 69
def reject_duplicate(declarations, state, index, line)
return unless declarations.key?(state)
fail_at(index + 1, column(line),
"duplicate state #{state}; first declared at line #{declarations.fetch(state)}")
end
|
#top_level_expectation ⇒ String
144
145
146
|
# File 'lib/ibex/error_messages/parser.rb', line 144
def top_level_expectation
"expected `state N`, `removed N`, a comment, or a blank line"
end
|
60
61
62
63
64
65
66
|
# File 'lib/ibex/error_messages/parser.rb', line 60
def
= @lines.first&.delete_prefix("\uFEFF")
return 1 if == HEADER_V1
return 2 if == HEADER
fail_at(1, 1, "expected #{HEADER.inspect} or #{HEADER_V1.inspect}")
end
|