use crate::code_block::{Arg, CodeBlock, FormatPart};
use crate::type_name::TypeName;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum CodeNode {
Literal(String),
TypeRef(TypeName),
NameRef(String),
StringLit(String),
InlineLiteral(String),
Nested(CodeBlock),
Comment(String),
SoftBreak,
Indent,
Dedent,
StatementBegin,
StatementEnd,
Newline,
BlockOpen,
BlockOpenOverride(String),
BlockClose,
BlockCloseTransition,
Sequence(Vec<CodeNode>),
}
pub(crate) fn parts_args_to_nodes(parts: &[FormatPart], args: &[Arg]) -> Vec<CodeNode> {
let mut nodes = Vec::with_capacity(parts.len());
let mut arg_index = 0;
for part in parts {
let node = match part {
FormatPart::Literal(text) => CodeNode::Literal(text.clone()),
FormatPart::Type => {
let arg = &args[arg_index];
arg_index += 1;
match arg {
Arg::TypeName(tn) => CodeNode::TypeRef(tn.clone()),
_ => CodeNode::Literal(String::new()),
}
}
FormatPart::Name => {
let arg = &args[arg_index];
arg_index += 1;
match arg {
Arg::Name(n) => CodeNode::NameRef(n.clone()),
_ => CodeNode::Literal(String::new()),
}
}
FormatPart::StringLit => {
let arg = &args[arg_index];
arg_index += 1;
match arg {
Arg::StringLit(s) => CodeNode::StringLit(s.clone()),
_ => CodeNode::Literal(String::new()),
}
}
FormatPart::Literal_ => {
let arg = &args[arg_index];
arg_index += 1;
match arg {
Arg::Literal(s) => CodeNode::InlineLiteral(s.clone()),
Arg::Code(block) => CodeNode::Nested(block.clone()),
_ => CodeNode::Literal(String::new()),
}
}
FormatPart::Wrap => CodeNode::SoftBreak,
FormatPart::Indent => CodeNode::Indent,
FormatPart::Dedent => CodeNode::Dedent,
FormatPart::StatementBegin => CodeNode::StatementBegin,
FormatPart::StatementEnd => CodeNode::StatementEnd,
FormatPart::Newline => CodeNode::Newline,
FormatPart::BlockOpen => CodeNode::BlockOpen,
FormatPart::BlockOpenOverride(s) => CodeNode::BlockOpenOverride(s.clone()),
FormatPart::BlockClose => CodeNode::BlockClose,
FormatPart::BlockCloseTransition => CodeNode::BlockCloseTransition,
};
nodes.push(node);
}
nodes
}
#[cfg(test)]
mod tests {
use super::*;
use crate::code_block::CodeBlock;
use crate::type_name::TypeName;
#[test]
fn test_literal_conversion() {
let parts = vec![FormatPart::Literal("hello".to_string())];
let args = vec![];
let nodes = parts_args_to_nodes(&parts, &args);
assert_eq!(nodes.len(), 1);
assert!(matches!(&nodes[0], CodeNode::Literal(s) if s == "hello"));
}
#[test]
fn test_type_ref_conversion() {
let tn = TypeName::primitive("string");
let parts = vec![FormatPart::Literal("x: ".to_string()), FormatPart::Type];
let args = vec![Arg::TypeName(tn)];
let nodes = parts_args_to_nodes(&parts, &args);
assert_eq!(nodes.len(), 2);
assert!(matches!(&nodes[0], CodeNode::Literal(s) if s == "x: "));
assert!(matches!(&nodes[1], CodeNode::TypeRef(_)));
}
#[test]
fn test_nested_block_conversion() {
let inner = CodeBlock::of("inner()", ()).unwrap();
let parts = vec![FormatPart::Literal_];
let args = vec![Arg::Code(inner)];
let nodes = parts_args_to_nodes(&parts, &args);
assert_eq!(nodes.len(), 1);
assert!(matches!(&nodes[0], CodeNode::Nested(_)));
}
#[test]
fn test_structural_nodes() {
let parts = vec![
FormatPart::Indent,
FormatPart::StatementBegin,
FormatPart::Literal("x".to_string()),
FormatPart::StatementEnd,
FormatPart::Newline,
FormatPart::Dedent,
];
let nodes = parts_args_to_nodes(&parts, &[]);
assert_eq!(nodes.len(), 6);
assert!(matches!(nodes[0], CodeNode::Indent));
assert!(matches!(nodes[1], CodeNode::StatementBegin));
assert!(matches!(nodes[3], CodeNode::StatementEnd));
assert!(matches!(nodes[4], CodeNode::Newline));
assert!(matches!(nodes[5], CodeNode::Dedent));
}
#[test]
fn test_soft_break_conversion() {
let parts = vec![
FormatPart::Literal("a".to_string()),
FormatPart::Wrap,
FormatPart::Literal("b".to_string()),
];
let nodes = parts_args_to_nodes(&parts, &[]);
assert_eq!(nodes.len(), 3);
assert!(matches!(nodes[1], CodeNode::SoftBreak));
}
#[test]
fn test_block_open_close_conversion() {
let parts = vec![
FormatPart::BlockOpen,
FormatPart::BlockClose,
FormatPart::BlockOpenOverride("where".to_string()),
FormatPart::BlockCloseTransition,
];
let nodes = parts_args_to_nodes(&parts, &[]);
assert_eq!(nodes.len(), 4);
assert!(matches!(nodes[0], CodeNode::BlockOpen));
assert!(matches!(nodes[1], CodeNode::BlockClose));
assert!(matches!(&nodes[2], CodeNode::BlockOpenOverride(s) if s == "where"));
assert!(matches!(nodes[3], CodeNode::BlockCloseTransition));
}
#[test]
fn test_mixed_args_conversion() {
let tn = TypeName::primitive("number");
let parts = vec![
FormatPart::Literal("let ".to_string()),
FormatPart::Name,
FormatPart::Literal(": ".to_string()),
FormatPart::Type,
FormatPart::Literal(" = ".to_string()),
FormatPart::StringLit,
];
let args = vec![
Arg::Name("x".to_string()),
Arg::TypeName(tn),
Arg::StringLit("hello".to_string()),
];
let nodes = parts_args_to_nodes(&parts, &args);
assert_eq!(nodes.len(), 6);
assert!(matches!(&nodes[1], CodeNode::NameRef(s) if s == "x"));
assert!(matches!(&nodes[3], CodeNode::TypeRef(_)));
assert!(matches!(&nodes[5], CodeNode::StringLit(s) if s == "hello"));
}
}