Skip to main content

scarf_parser/
lib.rs

1// =======================================================================
2// lib.rs
3// =======================================================================
4//! A SystemVerilog preprocessor and parser
5//!
6//! `scarf-parser` provides capabilities for transformting a SystemVerilog
7//! source file into a CST compliant with IEEE 1800-2023, with an
8//! emphasis on informative error messages. It can be used as the
9//! front-end for other tools looking to interpret SystemVerilog designs.
10//!
11//! ## Features
12//!
13//!  - `lossless`: Equivalent to the `lossless` feature for [`scarf_syntax`].
14//!    Produces a CST with room for non-trivia nodes, but does not actually
15//!    parse any from provided sources
16//!  - `parse_lossless`: Extends `lossless` to parse non-trivia tokens.
17//!    Due to their arbitrary position in source files, this adds a
18//!    measurable performance decrease, and should only be used if
19//!    newlines/comments are needed.
20
21mod error;
22pub mod lexer;
23pub mod parser;
24pub mod preprocessor;
25use ariadne::ReportBuilder;
26use ariadne::{Color, Label, ReportKind};
27pub use ariadne::{Report, Source, sources};
28pub use error::*;
29use lexer::*;
30pub use lexer::{LexedSource, Token, lex};
31pub use parser::parse;
32use parser::*;
33pub use preprocessor::*;
34use winnow::Parser;
35use winnow::stream::TokenSlice;
36#[cfg(test)]
37pub mod test;
38pub use scarf_syntax::Span;
39#[cfg(test)]
40pub use test::*;
41
42/// A string and its associated [`Span`] in the source files
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct SpannedString<'a>(pub &'a str, pub Span<'a>);
45
46/// A token and its location in the source code
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct SpannedToken<'s>(pub Token<'s>, pub Span<'s>);
49impl<'s> PartialEq<Token<'s>> for SpannedToken<'s> {
50    fn eq(&self, other: &Token) -> bool {
51        self.0 == *other
52    }
53}
54impl<'s> From<(Token<'s>, Span<'s>)> for SpannedToken<'s> {
55    fn from(item: (Token<'s>, Span<'s>)) -> Self {
56        (item.0, item.1).into()
57    }
58}
59
60fn get_expansion_string(expansion_depth: usize, is_first: bool) -> String {
61    if expansion_depth == 0 {
62        "Original token".to_string()
63    } else if (expansion_depth == 1) && is_first {
64        "Expanded here".to_string()
65    } else {
66        let suffix = match expansion_depth % 10 {
67            1 => "st",
68            2 => "nd",
69            3 => "rd",
70            _ => "th",
71        };
72        format!("Expanded here {}{}", expansion_depth, suffix)
73    }
74}
75
76pub(crate) fn kind_color<'s>(kind: &ariadne::ReportKind<'s>) -> Color {
77    match kind {
78        ReportKind::Error => Color::Red,
79        ReportKind::Warning => Color::Yellow,
80        ReportKind::Advice => Color::Fixed(147),
81        ReportKind::Custom(_, color) => color.clone(),
82    }
83}
84
85pub(crate) fn attach_span_label<'s, M>(
86    span: &Span<'s>,
87    color: ariadne::Color,
88    msg: M,
89    mut report: ReportBuilder<'s, (String, std::ops::Range<usize>)>,
90) -> ReportBuilder<'s, (String, std::ops::Range<usize>)>
91where
92    M: ToString,
93{
94    let mut curr_span: &Span<'s> = span;
95    let mut expansion_depth: usize = curr_span.expansion_depth();
96    let mut expanded = false;
97    loop {
98        if let Some(expanded_span) = curr_span.expanded_from {
99            report = report.with_label(
100                Label::new((
101                    curr_span.file.to_string(),
102                    curr_span.bytes.clone(),
103                ))
104                .with_message(get_expansion_string(
105                    expansion_depth,
106                    expanded == false,
107                ))
108                .with_color(Color::BrightGreen),
109            );
110            curr_span = expanded_span;
111            expansion_depth -= 1;
112            expanded = true;
113        } else {
114            break;
115        }
116    }
117    if expanded {
118        // Also label expansion in original spot
119        report = report.with_label(
120            Label::new((curr_span.file.to_string(), curr_span.bytes.clone()))
121                .with_message(get_expansion_string(expansion_depth, false))
122                .with_color(Color::BrightGreen),
123        );
124    }
125    curr_span = &span;
126    report = report.with_label(
127        Label::new((curr_span.file.to_string(), curr_span.bytes.clone()))
128            .with_message(msg)
129            .with_color(color)
130            .with_priority(1),
131    );
132    let mut note = "".to_string();
133    let mut note_pad = "".to_string();
134    let total_inclusion_depth = curr_span.inclusion_depth();
135    let mut curr_inclusion_depth = 0;
136    // Only display a max of 7 includes
137    loop {
138        if (curr_inclusion_depth == 3) & (total_inclusion_depth > 7) {
139            for _ in 7..=total_inclusion_depth {
140                curr_span = curr_span.included_from.unwrap();
141            }
142            note = format!("{}\n{}╰- ...", note, note_pad);
143            note_pad += "  ";
144        }
145        if let Some(included_span) = curr_span.included_from {
146            curr_inclusion_depth += 1;
147            curr_span = included_span;
148            if note.is_empty() {
149                note = format!("Included from {}", curr_span.file);
150            } else {
151                note = format!(
152                    "{}\n{}╰-Included from {}",
153                    note, note_pad, curr_span.file
154                );
155                note_pad += "  ";
156            }
157        } else {
158            break;
159        }
160    }
161    debug_assert!(curr_span.included_from.is_none());
162    if !note.is_empty() {
163        report = report.with_note(note);
164    }
165    report
166}