Skip to main content

scarf_python/
report.rs

1// =======================================================================
2// report.rs
3// =======================================================================
4//! A wrapper around [`scarf_parser::report::Report`]
5
6use crate::{PreprocessorError, Span};
7use pyo3::prelude::*;
8use scarf_parser::{PreprocessorCache, report::Sources};
9use std::fs;
10use yansi::Color;
11
12const NOTE_COLOR: Color = Color::Fixed(81);
13const NOTE_KIND: scarf_parser::report::ReportKind<'static> =
14    scarf_parser::report::ReportKind::Custom("note", NOTE_COLOR);
15
16/// The type of report being generated, used for coloring output
17#[pyclass(eq, from_py_object, module = "scarf_python")]
18#[derive(Clone, PartialEq, Eq, Hash)]
19pub enum ReportKind {
20    Error(),
21    Warning(),
22    Advice(),
23    Note(),
24}
25
26impl From<ReportKind> for scarf_parser::report::ReportKind<'static> {
27    fn from(value: ReportKind) -> Self {
28        match value {
29            ReportKind::Error() => scarf_parser::report::ReportKind::Error,
30            ReportKind::Warning() => scarf_parser::report::ReportKind::Warning,
31            ReportKind::Advice() => scarf_parser::report::ReportKind::Advice,
32            ReportKind::Note() => NOTE_KIND,
33        }
34    }
35}
36
37/// The base of a [`Report`]
38#[derive(Clone, PartialEq, Eq)]
39struct ReportBase {
40    kind: ReportKind,
41    span: Span,
42    code: String,
43    msg: String,
44}
45
46/// A label in a [`Report`]
47#[derive(Clone, PartialEq, Eq)]
48struct ReportLabel {
49    span: Span,
50    kind: ReportKind,
51    msg: String,
52}
53
54/// A wrapper around [`scarf_parser::report::Report`]
55///
56/// Here, since we can't pass in a cache from Python, we track which
57/// files are added in labels, so that we can re-read them in during
58/// printing
59#[pyclass(eq, from_py_object, module = "scarf_python")]
60#[derive(Clone, PartialEq, Eq)]
61pub struct Report {
62    /// The base of the [`Report`]
63    base: ReportBase,
64    /// The labels in the [`Report`]
65    labels: Vec<ReportLabel>,
66    /// Any additional source files that are needed for printing
67    additional_sources: Vec<(String, String)>,
68}
69
70impl Report {
71    /// Generates the [`scarf_parser::report::Report`]
72    fn report(&self) -> scarf_parser::report::Report {
73        let cache = PreprocessorCache::new();
74        let mut report = scarf_parser::report::Report::new(
75            self.base.kind.clone().into(),
76            self.base.span.to_span_ref(&cache),
77            &self.base.code,
78            &self.base.msg,
79        );
80        for label in &self.labels {
81            report = report.with_label(
82                label.span.to_span_ref(&cache),
83                label.kind.clone().into(),
84                &label.msg,
85            )
86        }
87        report
88    }
89
90    /// Reads in the sources needed to print the [`Report`]
91    fn sources(&self) -> PyResult<impl scarf_parser::report::Cache<String>> {
92        let mut name_content_pairs = self.additional_sources.clone();
93        // Base content
94        if !name_content_pairs
95            .iter()
96            .any(|(file_name, _)| *file_name == self.base.span.file)
97        {
98            let base_content = fs::read_to_string(&self.base.span.file)
99                .map_err(|err| {
100                    pyo3::exceptions::PyIOError::new_err(err.to_string())
101                })?;
102            name_content_pairs
103                .push((self.base.span.file.clone(), base_content));
104        }
105        // Label content
106        for label in &self.labels {
107            if !name_content_pairs
108                .iter()
109                .any(|(file_name, _)| *file_name == label.span.file)
110            {
111                let label_content = fs::read_to_string(&label.span.file)
112                    .map_err(|err| {
113                        pyo3::exceptions::PyIOError::new_err(err.to_string())
114                    })?;
115                name_content_pairs
116                    .push((label.span.file.clone(), label_content));
117            }
118        }
119        Ok(name_content_pairs.sources())
120    }
121}
122
123#[pymethods]
124impl Report {
125    /// Create a new [`Report`] indicating a particular location with a message
126    ///
127    /// This does not label/print the location; to do so, use [`Report::label`]
128    #[new]
129    pub fn new(
130        kind: ReportKind,
131        span: Span,
132        code: String,
133        msg: String,
134    ) -> Self {
135        Self {
136            base: ReportBase {
137                kind: kind.clone(),
138                span,
139                code,
140                msg,
141            },
142            labels: vec![],
143            additional_sources: vec![],
144        }
145    }
146
147    /// Include additional sources to use when printing
148    pub fn include(&mut self, file_name: String, file_content: String) {
149        self.additional_sources.push((file_name, file_content))
150    }
151
152    /// Adds a label to the [`Report`]
153    ///
154    /// A label includes a [`Span`] to highlight, as well as a message to
155    /// attach at that location
156    pub fn label(&mut self, span: Span, kind: ReportKind, msg: String) {
157        self.labels.push(ReportLabel { span, kind, msg })
158    }
159
160    /// Print the report to `stdout`
161    pub fn print(&self) -> PyResult<()> {
162        let report = self.report();
163        report.print(&mut self.sources()?).map_err(|err| {
164            pyo3::exceptions::PyIOError::new_err(err.to_string())
165        })
166    }
167
168    /// Print the report to `stderr`
169    pub fn eprint(&self) -> PyResult<()> {
170        let report = self.report();
171        report.eprint(&mut self.sources()?).map_err(|err| {
172            pyo3::exceptions::PyIOError::new_err(err.to_string())
173        })
174    }
175}
176
177// -----------------------------------------------------------------------
178// VerboseError
179// -----------------------------------------------------------------------
180
181impl crate::VerboseError {
182    /// Generate an error report for the [`VerboseError`]
183    pub(crate) fn report_with_code(&self, code: String) -> PyResult<Report> {
184        let error_span = if self.found.is_none() {
185            let file_len = std::fs::metadata(&self.span.file)
186                .map_err(|err| {
187                    pyo3::exceptions::PyIOError::new_err(err.to_string())
188                })?
189                .len();
190            let byte_span = std::ops::Range {
191                start: file_len as usize,
192                end: file_len as usize,
193            };
194            Span {
195                file: self.span.file.clone(),
196                bytes: byte_span.into(),
197                expanded_from: self.span.expanded_from.clone(),
198                included_from: self.span.included_from.clone(),
199            }
200        } else {
201            self.span.clone()
202        };
203        let mut report = Report::new(
204            ReportKind::Error(),
205            error_span.clone(),
206            code,
207            self.to_string(),
208        );
209        report.label(
210            error_span,
211            ReportKind::Error(),
212            match &self.found {
213                Some(tok) => format!("Didn't expect {}", tok),
214                None => "Didn't expect end of input".to_owned(),
215            },
216        );
217        Ok(report)
218    }
219}
220
221#[pymethods]
222impl crate::VerboseError {
223    /// Generate an error report for the [`VerboseError`]
224    fn report(&self) -> PyResult<Report> {
225        self.report_with_code("P1".to_owned())
226    }
227}
228
229// -----------------------------------------------------------------------
230// PreprocessorError
231// -----------------------------------------------------------------------
232
233#[pymethods]
234impl PreprocessorError {
235    pub fn report(&self) -> PyResult<Report> {
236        let report = match self {
237            PreprocessorError::Endif { endif_span } => {
238                let mut report = Report::new(
239                    ReportKind::Error(),
240                    endif_span.clone(),
241                    "PP1".to_owned(),
242                    "Unexpected `endif".to_owned(),
243                );
244                report.label(
245                    endif_span.clone(),
246                    ReportKind::Error(),
247                    "Unexpected `endif".to_owned(),
248                );
249                report
250            }
251            PreprocessorError::NoEndif {
252                cond_token,
253                cond_token_span,
254            } => {
255                let mut report = Report::new(
256                    ReportKind::Error(),
257                    cond_token_span.clone(),
258                    "PP2".to_owned(),
259                    format!("No matching `endif for {cond_token}"),
260                );
261                report.label(
262                    cond_token_span.clone(),
263                    ReportKind::Error(),
264                    "No matching `endif".to_owned(),
265                );
266                report
267            }
268            PreprocessorError::Elsif { elsif_span } => {
269                let mut report = Report::new(
270                    ReportKind::Error(),
271                    elsif_span.clone(),
272                    "PP3".to_owned(),
273                    "Unexpected `elsif".to_owned(),
274                );
275                report.label(
276                    elsif_span.clone(),
277                    ReportKind::Error(),
278                    "Unexpected `elsif".to_owned(),
279                );
280                report
281            }
282            PreprocessorError::Else { else_span } => {
283                let mut report = Report::new(
284                    ReportKind::Error(),
285                    else_span.clone(),
286                    "PP4".to_owned(),
287                    "Unexpected `else".to_owned(),
288                );
289                report.label(
290                    else_span.clone(),
291                    ReportKind::Error(),
292                    "Unexpected `else".to_owned(),
293                );
294                report
295            }
296            PreprocessorError::EndKeywords { end_keywords_span } => {
297                let mut report = Report::new(
298                    ReportKind::Error(),
299                    end_keywords_span.clone(),
300                    "PP5".to_owned(),
301                    "`end_keywords with no previous `begin_keywords".to_owned(),
302                );
303                report.label(
304                    end_keywords_span.clone(),
305                    ReportKind::Error(),
306                    "No matching `begin_keywords".to_owned(),
307                );
308                report
309            }
310            PreprocessorError::NoEndKeywords {
311                begin_keywords_span,
312            } => {
313                let mut report = Report::new(
314                    ReportKind::Error(),
315                    begin_keywords_span.clone(),
316                    "PP6".to_owned(),
317                    "`begin_keywords with no matching `end_keywords".to_owned(),
318                );
319                report.label(
320                    begin_keywords_span.clone(),
321                    ReportKind::Error(),
322                    "No matching `end_keywords".to_owned(),
323                );
324                report
325            }
326            PreprocessorError::InvalidDefineParameter {
327                other_token,
328                other_span,
329            } => {
330                let mut report = Report::new(
331                    ReportKind::Error(),
332                    other_span.clone(),
333                    "PP7".to_owned(),
334                    format!(
335                        concat!(
336                            "Found {}, expected a preprocessor ",
337                            "macro parameter/identifier"
338                        ),
339                        other_token
340                    ),
341                );
342                report.label(
343                    other_span.clone(),
344                    ReportKind::Error(),
345                    format!("Unexpected {}", other_token),
346                );
347                report
348            }
349            PreprocessorError::InvalidDefineArgument {
350                other_token,
351                other_span,
352            } => {
353                let mut report = Report::new(
354                    ReportKind::Error(),
355                    other_span.clone(),
356                    "PP7".to_owned(),
357                    format!(
358                        concat!(
359                            "Found {}, expected a comma, ), ",
360                            "or a preprocessor macro argument"
361                        ),
362                        other_token
363                    ),
364                );
365                report.label(
366                    other_span.clone(),
367                    ReportKind::Error(),
368                    format!("Unexpected {}", other_token),
369                );
370                report
371            }
372            PreprocessorError::InvalidVersionSpecifier {
373                invalid_version,
374                invalid_version_span,
375            } => {
376                let mut report = Report::new(
377                    ReportKind::Error(),
378                    invalid_version_span.clone(),
379                    "PP8".to_owned(),
380                    match invalid_version {
381                        crate::Token::StringLiteral { text } => {
382                            format!("{} is not a valid version specifier", text)
383                        }
384                        _ => {
385                            format!(
386                                "{} isn't a version specifier",
387                                invalid_version
388                            )
389                        }
390                    },
391                );
392                report.label(
393                    invalid_version_span.clone(),
394                    ReportKind::Error(),
395                    "Invalid version specifier".to_owned(),
396                );
397                report
398            }
399            PreprocessorError::IncompleteDirective { directive_span } => {
400                let mut report = Report::new(
401                    ReportKind::Error(),
402                    directive_span.clone(),
403                    "PP9".to_owned(),
404                    "Incomplete directive".to_owned(),
405                );
406                report.label(
407                    directive_span.clone(),
408                    ReportKind::Error(),
409                    "Expected a complete directive".to_owned(),
410                );
411                report
412            }
413            PreprocessorError::IncompleteDefine {
414                other_token,
415                other_span,
416            } => {
417                let mut report = Report::new(
418                    ReportKind::Error(),
419                    other_span.clone(),
420                    "PP10".to_owned(),
421                    format!(
422                        "Found {}, expected more in the preprocessor definition",
423                        other_token
424                    ),
425                );
426                report.label(
427                    other_span.clone(),
428                    ReportKind::Error(),
429                    "Expected more after".to_owned(),
430                );
431                report
432            }
433            PreprocessorError::UndefinedMacro {
434                undefined_name,
435                undefined_span,
436            } => {
437                let mut report = Report::new(
438                    ReportKind::Error(),
439                    undefined_span.clone(),
440                    "PP11".to_owned(),
441                    format!("{undefined_name} has not been previously defined"),
442                );
443                report.label(
444                    undefined_span.clone(),
445                    ReportKind::Error(),
446                    "Not previously defined".to_owned(),
447                );
448                report
449            }
450            PreprocessorError::RedefinedMacro {
451                macro_name,
452                redef_span,
453                prev_def_span,
454            } => {
455                let mut report = Report::new(
456                    ReportKind::Warning(),
457                    redef_span.clone(),
458                    "PP12".to_owned(),
459                    format!("Redefining {macro_name}"),
460                );
461                report.label(
462                    prev_def_span.clone(),
463                    ReportKind::Note(),
464                    "Previously defined here".to_owned(),
465                );
466                report.label(
467                    redef_span.clone(),
468                    ReportKind::Warning(),
469                    "Redefined here".to_owned(),
470                );
471                report
472            }
473            PreprocessorError::NotPreviouslyDefinedMacro {
474                macro_name,
475                macro_span,
476            } => {
477                let mut report = Report::new(
478                    ReportKind::Warning(),
479                    macro_span.clone(),
480                    "PP13".to_owned(),
481                    format!(
482                        "Undefining {}, which has not been previously defined",
483                        macro_name
484                    ),
485                );
486                report.label(
487                    macro_span.clone(),
488                    ReportKind::Warning(),
489                    "Not previously defined".to_owned(),
490                );
491                report
492            }
493            PreprocessorError::DuplicateMacroParameter {
494                define_name,
495                param_name,
496                dup_span,
497                prev_span,
498            } => {
499                let mut report = Report::new(
500                    ReportKind::Error(),
501                    dup_span.clone(),
502                    "PP14".to_owned(),
503                    format!(
504                        "'{}' was already declared as a macro parameter for {}",
505                        param_name, define_name
506                    ),
507                );
508                report.label(
509                    prev_span.clone(),
510                    ReportKind::Note(),
511                    "Previously declared here".to_owned(),
512                );
513                report.label(
514                    dup_span.clone(),
515                    ReportKind::Error(),
516                    "Duplicate parameter declaration".to_owned(),
517                );
518                report
519            }
520            PreprocessorError::NoDefaultAfterDefault {
521                default_param,
522                default_param_span,
523                non_default_param,
524                non_default_param_span,
525            } => {
526                let mut report = Report::new(
527                ReportKind::Error(),
528                non_default_param_span.clone(),
529                "PP15".to_owned(),
530                "No default specified for argument after one with a default".to_owned(),
531            );
532                report.label(
533                    default_param_span.clone(),
534                    ReportKind::Note(),
535                    format!("{} had a default specified", default_param),
536                );
537                report.label(
538                    non_default_param_span.clone(),
539                    ReportKind::Error(),
540                    format!("No default specified for {}", non_default_param),
541                );
542                report
543            }
544            PreprocessorError::NoMacroArguments {
545                macro_name,
546                define_span,
547                use_span,
548            } => {
549                let mut report = Report::new(
550                    ReportKind::Error(),
551                    use_span.clone(),
552                    "PP16".to_owned(),
553                    format!("Expected arguments when using {macro_name}"),
554                );
555                report.label(
556                    define_span.clone(),
557                    ReportKind::Note(),
558                    "Macro defined here".to_owned(),
559                );
560                report.label(
561                    use_span.clone(),
562                    ReportKind::Error(),
563                    "Expected arguments not present".to_owned(),
564                );
565                report
566            }
567            PreprocessorError::TooManyMacroArguments {
568                macro_name,
569                define_span,
570                use_span,
571                expected,
572                found,
573            } => {
574                let mut report = Report::new(
575                    ReportKind::Error(),
576                    use_span.clone(),
577                    "PP17".to_owned(),
578                    format!(
579                        "{} expected {} arguments, but {} were provided",
580                        macro_name, expected, found
581                    ),
582                );
583                report.label(
584                    define_span.clone(),
585                    ReportKind::Note(),
586                    format!("Macro definition expects {expected} arguments"),
587                );
588                report.label(
589                    use_span.clone(),
590                    ReportKind::Error(),
591                    format!("{found} arguments provided"),
592                );
593                report
594            }
595            PreprocessorError::MissingMacroArgument {
596                define_span,
597                use_span,
598                param_name,
599            } => {
600                let mut report = Report::new(
601                    ReportKind::Error(),
602                    use_span.clone(),
603                    "PP18".to_owned(),
604                    format!(
605                        "'{param_name}' wasn't specified and has no default"
606                    ),
607                );
608                report.label(
609                    define_span.clone(),
610                    ReportKind::Note(),
611                    "Macro defined here".to_owned(),
612                );
613                report.label(
614                    use_span.clone(),
615                    ReportKind::Error(),
616                    "Missing argument".to_owned(),
617                );
618                report
619            }
620            PreprocessorError::InvalidIdentifierFormation {
621                param_name,
622                arg_span,
623            } => {
624                let mut report = Report::new(
625                    ReportKind::Error(),
626                    arg_span.clone(),
627                    "PP19".to_owned(),
628                    format!(
629                        concat!(
630                            "The argument for '{}' cannot be ",
631                            "concatenated into an identifier"
632                        ),
633                        param_name
634                    ),
635                );
636                report.label(
637                    arg_span.clone(),
638                    ReportKind::Error(),
639                    "No valid conversion to identifier".to_owned(),
640                );
641                report
642            }
643            PreprocessorError::InvalidRelativeTimescales { timescale_span } => {
644                let mut report = Report::new(
645                    ReportKind::Error(),
646                    timescale_span.clone(),
647                    "PP20".to_owned(),
648                    "Time precision is larger than the time unit".to_owned(),
649                );
650                report.label(
651                    timescale_span.clone(),
652                    ReportKind::Error(),
653                    "Cannot have delay unit be smaller than precision"
654                        .to_owned(),
655                );
656                report
657            }
658            PreprocessorError::IncompleteMacroWithToken {
659                error_token,
660                error_span,
661            } => {
662                let mut report = Report::new(
663                    ReportKind::Error(),
664                    error_span.clone(),
665                    "PP21".to_owned(),
666                    format!(
667                        "Usage of {} resulted in an incomplete macro",
668                        error_token
669                    ),
670                );
671                report.label(
672                    error_span.clone(),
673                    ReportKind::Error(),
674                    concat!(
675                        "Expected a complete macro argument or ",
676                        "escaped newline after"
677                    )
678                    .to_owned(),
679                );
680                report
681            }
682            PreprocessorError::Include {
683                include_path,
684                include_path_span,
685                read_err,
686            } => {
687                let mut report = Report::new(
688                    ReportKind::Error(),
689                    include_path_span.clone(),
690                    "PP22".to_owned(),
691                    format!("Error when reading {}", include_path),
692                );
693                report.label(
694                    include_path_span.clone(),
695                    ReportKind::Error(),
696                    read_err.to_string(),
697                );
698                report
699            }
700            PreprocessorError::IncludeDepth { include_span } => {
701                let mut report = Report::new(
702                    ReportKind::Error(),
703                    include_span.clone(),
704                    "PP23".to_owned(),
705                    "Max include depth reached".to_owned(),
706                );
707                report.label(
708                    include_span.clone(),
709                    ReportKind::Error(),
710                    "Check for an `include loop".to_owned(),
711                );
712                report
713            }
714            PreprocessorError::VerboseError { err } => {
715                return err.report_with_code("PP24".to_owned());
716            }
717        };
718        Ok(report)
719    }
720}