#[cfg(test)]
mod tests {
use crate::engine::parser::Parser;
use perl_ast::ast::{Node, NodeKind, SourceLocation};
fn parse_code(input: &str) -> Option<perl_ast::ast::Node> {
let mut parser = Parser::new(input);
parser.parse().ok()
}
#[test]
fn parser_format_picture_lines() {
let source = r#"format REPORT =
@<<<<<<<<< @||||||| @>>>>>>>
$left, $center, $right
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "REPORT");
assert!(body.contains("@<<<<<<<<<"));
assert!(body.contains("@|||||||"));
assert!(body.contains("@>>>>>>>"));
assert!(body.contains("$left"));
assert!(body.contains("$center"));
assert!(body.contains("$right"));
} else {
unreachable!("Expected Format node, got {:?}", stmt.kind);
}
}
}
#[test]
fn parser_format_value_lines() {
let source = r#"format VALUES =
Name: @<<<<<<<<<<<< Length: @###
$name, length($name)
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "VALUES");
assert!(body.contains("$name"));
assert!(body.contains("length($name)"));
} else {
unreachable!("Expected Format node, got {:?}", stmt.kind);
}
}
}
#[test]
fn parser_format_multiline_fields() {
let source = r#"format MULTILINE =
Description:
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "MULTILINE");
assert!(body.contains("^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"));
let count = body.matches('^').count();
assert!(count >= 2, "Expected at least 2 multiline field markers, found {}", count);
} else {
unreachable!("Expected Format node, got {:?}", stmt.kind);
}
}
}
#[test]
fn parser_format_numeric_fields() {
let source = r#"format NUMBERS =
Integer: @#### Float: @###.##
$int, $float
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "NUMBERS");
assert!(body.contains("@####"));
assert!(body.contains("@###.##"));
} else {
unreachable!("Expected Format node, got {:?}", stmt.kind);
}
}
}
#[test]
fn parser_format_complex_specifiers() {
let source = r#"format COMPLEX =
~~^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$long_text
@||||||||||||||||||||||||||||||||||||||||||||||||||||
"centered text"
@#####.## @<<<< @>>>> @|||| @#### @>>>>>>>>
$num1, $str1, $str2, $str3, $num2, $str4
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "COMPLEX");
assert!(body.contains("~~^")); assert!(body.contains("@#####.##")); assert!(body.contains("@<<<<")); assert!(body.contains("@>>>>")); assert!(body.contains("@||||")); } else {
unreachable!("Expected Format node, got {:?}", stmt.kind);
}
}
}
#[test]
fn parser_format_with_special_variables() {
let source = r#"format SPECIAL =
File: @<<<<<<<<<< Line: @#### Page: @###
$ARGV, $., $%
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "SPECIAL");
assert!(body.contains("$ARGV"));
assert!(body.contains("$."));
assert!(body.contains("$%"));
} else {
unreachable!("Expected Format node, got {:?}", stmt.kind);
}
}
}
#[test]
fn parser_format_unterminated_error() {
let source = r#"format BROKEN =
Test: @<<<
$test
"#; let result = parse_code(source);
assert!(result.is_some(), "Parser should handle unterminated format gracefully");
}
#[test]
fn parser_format_multiple_formats() {
let source = r#"format STDOUT =
@<<< @>>>
$a, $b
.
format REPORT =
@||||
$title
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 2, "Expected 2 format declarations");
if let NodeKind::Format { name, body } = &statements[0].kind {
assert_eq!(name, "STDOUT");
assert!(body.contains("@<<<"));
} else {
unreachable!("Expected first Format node");
}
if let NodeKind::Format { name, body } = &statements[1].kind {
assert_eq!(name, "REPORT");
assert!(body.contains("@||||"));
} else {
unreachable!("Expected second Format node");
}
}
}
#[test]
fn parser_format_with_code_after() {
let source = r#"format TEST =
@<<<
$x
.
my $y = 42;
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 2, "Expected format + variable declaration");
if let NodeKind::Format { name, .. } = &statements[0].kind {
assert_eq!(name, "TEST");
} else {
unreachable!("Expected Format node first");
}
assert!(matches!(statements[1].kind, NodeKind::VariableDeclaration { .. }));
}
}
#[test]
fn parser_format_top_of_page() {
let source = r#"format REPORT_TOP =
Page @###
$%
================================
.
format REPORT =
@<<<<<<<<< @||||||| @>>>>>>>
$left, $center, $right
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 2);
if let NodeKind::Format { name, body } = &statements[0].kind {
assert_eq!(name, "REPORT_TOP");
assert!(body.contains("Page @###"));
assert!(body.contains("$%")); } else {
unreachable!("Expected REPORT_TOP format");
}
if let NodeKind::Format { name, .. } = &statements[1].kind {
assert_eq!(name, "REPORT");
} else {
unreachable!("Expected REPORT format");
}
}
}
#[test]
fn parser_format_with_expressions() {
let source = r#"format EXPR =
Name: @<<<<<<<<<<<<< Length: @###
$name, length($name)
Date: @<<<<<<<<<<<< Time: @<<<<<<<<<
scalar(localtime), $^T
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "EXPR");
assert!(body.contains("length($name)"));
assert!(body.contains("scalar(localtime)"));
assert!(body.contains("$^T"));
} else {
unreachable!("Expected Format node with expressions");
}
}
}
#[test]
fn parser_format_minimal() {
let source = r#"format MIN =
.
"#;
let ast_opt = parse_code(source);
assert!(ast_opt.is_some(), "Should have parsed successfully");
let ast = ast_opt.unwrap_or_else(|| {
Node::new(NodeKind::UnknownRest, SourceLocation { start: 0, end: 0 })
});
if let NodeKind::Program { statements } = &ast.kind {
let stmt = &statements[0];
if let NodeKind::Format { name, body } = &stmt.kind {
assert_eq!(name, "MIN");
assert_eq!(body.trim(), "");
} else {
unreachable!("Expected minimal Format node");
}
}
}
}