use perl_ast::ast::{Node, NodeKind, SourceLocation};
fn loc(start: usize, end: usize) -> SourceLocation {
SourceLocation { start, end }
}
fn num(value: &str) -> Node {
Node::new(NodeKind::Number { value: value.to_string() }, loc(0, value.len()))
}
fn block_empty() -> Node {
Node::new(NodeKind::Block { statements: vec![] }, loc(0, 1))
}
fn make_binary(op: &str) -> Node {
Node::new(
NodeKind::Binary {
op: op.to_string(),
left: Box::new(num("1")),
right: Box::new(num("2")),
},
loc(0, 5),
)
}
#[test]
fn sexp_binary_lt_operator() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("lt").to_sexp();
assert!(sexp.starts_with("(binary_lt "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_le_operator() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("le").to_sexp();
assert!(sexp.starts_with("(binary_le "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_gt_operator() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("gt").to_sexp();
assert!(sexp.starts_with("(binary_gt "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_ge_operator() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("ge").to_sexp();
assert!(sexp.starts_with("(binary_ge "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_cmp_operator() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("cmp").to_sexp();
assert!(sexp.starts_with("(binary_cmp "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_xor_operator() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("xor").to_sexp();
assert!(sexp.starts_with("(binary_xor "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_bitwise_and() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("&").to_sexp();
assert!(sexp.starts_with("(binary_& "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_bitwise_or() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("|").to_sexp();
assert!(sexp.starts_with("(binary_| "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_bitwise_xor() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("^").to_sexp();
assert!(sexp.starts_with("(binary_^ "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_left_shift() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("<<").to_sexp();
assert!(sexp.starts_with("(binary_<< "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_right_shift() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary(">>").to_sexp();
assert!(sexp.starts_with("(binary_>> "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_string_repetition_x() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("x").to_sexp();
assert!(sexp.starts_with("(binary_x "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_exclusive_range() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("...").to_sexp();
assert!(sexp.starts_with("(binary_... "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_hash_subscript() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("{}").to_sexp();
assert!(sexp.starts_with("(binary_{} "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_array_subscript() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("[]").to_sexp();
assert!(sexp.starts_with("(binary_[] "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_arrow_hash_deref() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("->{}").to_sexp();
assert!(sexp.starts_with("(arrow_hash_deref "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_arrow_array_deref() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("->[]").to_sexp();
assert!(sexp.starts_with("(arrow_array_deref "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_unknown_operator_uses_fallback_format() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("not_a_real_op").to_sexp();
assert!(sexp.starts_with("(binary_not_a_real_op "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_binary_operator_with_spaces_in_name_replaces_spaces()
-> Result<(), Box<dyn std::error::Error>> {
let sexp = make_binary("op with spaces").to_sexp();
assert!(sexp.starts_with("(binary_op_with_spaces "), "got: {sexp}");
Ok(())
}
fn make_unary(op: &str) -> Node {
Node::new(NodeKind::Unary { op: op.to_string(), operand: Box::new(num("1")) }, loc(0, 5))
}
#[test]
fn sexp_unary_unknown_operator_uses_fallback_format() -> Result<(), Box<dyn std::error::Error>> {
let sexp = make_unary("some_custom_op").to_sexp();
assert!(sexp.starts_with("(unary_some_custom_op "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_unary_unknown_operator_with_spaces_replaces_spaces()
-> Result<(), Box<dyn std::error::Error>> {
let sexp = make_unary("my op").to_sexp();
assert!(sexp.starts_with("(unary_my_op "), "got: {sexp}");
Ok(())
}
#[test]
fn sexp_class_with_single_parent() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Class {
name: "Animal".to_string(),
name_span: None,
parents: vec!["Mammal".to_string()],
body: Box::new(block_empty()),
},
loc(0, 30),
);
let sexp = node.to_sexp();
assert!(sexp.contains(":isa("), "expected :isa( in sexp, got: {sexp}");
assert!(sexp.contains("Mammal"), "expected parent name, got: {sexp}");
Ok(())
}
#[test]
fn sexp_class_with_multiple_parents() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Class {
name: "Mule".to_string(),
name_span: None,
parents: vec!["Horse".to_string(), "Donkey".to_string()],
body: Box::new(block_empty()),
},
loc(0, 40),
);
let sexp = node.to_sexp();
assert!(sexp.contains(":isa(Horse,Donkey)"), "expected comma-joined parents, got: {sexp}");
Ok(())
}
#[test]
fn sexp_class_without_parents_has_no_isa() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Class {
name: "Standalone".to_string(),
name_span: None,
parents: vec![],
body: Box::new(block_empty()),
},
loc(0, 20),
);
let sexp = node.to_sexp();
assert!(!sexp.contains(":isa("), "no-parent class must not contain :isa, got: {sexp}");
assert!(sexp.starts_with("(class Standalone "), "got: {sexp}");
Ok(())
}
#[test]
fn span_len_normal_span() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(5, 10));
assert_eq!(node.span_len(), 5);
Ok(())
}
#[test]
fn span_len_zero_length_span() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(7, 7));
assert_eq!(node.span_len(), 0);
Ok(())
}
#[test]
fn span_len_inverted_span_saturates_to_zero() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(10, 3));
assert_eq!(node.span_len(), 0);
Ok(())
}
#[test]
fn contains_offset_start_is_inclusive() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(5, 10));
assert!(node.contains_offset(5), "start should be inclusive");
Ok(())
}
#[test]
fn contains_offset_end_is_exclusive() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(5, 10));
assert!(!node.contains_offset(10), "end should be exclusive");
Ok(())
}
#[test]
fn contains_offset_just_before_start_returns_false() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(5, 10));
assert!(!node.contains_offset(4), "offset before start must be false");
Ok(())
}
#[test]
fn contains_offset_just_before_end_returns_true() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(5, 10));
assert!(node.contains_offset(9), "offset one before end must be true");
Ok(())
}
#[test]
fn contains_offset_zero_length_span_is_always_false() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Undef, loc(5, 5));
assert!(!node.contains_offset(5), "zero-length span contains no offset, not even start");
assert!(!node.contains_offset(4));
assert!(!node.contains_offset(6));
Ok(())
}