use tree_sitter::Node;
use crate::{ast::ArgumentKind, ast::Ident, spans::Span, utils::tree_sitter_helpers::NodeExt};
use super::{
from_node::{FromNode, FromNodeError},
Argument, Word,
};
#[derive(Debug, Default, PartialEq, Clone)]
pub struct VariableDef {
pub variable_id: Ident, pub variable_style: Word,
pub args: Vec<Argument>,
pub span: Span,
}
impl FromNode for VariableDef {
type Error = FromNodeError;
fn from_node(node: &Node, text: &str) -> Result<Self, Self::Error> {
let span: Span = node.range().into();
let mut cursor = node.walk();
let mut children = node.children(&mut cursor);
let variable_keyword = children
.next()
.ok_or(Self::Error::PartialNode(
"missing variable keyword".to_string(),
))?;
debug_assert_eq!(variable_keyword.str_text(text), "variable");
let variable_ident = children
.next()
.ok_or(Self::Error::PartialNode(
"missing variable identifier".to_string(),
))?;
let variable_ident = Ident::new(&variable_ident, text)?;
let variable_kind = Word::from_node(
&children.next().ok_or(Self::Error::PartialNode(
"missing variable style".to_string(),
))?,
text,
)?;
let args: Result<Vec<Argument>, _> = children
.map(|arg| {
if arg.is_missing() {
return Err(FromNodeError::PartialNode(
"missing variable expression".to_string(),
));
}
Argument::from_node(&arg, text)
})
.collect();
let args = args?;
if variable_kind.contents != "delete" && args.is_empty() {
return Err(Self::Error::PartialNode(
"missing arguments in variable command".to_string(),
));
}
if matches!(variable_kind.contents.as_str(), "equal" | "atom") {
if !matches!(args[0].kind, ArgumentKind::Expression(_)) {
return Err(Self::Error::PartialNode(format!(
"expected expression for variable style {}",
variable_kind.contents
)));
}
if args.len() > 1 {
return Err(Self::Error::PartialNode(format!(
"only one argument expected for variable style {}",
variable_kind.contents
)));
}
}
Ok(VariableDef {
variable_id: variable_ident,
variable_style: variable_kind,
args,
span,
})
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use pretty_assertions::assert_eq;
use super::*;
use crate::ast;
use crate::utils::testing::parse;
#[test]
fn parse_variable_def() {
let source = "variable a equal 1.0*dt";
let tree = parse(source);
let root_node = tree.root_node();
use ast::expressions::BinaryOp;
use ast::Expression as Exp;
let expected = VariableDef {
variable_id: Ident {
name: "a".into(),
ident_type: ast::IdentType::Variable,
span: ((0, 9)..(0, 10)).into(),
},
variable_style: Word {
contents: "equal".into(),
span: ((0, 11)..(0, 16)).into(),
},
args: vec![Argument {
kind: ArgumentKind::Expression(Exp::BinaryOp(
Box::new(Exp::Float(1.0)),
BinaryOp::Multiply,
Box::new(Exp::ThermoKeyword(Word {
contents: "dt".to_owned(),
span: ((0, 21)..(0, 23)).into(),
})),
)),
span: ((0, 17)..(0, 23)).into(),
}],
span: ((0, 0)..(0, 23)).into(),
};
let variable_node = root_node.child(0).expect("Should find child node.");
let parsed = VariableDef::from_node(&variable_node, source);
assert_eq!(parsed, Ok(expected));
}
#[test]
fn parse_index_variable() {
let text = "variable file_name index step4.1.atm\n";
let tree = parse(text);
dbg!(tree.root_node().to_sexp());
let root_node = tree.root_node();
let var_def_node = root_node.child(0).unwrap();
let expected = VariableDef {
variable_id: Ident {
name: "file_name".to_string(),
ident_type: ast::IdentType::Variable,
span: ((0, 9)..(0, 18)).into(),
},
variable_style: ast::Word::new("index".to_string(), (0, 19)..(0, 24)),
args: vec![Argument {
kind: ArgumentKind::Word("step4.1.atm".to_string()),
span: ((0, 25)..(0, 36)).into(),
}],
span: ((0, 0)..(0, 36)).into(),
};
assert_eq!(
VariableDef::from_node(&var_def_node, text).as_ref(),
Ok(&expected)
);
let ast = ast::ts_to_ast(&tree, text);
assert!(ast.is_ok());
if let Ok(ast) = ast {
dbg!(&ast);
assert_eq!(ast.commands.len(), 1);
match &ast.commands[0] {
crate::ast::Command::VariableDef(var) => {
assert_eq!(*var, expected)
}
cmd => panic!("Unexpected command {cmd:?}"),
}
}
}
#[test]
fn parse_incomplete_variable_def() {
let source = "variable a equal\n";
let tree = parse(source);
let root_node = tree.root_node();
dbg!(root_node.to_sexp());
assert!(!root_node.is_missing());
let expected =
FromNodeError::PartialNode("missing arguments in variable command".to_string());
let variable_node = root_node.child(0).expect("Should find child node.");
let parsed = VariableDef::from_node(&variable_node, source);
assert_eq!(parsed, Err(expected));
}
}