use crate::ModuleId;
use crate::SourceRange;
use crate::errors::CompilationIssue;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::parsing::ast::types::Node;
use crate::parsing::ast::types::Program;
use crate::parsing::token::TokenStream;
pub(crate) mod ast;
mod math;
pub(crate) mod parser;
pub(crate) mod token;
pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
pub const PIPE_OPERATOR: &str = "|>";
macro_rules! pr_try {
($e: expr_2021) => {
match $e {
Ok(a) => a,
Err(e) => return e.into(),
}
};
}
#[cfg(test)]
pub fn top_level_parse(code: &str) -> ParseResult {
let module_id = ModuleId::default();
parse_str(code, module_id)
}
pub fn parse_str(code: &str, module_id: ModuleId) -> ParseResult {
let tokens = pr_try!(crate::parsing::token::lex(code, module_id));
parse_tokens(tokens)
}
pub fn parse_tokens(mut tokens: TokenStream) -> ParseResult {
let unknown_tokens = tokens.remove_unknown();
if !unknown_tokens.is_empty() {
let source_ranges = unknown_tokens.iter().map(SourceRange::from).collect();
let token_list = unknown_tokens.iter().map(|t| t.value.as_str()).collect::<Vec<_>>();
let message = if token_list.len() == 1 {
format!("found unknown token '{}'", token_list[0])
} else {
format!("found unknown tokens [{}]", token_list.join(", "))
};
return KclError::new_lexical(KclErrorDetails::new(message, source_ranges)).into();
}
if tokens.is_empty() {
return Node::<Program>::default().into();
}
if tokens.iter().all(|t| t.token_type.is_whitespace()) {
return Node::<Program>::default().into();
}
parser::run_parser(tokens.as_slice())
}
#[derive(Debug, Clone)]
pub(crate) struct ParseResult(pub Result<(Option<Node<Program>>, Vec<CompilationIssue>), KclError>);
impl ParseResult {
#[cfg(test)]
#[track_caller]
pub fn unwrap(self) -> Node<Program> {
if self.0.is_err() || self.0.as_ref().unwrap().0.is_none() {
eprint!("{self:#?}");
}
self.0.unwrap().0.unwrap()
}
#[cfg(test)]
pub fn is_ok(&self) -> bool {
match &self.0 {
Ok((p, errs)) => p.is_some() && !errs.iter().any(|e| e.severity.is_err()),
Err(_) => false,
}
}
#[cfg(test)]
#[track_caller]
pub fn unwrap_errs(&self) -> impl Iterator<Item = &CompilationIssue> {
self.0.as_ref().unwrap().1.iter().filter(|e| e.severity.is_err())
}
pub fn parse_errs_as_err(self) -> Result<Node<Program>, KclError> {
let (p, errs) = self.0?;
if let Some(err) = errs.iter().find(|e| e.severity.is_err()) {
return Err(KclError::new_syntax(err.clone().into()));
}
match p {
Some(p) => Ok(p),
None => Err(KclError::internal("Unknown parsing error".to_owned())),
}
}
}
impl From<Result<(Option<Node<Program>>, Vec<CompilationIssue>), KclError>> for ParseResult {
fn from(r: Result<(Option<Node<Program>>, Vec<CompilationIssue>), KclError>) -> ParseResult {
ParseResult(r)
}
}
impl From<(Option<Node<Program>>, Vec<CompilationIssue>)> for ParseResult {
fn from(p: (Option<Node<Program>>, Vec<CompilationIssue>)) -> ParseResult {
ParseResult(Ok(p))
}
}
impl From<Node<Program>> for ParseResult {
fn from(p: Node<Program>) -> ParseResult {
ParseResult(Ok((Some(p), vec![])))
}
}
impl From<KclError> for ParseResult {
fn from(e: KclError) -> ParseResult {
ParseResult(Err(e))
}
}
const STR_DEPRECATIONS: [(&str, &str); 16] = [
("XY", "XY"),
("XZ", "XZ"),
("YZ", "YZ"),
("-XY", "-XY"),
("-XZ", "-XZ"),
("-YZ", "-YZ"),
("xy", "XY"),
("xz", "XZ"),
("yz", "YZ"),
("-xy", "-XY"),
("-xz", "-XZ"),
("-yz", "-YZ"),
("START", "START"),
("start", "START"),
("END", "END"),
("end", "END"),
];
const FN_DEPRECATIONS: [(&str, &str); 3] = [("pi", "PI"), ("e", "E"), ("tau", "TAU")];
const CONST_DEPRECATIONS: [(&str, &str); 4] = [
("ZERO", "turns::ZERO"),
("QUARTER_TURN", "turns::QUARTER_TURN"),
("HALF_TURN", "turns::HALF_TURN"),
("THREE_QUARTER_TURN", "turns::THREE_QUARTER_TURN"),
];
#[derive(Clone, Copy)]
pub enum DeprecationKind {
String,
Function,
Const,
}
pub fn deprecation(s: &str, kind: DeprecationKind) -> Option<&'static str> {
match kind {
DeprecationKind::String => STR_DEPRECATIONS.iter().find_map(|(a, b)| (*a == s).then_some(*b)),
DeprecationKind::Function => FN_DEPRECATIONS.iter().find_map(|(a, b)| (*a == s).then_some(*b)),
DeprecationKind::Const => CONST_DEPRECATIONS.iter().find_map(|(a, b)| (*a == s).then_some(*b)),
}
}
#[cfg(test)]
mod tests {
macro_rules! parse_and_lex {
($func_name:ident, $test_kcl_program:expr_2021) => {
#[test]
fn $func_name() {
let _ = crate::parsing::top_level_parse($test_kcl_program);
}
};
}
parse_and_lex!(crash_eof_1, "{\"ގގ\0\0\0\"\".");
parse_and_lex!(crash_eof_2, "(/=e\"\u{616}ݝ\"\"");
}