1use crate::{ligkern::InfiniteLoopError, NextLargerProgramWarning};
4
5#[derive(PartialEq, Eq, Debug, Clone)]
7pub struct ParseWarning {
8 pub span: std::ops::Range<usize>,
12 pub knuth_pltotf_offset: Option<usize>,
29 pub kind: ParseWarningKind,
31}
32
33#[derive(PartialEq, Eq, Debug, Clone)]
35pub enum ParseWarningKind {
36 UnbalancedOpeningParenthesis {
38 opening_parenthesis_span: std::ops::Range<usize>,
39 },
40 UnexpectedClosingParenthesis,
42 JunkInsidePropertyList { junk: String },
45 JunkAfterPropertyValue { junk: String },
48 ParameterNumberIsTooBig,
54 ParameterNumberIsZero,
56 SmallIntegerIsTooBig { radix: u8 },
58 IntegerIsTooBig { radix: u8 },
60 DecimalNumberIsTooBig,
62 InvalidPropertyName {
64 provided_name: String,
66 allowed_property_names: &'static [&'static str],
68 },
69 InvalidPrefixForInteger { prefix: Option<char> },
71 InvalidPrefixForSmallInteger,
73 InvalidPrefixForDecimalNumber,
75 InvalidOctalDigit { invalid_digit: char },
77 EmptyCharacterValue,
79 InvalidFaceCode,
81 InvalidBoolean,
83 HeaderIndexIsTooSmall,
85 LigTableIsTooBig,
87 CycleInLigKernProgram(InfiniteLoopError),
89 CycleInNextLargerProgram(NextLargerProgramWarning),
91 NotReallySevenBitSafe,
93 DesignSizeIsTooSmall,
95 NonVisibleAsciiCharacter { character: char },
97}
98
99struct Data {
100 rule: String,
101 problem: String,
102 action: &'static str,
103 pltotf_message: String,
104 pltotf_section: (u8, u8),
105}
106
107impl ParseWarning {
108 pub fn pltotf_message(&self, pl_source: &str) -> String {
109 let data = self.kind.data();
110 match self.knuth_pltotf_offset {
111 None => data.pltotf_message,
112 Some(pltotf_point) => {
113 add_pltotf_error_context(pl_source, data.pltotf_message, pltotf_point)
114 }
115 }
116 }
117}
118
119impl ParseWarningKind {
120 fn data(&self) -> Data {
121 use ParseWarningKind::*;
122 match self.clone() {
123 EmptyCharacterValue => Data {
124 rule: "an ASCII character must be specified after a \"C\"".into(),
125 problem: "empty".into(),
126 action: "",
127 pltotf_message: "\"C\" value must be standard ASCII and not a paren".into(),
128 pltotf_section: (52,1),
129 },
130 InvalidBoolean => Data {
131 rule: "boolean values must start with T, t, F or f.".into(),
132 problem: "invalid boolean value".into(),
133 action: "this property list element will be skipped",
134 pltotf_message: "The flag value should be \"TRUE\" or \"FALSE\"".into(),
135 pltotf_section: (90, 1),
136 },
137 InvalidOctalDigit{ invalid_digit } => Data {
138 rule: "octal numbers can only contain the characters 0 through 7 inclusive".into(),
139 problem: format!["octal number contains the non-octal character {invalid_digit}"],
140 action: "the octal number will be truncated to before the problematic character",
141 pltotf_message: "Illegal digit".into(),
142 pltotf_section: (60, 2),
143 },
144 NonVisibleAsciiCharacter{ character: c } => Data {
145 rule: "property list files can only contain newlines and printable ASCII characters (space through tilde inclusive)".into(),
146 problem: format!["invalid character {} (U+{:04X})", c, c as u32] ,
147 action: "this character will be ignored",
148 pltotf_message: if (c as usize) < 128 { "Illegal character in the file".into()} else {"".into()},
149 pltotf_section: (32, 1),
150 },
151 UnbalancedOpeningParenthesis { opening_parenthesis_span: _ } => Data {
152 rule: "all opening parentheses must be balanced by a subsequent closing parenthesis".into(),
153 problem: "opening parenthesis is never closed".into(),
154 action: "an additional closing parenthesis will be appended to the file",
155 pltotf_message: "File ended unexpectedly: No closing \")\"".into(),
156 pltotf_section: (33,1),
157 },
158 JunkInsidePropertyList { junk } => Data {
159 rule: "".into(),
160 problem: format!["junk string '{junk}' in the middle of a property list"],
161 action: "this junk will be ignored",
162 pltotf_message: "There's junk here that is not in parentheses".into(),
163 pltotf_section: (83,1),
164 },
165 JunkAfterPropertyValue { junk } => Data {
166 rule: "".into(),
167 problem: format!["junk string '{junk}' after a property list value"],
168 action: "this junk will be ignored",
169 pltotf_message: "Junk after property value will be ignored".into(),
170 pltotf_section: (35,1),
171 },
172 UnexpectedClosingParenthesis => Data {
173 rule: "all closing parentheses must be balanced by a preceding opening parenthesis".into(),
174 problem: "closing parenthesis was never opened".into(),
175 action: "the closing parenthesis will be ignored",
176 pltotf_message: "Extra right parenthesis".into(),
177 pltotf_section: (82, 1),
178 },
179 InvalidPrefixForSmallInteger => Data {
180 rule: "8-bit integers must be prefixed by C, D, F, H or O".into(),
181 problem: "invalid prefix for an 8-bit integer".into(),
182 action: "0 will be used instead",
183 pltotf_message: "You need \"C\" or \"D\" or \"O\" or \"H\" or \"F\" here".into(),
184 pltotf_section: (51,1),
185 },
186 InvalidPrefixForInteger{ prefix: _ } => Data {
187 rule: "32-bit integers must be prefixed by H or O".into(),
188 problem: "invalid prefix for an 32-bit integer".into(),
189 action: "0 will be used instead",
190 pltotf_message: "An octal (\"O\") or hex (\"H\") value is needed here".into(),
191 pltotf_section: (59,1),
192 },
193 InvalidPropertyName {
194 provided_name,
195 allowed_property_names,
196 } => {
197 let allowed_property_names = {
198 let mut v: Vec<&'static str> = allowed_property_names.to_vec();
199 v.sort();
200 v
201 };
202 Data {
203 rule: format![
204 "the following property names are allowed here: {}",
205 allowed_property_names.join(", ")
206 ],
207 problem: format!("the property name {provided_name} is not allowed here"),
208 action: "this element will be ignored",
209 pltotf_message: "Sorry, I don't know that property name".into(),
210 pltotf_section: (49, 1),
211 }
212 }
213 CycleInNextLargerProgram(warning) => {
214 let (rule, problem, action) = match warning {
215 NextLargerProgramWarning::NonExistentCharacter { original, next_larger } => (
216 "next larger rules must reference characters defined in the file",
217 format!["the next larger rule for character {original} references an undefined character {next_larger}"],
218 "the element flagged here will be ignored",
219 ),
220 NextLargerProgramWarning::InfiniteLoop{ original, next_larger } => (
221 "the next larger rules cannot create cycles (e.g., X larger than Y and Y larger than X)",
222 format!("setting {} as the next larger character for {} creates a cycle", next_larger, original),
223 "the element flagged here will be skipped in order to break the cycle",
224 ),
225 };
226 Data {
227 rule: rule.into(),
228 problem,
229 action,
230 pltotf_message: warning.pltotf_message(),
231 pltotf_section: (warning.pltotf_section(), 1),
232 }
233 }
234 ParameterNumberIsZero => Data {
235 rule: "parameter indices must be integers in the range [1,254] inclusive".into(),
236 problem: "0 is not a valid parameter index".into(),
237 action: "this parameter element will be ignored",
238 pltotf_message: "PARAMETER index must not be zero".into(),
239 pltotf_section: (93, 1),
240 },
241 ParameterNumberIsTooBig => Data {
242 rule: "parameter indices must be integers in the range [1,254] inclusive".into(),
243 problem: "this parameter index is too big".into(),
244 action: "this parameter element will be ignored",
245 pltotf_message: "This PARAMETER index is too big for my present table size".into(),
246 pltotf_section: (93, 2),
247 },
248 SmallIntegerIsTooBig { radix } => Data {
249 rule: format!["8-bit numbers can be at most 2^8 = {} = 0x{:x} = 0o{:o}", u8::MAX, u8::MAX, u8::MAX],
250 problem: "this number is too big".into(),
251 action: "0 will be used instead",
252 pltotf_message: {
253 let max = match radix {
254 8 => format!["'{:o}", u8::MAX],
255 10 => format!["{}", u8::MAX],
256 _ => format!["\"{:X}", u8::MAX],
257 };
258 format!["This value shouldn't exceed {max}"]
259 },
260 pltotf_section: match radix {
261 8 => (54, 1),
262 10 => (53, 1),
263 _ => (55, 1),
264 },
265 },
266 IntegerIsTooBig { radix, } => Data {
267 rule: format!["32-bit numbers can be at most 2^32 = {} = 0x{:x} = 0o{:o}", u32::MAX, u32::MAX, u32::MAX],
268 problem: format!["this {} number is too big", if radix == 8 {
269 "octal"
270 } else {
271 "hexadecimal"
272 }],
273 action: "0 will be used instead",
274 pltotf_message: if radix == 8 {
275 format!["Sorry, the maximum octal value is O {:o}", u32::MAX]
276 } else {
277 format!["Sorry, the maximum hex value is H {:X}", u32::MAX]
278 },
279 pltotf_section: (60, 1),
280 },
281 InvalidFaceCode => Data{
282 rule: "face codes must satisfy the pattern [BLM][IR][CER]".into(),
283 problem: "invalid face code".into(),
284 action: "the face code MRR will be used instead",
285 pltotf_message: "Illegal face code, I changed it to MRR".into(),
286 pltotf_section: (56, 1),
287 },
288 CycleInLigKernProgram(err) => Data {
289 rule: "ligature programs cannot contain cycles".into(),
290 problem: format!["this ligature rule for the pair ({},{}) creates a cycle",
291 match err.starting_pair.0 {
292 None => "boundary".to_string(),
293 Some(c) => format!["{}", c],
294 },
295 err.starting_pair.1,
296 ],
297 action: "all ligatures will be deleted",
298 pltotf_message: format![
299 "Infinite ligature loop starting with {} and '{:o}!\nAll ligatures will be cleared.",
300 match err.starting_pair.0 {
301 None => "boundary".to_string(),
302 Some(c) => format!["'{:o}", c.0],
303 },
304 err.starting_pair.1.0,
305 ],
306 pltotf_section: (125, 1),
307 },
308 InvalidPrefixForDecimalNumber => Data{
309 rule: "decimal numbers must be prefixed by D or R".into(),
310 problem: "invalid prefix for a decimal number".into(),
311 action: "0 will be used instead",
312 pltotf_message: "An \"R\" or \"D\" value is needed here".into(),
313 pltotf_section: (62,1),
314 },
315 HeaderIndexIsTooSmall => Data {
316 rule: "header indices must be at least 18".into(),
317 problem: "header index is too small".into(),
318 action: "this property list element will be ignored",
319 pltotf_message: "HEADER indices should be 18 or more".into(),
320 pltotf_section: (91,1),
321 },
322 DesignSizeIsTooSmall => Data {
323 rule: "the design size must be at least 1".into(),
324 problem: "the design size is too small".into(),
325 action: "this property list element will be ignored",
326 pltotf_message: "The design size must be at least 1".into(),
327 pltotf_section: (88, 1),
328 },
329 _ => todo!("unhandled {self:?}"),
330 }
331 }
332}
333
334impl ParseWarning {
335 #[cfg(feature = "ariadne")]
336 pub fn ariadne_report<'a>(
337 &self,
338 file_name: &'a str,
339 ) -> ariadne::Report<'a, (&'a str, std::ops::Range<usize>)> {
340 use ariadne::*;
341 let light_blue = Color::Fixed(81);
342
343 let data = self.kind.data();
344 let mut builder = Report::build(ReportKind::Error, (file_name, self.span.clone()))
345 .with_code(format![
346 "{}.{}",
347 data.pltotf_section.0, data.pltotf_section.1,
348 ])
349 .with_message(&data.problem)
350 .with_label(
351 Label::new((file_name, self.span.clone()))
352 .with_message(&data.problem)
353 .with_color(light_blue),
354 )
355 .with_note(data.action);
356 if !data.rule.is_empty() {
357 builder = builder.with_help(data.rule);
358 }
359 builder.finish()
360 }
361}
362
363fn add_pltotf_error_context(pl_source: &str, error_message: String, error_point: usize) -> String {
364 let mut line_index = 0;
365 let mut line_offset = 0;
366 let mut total_chars = 0;
367 for (i, c) in super::Chars::new(pl_source).enumerate() {
368 total_chars += 1;
369 if error_point == i {
370 break;
371 }
372 line_offset += c.len_utf8();
373 if c == '\n' {
374 line_index += 1;
375 line_offset = 0;
376 }
377 }
378 if error_point == total_chars {
379 let num_lines = line_index + 1;
381 return format!("{error_message} (line {num_lines}).\n...) \n ...",);
382 }
383 let line = pl_source
384 .lines()
385 .nth(line_index)
386 .expect("we know there are line_index+1 lines in the file");
387 let start = &line[..line_offset];
388 let end = &line[line_offset..];
389 let line_number = line_index + 1;
390 format!(
391 "{error_message} (line {line_number}).\n{start} \n{}{end} ",
392 " ".repeat(start.len())
393 )
394}