kcl_lib/parsing/
mod.rs

1use crate::{
2    errors::{CompilationError, KclError, KclErrorDetails},
3    parsing::{
4        ast::types::{Node, Program},
5        token::TokenStream,
6    },
7    source_range::SourceRange,
8    ModuleId,
9};
10
11pub(crate) mod ast;
12mod math;
13pub(crate) mod parser;
14pub(crate) mod token;
15
16pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
17pub const PIPE_OPERATOR: &str = "|>";
18
19// `?` like behavior for `Result`s to return a ParseResult if there is an error.
20macro_rules! pr_try {
21    ($e: expr) => {
22        match $e {
23            Ok(a) => a,
24            Err(e) => return e.into(),
25        }
26    };
27}
28
29#[cfg(test)]
30/// Parse the given KCL code into an AST.  This is the top-level.
31pub fn top_level_parse(code: &str) -> ParseResult {
32    let module_id = ModuleId::default();
33    parse_str(code, module_id)
34}
35
36/// Parse the given KCL code into an AST.
37pub fn parse_str(code: &str, module_id: ModuleId) -> ParseResult {
38    let tokens = pr_try!(crate::parsing::token::lex(code, module_id));
39    parse_tokens(tokens)
40}
41
42/// Parse the supplied tokens into an AST.
43pub fn parse_tokens(mut tokens: TokenStream) -> ParseResult {
44    let unknown_tokens = tokens.remove_unknown();
45
46    if !unknown_tokens.is_empty() {
47        let source_ranges = unknown_tokens.iter().map(SourceRange::from).collect();
48        let token_list = unknown_tokens.iter().map(|t| t.value.as_str()).collect::<Vec<_>>();
49        let message = if token_list.len() == 1 {
50            format!("found unknown token '{}'", token_list[0])
51        } else {
52            format!("found unknown tokens [{}]", token_list.join(", "))
53        };
54        return KclError::new_lexical(KclErrorDetails::new(message, source_ranges)).into();
55    }
56
57    // Important, to not call this before the unknown tokens check.
58    if tokens.is_empty() {
59        // Empty file should just do nothing.
60        return Node::<Program>::default().into();
61    }
62
63    // Check all the tokens are whitespace.
64    if tokens.iter().all(|t| t.token_type.is_whitespace()) {
65        return Node::<Program>::default().into();
66    }
67
68    parser::run_parser(tokens.as_slice())
69}
70
71/// Result of parsing.
72///
73/// Will be a KclError if there was a lexing error or some unexpected error during parsing.
74///   TODO - lexing errors should be included with the parse errors.
75/// Will be Ok otherwise, including if there were parsing errors. Any errors or warnings will
76/// be in the ParseContext. If an AST was produced, then that will be in the Option.
77///
78/// Invariants:
79/// - if there are no errors, then the Option will be Some
80/// - if the Option is None, then there will be at least one error in the ParseContext.
81#[derive(Debug, Clone)]
82pub(crate) struct ParseResult(pub Result<(Option<Node<Program>>, Vec<CompilationError>), KclError>);
83
84impl ParseResult {
85    #[cfg(test)]
86    #[track_caller]
87    pub fn unwrap(self) -> Node<Program> {
88        if self.0.is_err() || self.0.as_ref().unwrap().0.is_none() {
89            eprint!("{self:#?}");
90        }
91        self.0.unwrap().0.unwrap()
92    }
93
94    #[cfg(test)]
95    pub fn is_ok(&self) -> bool {
96        match &self.0 {
97            Ok((p, errs)) => p.is_some() && !errs.iter().any(|e| e.severity.is_err()),
98            Err(_) => false,
99        }
100    }
101
102    #[cfg(test)]
103    #[track_caller]
104    pub fn unwrap_errs(&self) -> impl Iterator<Item = &CompilationError> {
105        self.0.as_ref().unwrap().1.iter().filter(|e| e.severity.is_err())
106    }
107
108    /// Treat parsing errors as an Error.
109    pub fn parse_errs_as_err(self) -> Result<Node<Program>, KclError> {
110        let (p, errs) = self.0?;
111
112        if let Some(err) = errs.iter().find(|e| e.severity.is_err()) {
113            return Err(KclError::new_syntax(err.clone().into()));
114        }
115        match p {
116            Some(p) => Ok(p),
117            None => Err(KclError::internal("Unknown parsing error".to_owned())),
118        }
119    }
120}
121
122impl From<Result<(Option<Node<Program>>, Vec<CompilationError>), KclError>> for ParseResult {
123    fn from(r: Result<(Option<Node<Program>>, Vec<CompilationError>), KclError>) -> ParseResult {
124        ParseResult(r)
125    }
126}
127
128impl From<(Option<Node<Program>>, Vec<CompilationError>)> for ParseResult {
129    fn from(p: (Option<Node<Program>>, Vec<CompilationError>)) -> ParseResult {
130        ParseResult(Ok(p))
131    }
132}
133
134impl From<Node<Program>> for ParseResult {
135    fn from(p: Node<Program>) -> ParseResult {
136        ParseResult(Ok((Some(p), vec![])))
137    }
138}
139
140impl From<KclError> for ParseResult {
141    fn from(e: KclError) -> ParseResult {
142        ParseResult(Err(e))
143    }
144}
145
146const STR_DEPRECATIONS: [(&str, &str); 16] = [
147    ("XY", "XY"),
148    ("XZ", "XZ"),
149    ("YZ", "YZ"),
150    ("-XY", "-XY"),
151    ("-XZ", "-XZ"),
152    ("-YZ", "-YZ"),
153    ("xy", "XY"),
154    ("xz", "XZ"),
155    ("yz", "YZ"),
156    ("-xy", "-XY"),
157    ("-xz", "-XZ"),
158    ("-yz", "-YZ"),
159    ("START", "START"),
160    ("start", "START"),
161    ("END", "END"),
162    ("end", "END"),
163];
164const FN_DEPRECATIONS: [(&str, &str); 3] = [("pi", "PI"), ("e", "E"), ("tau", "TAU")];
165const CONST_DEPRECATIONS: [(&str, &str); 4] = [
166    ("ZERO", "turns::ZERO"),
167    ("QUARTER_TURN", "turns::QUARTER_TURN"),
168    ("HALF_TURN", "turns::HALF_TURN"),
169    ("THREE_QUARTER_TURN", "turns::THREE_QUARTER_TURN"),
170];
171
172#[derive(Clone, Copy)]
173pub enum DeprecationKind {
174    String,
175    Function,
176    Const,
177}
178
179pub fn deprecation(s: &str, kind: DeprecationKind) -> Option<&'static str> {
180    match kind {
181        DeprecationKind::String => STR_DEPRECATIONS.iter().find_map(|(a, b)| (*a == s).then_some(*b)),
182        DeprecationKind::Function => FN_DEPRECATIONS.iter().find_map(|(a, b)| (*a == s).then_some(*b)),
183        DeprecationKind::Const => CONST_DEPRECATIONS.iter().find_map(|(a, b)| (*a == s).then_some(*b)),
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    macro_rules! parse_and_lex {
190        ($func_name:ident, $test_kcl_program:expr) => {
191            #[test]
192            fn $func_name() {
193                let _ = crate::parsing::top_level_parse($test_kcl_program);
194            }
195        };
196    }
197
198    parse_and_lex!(crash_eof_1, "{\"ގގ\0\0\0\"\".");
199    parse_and_lex!(crash_eof_2, "(/=e\"\u{616}ݝ\"\"");
200}