use perl_position_tracking::Range;
pub type NodeId = usize;
pub type DiagnosticId = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MissingKind {
Expression,
Statement,
Identifier,
Block,
ClosingDelimiter(char),
Semicolon,
Condition,
Argument,
Operator,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub id: NodeId,
pub kind: NodeKind,
pub range: Range,
}
impl Node {
pub fn new(id: NodeId, kind: NodeKind, range: Range) -> Self {
Node { id, kind, range }
}
pub fn to_sexp(&self) -> String {
self.kind.to_sexp()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeKind {
Program {
statements: Vec<Node>,
},
Block {
statements: Vec<Node>,
},
VariableDeclaration {
declarator: String, variable: Box<Node>,
attributes: Vec<String>,
initializer: Option<Box<Node>>,
},
VariableListDeclaration {
declarator: String,
variables: Vec<Node>,
attributes: Vec<String>,
initializer: Option<Box<Node>>,
},
Variable {
sigil: String, name: String,
},
Error {
message: String,
expected: Vec<String>,
partial: Option<Box<Node>>,
},
ErrorRef {
diag_id: DiagnosticId,
},
MissingExpression,
MissingStatement,
MissingIdentifier,
MissingBlock,
Missing(MissingKind),
Binary {
op: String,
left: Box<Node>,
right: Box<Node>,
},
Unary {
op: String,
operand: Box<Node>,
},
If {
condition: Box<Node>,
then_branch: Box<Node>,
elsif_branches: Vec<(Node, Node)>,
else_branch: Option<Box<Node>>,
},
Number {
value: String,
},
String {
value: String,
interpolated: bool,
},
Identifier {
name: String,
},
}
impl NodeKind {
pub fn to_sexp(&self) -> String {
use NodeKind::*;
match self {
Program { statements } => {
let stmts = statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ");
format!("(source_file {})", stmts)
}
Block { statements } => {
let stmts = statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ");
format!("(block {})", stmts)
}
Variable { sigil, name } => {
format!("(variable {} {})", sigil, name)
}
Number { value } => format!("(number {})", value),
String { value, interpolated } => {
if *interpolated {
format!("(string_interpolated {:?})", value)
} else {
format!("(string {:?})", value)
}
}
Binary { op, left, right } => {
format!("(binary_{} {} {})", op, left.to_sexp(), right.to_sexp())
}
Error { message, .. } => format!("(ERROR {})", message),
ErrorRef { diag_id } => format!("(ERROR_REF #{})", diag_id),
MissingExpression => "(MISSING_EXPRESSION)".to_string(),
MissingStatement => "(MISSING_STATEMENT)".to_string(),
MissingIdentifier => "(MISSING_IDENTIFIER)".to_string(),
MissingBlock => "(MISSING_BLOCK)".to_string(),
Missing(kind) => format!("(MISSING {:?})", kind),
_ => format!("({:?})", self),
}
}
}
pub struct NodeIdGenerator {
next_id: NodeId,
}
impl NodeIdGenerator {
pub fn new() -> Self {
NodeIdGenerator { next_id: 0 }
}
pub fn next_id(&mut self) -> NodeId {
let id = self.next_id;
self.next_id += 1;
id
}
}
impl Default for NodeIdGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use perl_position_tracking::{Position, Range};
#[test]
fn test_node_creation() {
let mut id_gen = NodeIdGenerator::new();
let range = Range::new(Position::new(0, 1, 1), Position::new(5, 1, 6));
let node = Node::new(id_gen.next_id(), NodeKind::Number { value: "42".to_string() }, range);
assert_eq!(node.id, 0);
assert_eq!(node.to_sexp(), "(number 42)");
}
#[test]
fn test_error_nodes() {
let mut id_gen = NodeIdGenerator::new();
let range = Range::new(Position::new(0, 1, 1), Position::new(0, 1, 1));
let error = Node::new(
id_gen.next_id(),
NodeKind::Error {
message: "Unexpected token".to_string(),
expected: vec!["identifier".to_string()],
partial: None,
},
range,
);
assert_eq!(error.to_sexp(), "(ERROR Unexpected token)");
}
}