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