use crate::{
ast::*,
parser::*,
span::{SourceSpan, Spanned}
};
use pretty_assertions::assert_eq;
#[test]
fn test_s_expr_lossless_roundtrip()
{
use crate::s_expr::{SExpressible as _, SExpressibleOptions, read_s_expr};
use crate::support::read_compilation_test_cases;
let test_cases = read_compilation_test_cases(include_str!(
"../../../tests/test_parse.txt"
));
let opts = SExpressibleOptions::default()
.with_spans(true)
.with_groups(true);
for (index, (source, _expected)) in test_cases.iter().enumerate()
{
let ast = match Parser::parse(source)
{
Ok(f) => f,
Err(e) => panic!(
"case {}: parse failed for {:?}: {}",
index + 1,
source,
e
)
};
let serialized = ast.to_s_expr(opts);
let reparsed = match read_s_expr(&serialized)
{
Ok(f) => f,
Err(e) => panic!(
"case {}: lossless s-expr read failed for {:?}\n\
serialized: {}\n\
error: {}",
index + 1,
source,
serialized,
e
)
};
assert_eq!(
reparsed,
ast,
"case {}: lossless round-trip mismatch for {:?}\n\
serialized: {}",
index + 1,
source,
serialized
);
}
}
#[test]
fn test_s_expr_default_options_omit_span_metadata_and_groups()
{
use crate::s_expr::{SExpressible as _, SExpressibleOptions};
let ast = Parser::parse("(2 + 3) * 4").unwrap();
let output = ast.to_s_expr(SExpressibleOptions::default());
assert!(
!output.contains('^'),
"default output must not include span prefixes: {}",
output
);
assert!(
!output.contains("(group"),
"default output must not include opaque groups: {}",
output
);
}
#[test]
fn test_s_expr_synthetic_spans_are_suppressed()
{
use crate::s_expr::{SExpressible as _, SExpressibleOptions};
let ast = Parser::parse("{x} + 1").unwrap().untethered();
let opts = SExpressibleOptions::default()
.with_spans(true)
.with_groups(true);
let output = ast.to_s_expr(opts);
assert!(
!output.contains('^'),
"untethered AST must not emit span prefixes: {}",
output
);
}
#[test]
fn test_s_expr_writer_emits_span_prefixes()
{
use crate::s_expr::{SExpressible as _, SExpressibleOptions};
let ast = Parser::parse("42").unwrap();
let opts = SExpressibleOptions::default().with_spans(true);
let output = ast.to_s_expr(opts);
assert_eq!(
output.trim(),
"^[0 2] (function [] ^[0 2] 42)",
"writer output mismatch: {}",
output
);
}
#[test]
fn test_s_expr_reader_accepts_span_prefix()
{
use crate::s_expr::read_s_expr;
let ast = read_s_expr("^[0 2] (function [] ^[0 2] 42)").unwrap();
assert_eq!(ast.span, SourceSpan { start: 0, end: 2 });
match &ast.body
{
Expression::Constant(c) =>
{
assert_eq!(c.value, 42);
assert_eq!(c.span, SourceSpan { start: 0, end: 2 });
},
other => panic!("unexpected body: {:?}", other)
}
}
#[test]
fn test_s_expr_reader_tolerates_whitespace_after_span_prefix()
{
use crate::s_expr::read_s_expr;
let ast = read_s_expr("^[0 2] (function [] ^[0 2]\n\t42)").unwrap();
assert_eq!(ast.span, SourceSpan { start: 0, end: 2 });
}
#[test]
fn test_s_expr_reader_accepts_parameter_span_prefixes()
{
use crate::s_expr::read_s_expr;
let ast =
read_s_expr("^[0 7] (function [^[0 1] a ^[3 4] b] ^[6 7] 0)").unwrap();
let params = ast.parameters.as_ref().expect("expected parameters");
assert_eq!(params.len(), 2);
assert_eq!(params[0].name, "a");
assert_eq!(params[0].span, SourceSpan { start: 0, end: 1 });
assert_eq!(params[1].name, "b");
assert_eq!(params[1].span, SourceSpan { start: 3, end: 4 });
}
#[test]
fn test_s_expr_reader_accepts_opaque_group()
{
use crate::s_expr::read_s_expr;
let ast = read_s_expr("(function [] (group 42))").unwrap();
match &ast.body
{
Expression::Group(g) => match g.expression.as_ref()
{
Expression::Constant(c) => assert_eq!(c.value, 42),
other => panic!("expected Constant, got {:?}", other)
},
other => panic!("expected Group, got {:?}", other)
}
}
#[test]
fn test_s_expr_reader_accepts_span_less_input()
{
use crate::s_expr::read_s_expr;
let ast = read_s_expr("(function [x] (add {x} 1))").unwrap();
assert_eq!(ast.span, SourceSpan::SYNTHETIC);
let params = ast.parameters.as_ref().unwrap();
assert_eq!(params[0].span, SourceSpan::SYNTHETIC);
assert_eq!(ast.body.span(), SourceSpan::SYNTHETIC);
}
#[test]
fn test_s_expr_reader_rejects_unrecognized_metadata()
{
use crate::s_expr::read_s_expr;
for bad in [
"^{start 0 end 5} 42",
"^symbol 42",
"^\"quoted\" 42",
"^ [0 5] 42"
]
{
let input = format!("(function [] {})", bad);
let err = read_s_expr(&input).unwrap_err();
assert!(
err.to_string().contains("expected '[' after '^'"),
"input {:?} — unexpected error: {}",
bad,
err
);
}
}
#[test]
fn test_s_expr_reader_rejects_inverted_span()
{
use crate::s_expr::read_s_expr;
let err = read_s_expr("^[5 2] (function [] 0)").unwrap_err();
assert!(
err.to_string().contains("span start 5 exceeds end 2"),
"unexpected error: {}",
err
);
}
#[test]
fn test_s_expr_reader_rejects_containment_violation()
{
use crate::s_expr::read_s_expr;
let input = "(function [] ^[0 5] (add ^[0 1] 1 ^[4 9] 2))";
let err = read_s_expr(input).unwrap_err();
assert!(
err.to_string().contains("escapes parent span"),
"unexpected error: {}",
err
);
}
#[test]
fn test_s_expr_reader_rejects_sibling_overlap()
{
use crate::s_expr::read_s_expr;
let input = "(function [] ^[0 5] (add ^[0 3] 1 ^[2 5] 2))";
let err = read_s_expr(input).unwrap_err();
assert!(
err.to_string()
.contains("overlaps or precedes prior sibling"),
"unexpected error: {}",
err
);
}
#[test]
fn test_s_expr_reader_allows_mixed_synthetic_and_annotated()
{
use crate::s_expr::read_s_expr;
let ast = read_s_expr("^[0 5] (function [] ^[0 5] (add 1 2))").unwrap();
assert_eq!(ast.span, SourceSpan { start: 0, end: 5 });
}