Skip to main content

scarf_python/
lib.rs

1// =======================================================================
2// lib.rs
3// =======================================================================
4//! Python bindings for the `scarf` SystemVerilog tools
5//!
6//! When crossing the FFI boundary, Rust's borrow checker can no longer
7//! provide lifetime guarantees; as such, many data structures must be
8//! cloned, and have associated [`From`]/[`Into`] implementations for
9//! their reference-based Rust counterparts. This quickly becomes the
10//! dominant factor in runtime; if speed/space usage becomes a concern,
11//! native Rust applications should be considered instead.
12
13mod define;
14mod error;
15mod node;
16mod report;
17mod token;
18use std::path::PathBuf;
19
20pub use define::*;
21pub use error::*;
22pub use node::*;
23use pyo3::prelude::*;
24pub use report::*;
25use scarf_parser::{LexedSource, PreprocessorCache};
26pub use token::*;
27
28/// The top-level Python module
29#[pymodule]
30pub mod scarf_python {
31    #[pymodule_export]
32    pub use super::lex;
33    #[pymodule_export]
34    pub use super::{
35        Bytes, Expectation, Node, NodeIterator, Report, ReportKind, Span,
36        SpannedToken, Token, VerboseError,
37    };
38    #[pymodule_export]
39    pub use super::{Define, define_empty, define_text};
40    #[pymodule_export]
41    pub use super::{ParserResult, parse, parse_from_preprocess};
42    #[pymodule_export]
43    pub use super::{
44        PreprocessorError, PreprocessorResult, preprocess, preprocess_from_lex,
45    };
46}
47
48// -----------------------------------------------------------------------
49// Lexing
50// -----------------------------------------------------------------------
51
52/// Separate a source file into syntactic tokens
53#[pyfunction]
54pub fn lex(src: String, file_name: String) -> Vec<SpannedToken> {
55    scarf_parser::lex(&src, &file_name)
56        .tokens()
57        .map(|rust_spanned_token| rust_spanned_token.into())
58        .collect()
59}
60
61// -----------------------------------------------------------------------
62// Preprocessing
63// -----------------------------------------------------------------------
64
65/// The result of preprocessing a SystemVerilog source
66#[pyclass(eq, from_py_object, module = "scarf_python")]
67#[derive(Clone, PartialEq, Eq)]
68pub enum PreprocessorResult {
69    Ok { tokens: Vec<SpannedToken> },
70    Err { errors: Vec<PreprocessorError> },
71}
72
73/// Same as [`preprocess`], but operates on the output of [`lex`]
74///
75/// Comparitively, this incurs overhead from copying data between
76/// Rust and Python's ownership models. Only use if you need to
77/// modify the output of [`lex`] before preprocessing
78#[pyfunction]
79pub fn preprocess_from_lex(
80    tokens: Vec<SpannedToken>,
81    include_paths: Vec<PathBuf>,
82    defines: Vec<crate::Define>,
83) -> PreprocessorResult {
84    let cache = PreprocessorCache::new();
85    let rust_tokens = tokens
86        .iter()
87        .map(|python_token| python_token.to_rust(&cache));
88    let mut state = scarf_parser::PreprocessorState::new(
89        include_paths
90            .iter()
91            .map(|pathbuf| pathbuf.as_path())
92            .collect(),
93        defines
94            .iter()
95            .map(|python_define| python_define.to_rust(&cache))
96            .collect(),
97    );
98    match scarf_parser::preprocess(rust_tokens, &mut state, &cache) {
99        Ok(tokens) => PreprocessorResult::Ok {
100            tokens: tokens
101                .into_iter()
102                .map(|rust_token| rust_token.into())
103                .collect(),
104        },
105        Err(()) => PreprocessorResult::Err {
106            errors: state.errors.into_iter().map(|err| err.into()).collect(),
107        },
108    }
109}
110
111/// Preprocess a token stream, elaborating compiler directives
112#[pyfunction]
113pub fn preprocess(
114    src: String,
115    file_name: String,
116    include_paths: Vec<PathBuf>,
117    defines: Vec<crate::Define>,
118) -> PreprocessorResult {
119    let cache = PreprocessorCache::new();
120    let tokens = scarf_parser::lex(&src, &file_name).tokens();
121    let mut state = scarf_parser::PreprocessorState::new(
122        include_paths
123            .iter()
124            .map(|pathbuf| pathbuf.as_path())
125            .collect(),
126        defines
127            .iter()
128            .map(|python_define| python_define.to_rust(&cache))
129            .collect(),
130    );
131    match scarf_parser::preprocess(tokens, &mut state, &cache) {
132        Ok(tokens) => PreprocessorResult::Ok {
133            tokens: tokens
134                .into_iter()
135                .map(|rust_token| rust_token.into())
136                .collect(),
137        },
138        Err(()) => PreprocessorResult::Err {
139            errors: state.errors.into_iter().map(|err| err.into()).collect(),
140        },
141    }
142}
143
144// -----------------------------------------------------------------------
145// Parsing
146// -----------------------------------------------------------------------
147
148/// The result of parsing a SystemVerilog source
149#[pyclass(eq, from_py_object, module = "scarf_python")]
150#[derive(Clone, PartialEq, Eq)]
151pub enum ParserResult {
152    Ok { root: Node },
153    ParserErr { error: VerboseError },
154    PreprocessorErr { errors: Vec<PreprocessorError> },
155}
156
157/// Same as [`parse`], but operates on the output of [`preprocess`]
158///
159/// Comparitively, this incurs overhead from copying data between
160/// Rust and Python's ownership models. Only use if you need to
161/// modify the output of [`preprocess`] before parsing
162#[pyfunction]
163pub fn parse_from_preprocess(tokens: Vec<SpannedToken>) -> ParserResult {
164    let cache = PreprocessorCache::new();
165    let rust_tokens = tokens
166        .iter()
167        .map(|python_token| python_token.to_rust(&cache))
168        .collect::<Vec<_>>();
169    match scarf_parser::parse(&rust_tokens) {
170        Ok(result) => {
171            let node: scarf_syntax::Node<'_, '_> = (&result).into();
172            ParserResult::Ok { root: node.into() }
173        }
174        Err(err) => ParserResult::ParserErr { error: err.into() },
175    }
176}
177
178/// Parse the token stream into a concrete syntax tree
179#[pyfunction]
180pub fn parse(
181    src: String,
182    file_name: String,
183    include_paths: Vec<PathBuf>,
184    defines: Vec<crate::Define>,
185) -> ParserResult {
186    let cache = PreprocessorCache::new();
187    let tokens = scarf_parser::lex(&src, &file_name).tokens();
188    let mut state = scarf_parser::PreprocessorState::new(
189        include_paths
190            .iter()
191            .map(|pathbuf| pathbuf.as_path())
192            .collect(),
193        defines
194            .iter()
195            .map(|python_define| python_define.to_rust(&cache))
196            .collect(),
197    );
198    let tokens = match scarf_parser::preprocess(tokens, &mut state, &cache) {
199        Ok(tokens) => tokens,
200        Err(()) => {
201            return ParserResult::PreprocessorErr {
202                errors: state
203                    .errors
204                    .into_iter()
205                    .map(|err| err.into())
206                    .collect(),
207            };
208        }
209    };
210    match scarf_parser::parse(&tokens) {
211        Ok(result) => {
212            let node: scarf_syntax::Node<'_, '_> = (&result).into();
213            ParserResult::Ok { root: node.into() }
214        }
215        Err(err) => ParserResult::ParserErr { error: err.into() },
216    }
217}