use super::parse;
use crate::core::{BuiltinVar, ElementKind, Expr, ExprKind, RangeKind, StrPart};
use crate::span::Span;
use crate::template::{Configuration, Template};
use crate::value::TNumber;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use std::rc::Rc;
use std::str::FromStr;
fn cfg() -> Rc<Configuration> {
Rc::new(Configuration::new())
}
fn parse_ok(src: &str) -> Template {
parse(&cfg(), "t", src).unwrap_or_else(|e| panic!("parse failed for {src:?}: {e}"))
}
#[allow(dead_code)]
fn parse_err(src: &str) -> String {
match parse(&cfg(), "t", src) {
Err(e) => e.to_string(),
Ok(_) => panic!("expected parse error for {src:?}"),
}
}
fn expr_of(src: &str) -> ExprKind {
let t = parse_ok(&format!("${{{src}}}"));
match &t.root[0].kind {
ElementKind::Interpolation { expr, .. } => expr.kind.clone(),
k => panic!("expected interpolation, got {k:?}"),
}
}
fn num(v: TNumber) -> ExprKind {
ExprKind::Num(v)
}
fn ident(n: &str) -> ExprKind {
ExprKind::Ident(n.to_string())
}
fn strlit(s: &str) -> ExprKind {
ExprKind::Str(s.to_string())
}
#[test]
fn number_literal_mapping() {
assert_eq!(expr_of("1"), num(TNumber::Int(1)));
assert_eq!(expr_of("1L"), num(TNumber::Long(1)));
assert_eq!(expr_of("1F"), num(TNumber::Float(1.0)));
assert_eq!(expr_of("1D"), num(TNumber::Double(1.0)));
assert_eq!(
expr_of("1.5"),
num(TNumber::Decimal(BigDecimal::from_str("1.5").unwrap()))
);
assert_eq!(
expr_of("1e3"),
num(TNumber::Decimal(BigDecimal::from_str("1000").unwrap()))
);
assert_eq!(
expr_of("1.5e-2"),
num(TNumber::Decimal(BigDecimal::from_str("0.015").unwrap()))
);
assert_eq!(expr_of("0x1A"), num(TNumber::Int(26)));
assert_eq!(expr_of("0x10L"), num(TNumber::Long(16)));
assert_eq!(
expr_of("99999999999999999999"),
num(TNumber::BigInt(
BigInt::from_str("99999999999999999999").unwrap()
))
);
assert_eq!(
expr_of("99999999999999999999L"),
num(TNumber::BigInt(
BigInt::from_str("99999999999999999999").unwrap()
))
);
}
#[test]
fn string_literal_escapes() {
assert_eq!(expr_of(r#""abc""#), strlit("abc"));
assert_eq!(expr_of(r#""a\n\t\\\'\"b""#), strlit("a\n\t\\'\"b"));
assert_eq!(expr_of(r#""\l\g\a""#), strlit("<>&"));
assert_eq!(expr_of(r#""\x41""#), strlit("A"));
assert_eq!(expr_of(r#""\u0041\u00e9""#), strlit("Aé"));
}
#[test]
fn string_interpolation_parts() {
assert_eq!(
expr_of(r#""a${x}b""#),
ExprKind::InterpStr(vec![
StrPart::Text("a".to_string()),
StrPart::Interp(Box::new(Expr::new(ident("x"), Span::new(1, 1)))),
StrPart::Text("b".to_string()),
])
);
assert_eq!(
expr_of(r#""${x}""#),
ExprKind::InterpStr(vec![StrPart::Interp(Box::new(Expr::new(
ident("x"),
Span::new(1, 1)
)))])
);
assert_eq!(
expr_of(r#"'x ${"a${y}b"} z'"#),
ExprKind::InterpStr(vec![
StrPart::Text("x ".to_string()),
StrPart::Interp(Box::new(Expr::new(
ExprKind::InterpStr(vec![
StrPart::Text("a".to_string()),
StrPart::Interp(Box::new(Expr::new(ident("y"), Span::new(1, 1)))),
StrPart::Text("b".to_string()),
]),
Span::new(1, 1)
))),
StrPart::Text(" z".to_string()),
])
);
assert_eq!(
expr_of(r#""$${x}""#),
ExprKind::InterpStr(vec![
StrPart::Text("$".to_string()),
StrPart::Interp(Box::new(Expr::new(ident("x"), Span::new(1, 1)))),
])
);
assert_eq!(expr_of(r#"r"a${x}\n""#), strlit("a${x}\\n"));
}
#[test]
fn string_literal_unclosed() {
let msg = parse_err(r#"${"abc}"#);
assert!(
msg.contains("Lexical error: encountered <EOF> after \"\\\"abc}\"."),
"{msg}"
);
}
#[test]
fn invalid_escape_sequence() {
let msg = parse_err(r#"${"a\qb"}"#);
assert!(msg.contains("Invalid escape sequence"), "{msg}");
}
#[test]
fn boolean_and_identifier() {
assert_eq!(expr_of("true"), ExprKind::Bool(true));
assert_eq!(expr_of("false"), ExprKind::Bool(false));
assert_eq!(expr_of("now"), ExprKind::BuiltinVar(BuiltinVar::Now));
assert_eq!(expr_of("fooBar_$1"), ident("fooBar_$1"));
assert_eq!(expr_of("français"), ident("français"));
assert_eq!(expr_of(".now"), ExprKind::BuiltinVar(BuiltinVar::Now));
}
#[test]
fn postfix_operations() {
assert_eq!(
expr_of("a.b"),
ExprKind::Dot {
target: Box::new(Expr::new(ident("a"), Span::new(1, 3))),
name: "b".to_string(),
}
);
assert_eq!(
expr_of("a.b.c"),
ExprKind::Dot {
target: Box::new(Expr::new(
ExprKind::Dot {
target: Box::new(Expr::new(ident("a"), Span::new(1, 3))),
name: "b".to_string(),
},
Span::new(1, 3)
)),
name: "c".to_string(),
}
);
assert_eq!(
expr_of(r#"a["k"]"#),
ExprKind::DynKey {
target: Box::new(Expr::new(ident("a"), Span::new(1, 3))),
key: Box::new(Expr::new(strlit("k"), Span::new(1, 5))),
}
);
assert_eq!(
expr_of("a.in"),
ExprKind::Dot {
target: Box::new(Expr::new(ident("a"), Span::new(1, 3))),
name: "in".to_string(),
}
);
}
#[test]
fn method_call() {
assert_eq!(
expr_of("f(x, y)"),
ExprKind::Call {
callee: Box::new(Expr::new(ident("f"), Span::new(1, 3))),
args: vec![
Expr::new(ident("x"), Span::new(1, 5)),
Expr::new(ident("y"), Span::new(1, 8)),
],
}
);
}
#[test]
fn builtin_variants() {
assert_eq!(
expr_of("x?upper_case"),
ExprKind::BuiltIn {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
name: "upper_case".to_string(),
args: None,
}
);
assert_eq!(
expr_of(r#"x?string("0.##")"#),
ExprKind::BuiltIn {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
name: "string".to_string(),
args: Some(vec![Expr::new(strlit("0.##"), Span::new(1, 12))]),
}
);
}
#[test]
fn exists_and_default_to() {
assert_eq!(
expr_of("x??"),
ExprKind::Exists(Box::new(Expr::new(ident("x"), Span::new(1, 3))))
);
assert_eq!(
expr_of("x!"),
ExprKind::Default {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
default: None,
}
);
assert_eq!(
expr_of("x!y"),
ExprKind::Default {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
default: Some(Box::new(Expr::new(ident("y"), Span::new(1, 5)))),
}
);
assert_eq!(
expr_of("x!y + z"),
ExprKind::Default {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
default: Some(Box::new(Expr::new(
ExprKind::Add(
Box::new(Expr::new(ident("y"), Span::new(1, 5))),
Box::new(Expr::new(ident("z"), Span::new(1, 9))),
),
Span::new(1, 5)
))),
}
);
assert_eq!(
expr_of("x! && y"),
ExprKind::And(
Box::new(Expr::new(
ExprKind::Default {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
default: None,
},
Span::new(1, 3)
)),
Box::new(Expr::new(ident("y"), Span::new(1, 9))),
)
);
}
#[test]
fn lambda_expression() {
assert_eq!(
expr_of("x?filter(y -> y > 1)"),
ExprKind::BuiltIn {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
name: "filter".to_string(),
args: Some(vec![Expr::new(
ExprKind::Lambda {
params: vec!["y".to_string()],
body: Box::new(Expr::new(
ExprKind::Gt(
Box::new(Expr::new(ident("y"), Span::new(1, 17))),
Box::new(Expr::new(num(TNumber::Int(1)), Span::new(1, 21))),
),
Span::new(1, 17)
)),
},
Span::new(1, 12)
)]),
}
);
assert_eq!(
expr_of("x?map((y) -> y * 2)"),
ExprKind::BuiltIn {
target: Box::new(Expr::new(ident("x"), Span::new(1, 3))),
name: "map".to_string(),
args: Some(vec![Expr::new(
ExprKind::Lambda {
params: vec!["y".to_string()],
body: Box::new(Expr::new(
ExprKind::Mul(
Box::new(Expr::new(ident("y"), Span::new(1, 16))),
Box::new(Expr::new(num(TNumber::Int(2)), Span::new(1, 20))),
),
Span::new(1, 16)
)),
},
Span::new(1, 9)
)]),
}
);
}
#[test]
fn list_and_hash_literals() {
assert_eq!(
expr_of("[1, 2]"),
ExprKind::ListLit(vec![
Expr::new(num(TNumber::Int(1)), Span::new(1, 4)),
Expr::new(num(TNumber::Int(2)), Span::new(1, 7)),
])
);
assert_eq!(expr_of("[]"), ExprKind::ListLit(vec![]));
assert_eq!(
expr_of(r#"{"a": 1}"#),
ExprKind::HashLit(vec![(
Expr::new(strlit("a"), Span::new(1, 4)),
Expr::new(num(TNumber::Int(1)), Span::new(1, 9)),
)])
);
assert_eq!(
expr_of(r#"{"a", 1}"#),
ExprKind::HashLit(vec![(
Expr::new(strlit("a"), Span::new(1, 4)),
Expr::new(num(TNumber::Int(1)), Span::new(1, 9)),
)])
);
let msg = parse_err(r#"${ {1: 2} }"#);
assert!(
msg.contains("Found number literal: 1. Expecting string"),
"{msg}"
);
}
#[test]
fn parenthesis_and_unary() {
assert_eq!(
expr_of("(x)"),
ExprKind::Paren(Box::new(Expr::new(ident("x"), Span::new(1, 4))))
);
assert_eq!(
expr_of("-x"),
ExprKind::UnaryMinus(Box::new(Expr::new(ident("x"), Span::new(1, 4))))
);
assert_eq!(expr_of("+x"), ident("x"));
assert_eq!(
expr_of("!x"),
ExprKind::Not(Box::new(Expr::new(ident("x"), Span::new(1, 4))))
);
assert_eq!(
expr_of("!!x"),
ExprKind::Not(Box::new(Expr::new(
ExprKind::Not(Box::new(Expr::new(ident("x"), Span::new(1, 5)))),
Span::new(1, 4)
)))
);
assert_eq!(
expr_of("-1"),
ExprKind::UnaryMinus(Box::new(Expr::new(num(TNumber::Int(1)), Span::new(1, 4))))
);
}
#[test]
fn precedence_and_associativity() {
assert_eq!(
expr_of("1 + 2 * 3"),
ExprKind::Add(
Box::new(Expr::new(num(TNumber::Int(1)), Span::new(1, 3))),
Box::new(Expr::new(
ExprKind::Mul(
Box::new(Expr::new(num(TNumber::Int(2)), Span::new(1, 7))),
Box::new(Expr::new(num(TNumber::Int(3)), Span::new(1, 11))),
),
Span::new(1, 7)
)),
)
);
assert_eq!(
expr_of("(1 + 2) * 3"),
ExprKind::Mul(
Box::new(Expr::new(
ExprKind::Paren(Box::new(Expr::new(
ExprKind::Add(
Box::new(Expr::new(num(TNumber::Int(1)), Span::new(1, 4))),
Box::new(Expr::new(num(TNumber::Int(2)), Span::new(1, 8))),
),
Span::new(1, 4)
))),
Span::new(1, 3)
)),
Box::new(Expr::new(num(TNumber::Int(3)), Span::new(1, 13))),
)
);
assert_eq!(
expr_of("a + b - c"),
ExprKind::Sub(
Box::new(Expr::new(
ExprKind::Add(
Box::new(Expr::new(ident("a"), Span::new(1, 3))),
Box::new(Expr::new(ident("b"), Span::new(1, 7))),
),
Span::new(1, 3)
)),
Box::new(Expr::new(ident("c"), Span::new(1, 11))),
)
);
assert_eq!(
expr_of("a && b || c"),
ExprKind::Or(
Box::new(Expr::new(
ExprKind::And(
Box::new(Expr::new(ident("a"), Span::new(1, 3))),
Box::new(Expr::new(ident("b"), Span::new(1, 8))),
),
Span::new(1, 3)
)),
Box::new(Expr::new(ident("c"), Span::new(1, 13))),
)
);
assert_eq!(
expr_of("a == b && c"),
ExprKind::And(
Box::new(Expr::new(
ExprKind::Eq(
Box::new(Expr::new(ident("a"), Span::new(1, 3))),
Box::new(Expr::new(ident("b"), Span::new(1, 8))),
),
Span::new(1, 3)
)),
Box::new(Expr::new(ident("c"), Span::new(1, 13))),
)
);
assert_eq!(
expr_of("!a && b"),
ExprKind::And(
Box::new(Expr::new(
ExprKind::Not(Box::new(Expr::new(ident("a"), Span::new(1, 4)))),
Span::new(1, 3)
)),
Box::new(Expr::new(ident("b"), Span::new(1, 9))),
)
);
assert_eq!(
expr_of("x > 1 == y"),
ExprKind::Eq(
Box::new(Expr::new(
ExprKind::Gt(
Box::new(Expr::new(ident("x"), Span::new(1, 3))),
Box::new(Expr::new(num(TNumber::Int(1)), Span::new(1, 7))),
),
Span::new(1, 3)
)),
Box::new(Expr::new(ident("y"), Span::new(1, 12))),
)
);
let msg = parse_err("${a == b == c}");
assert!(msg.contains("line 1, column 10"), "{msg}");
assert!(msg.contains("Expected \"}\""), "{msg}");
}
#[test]
fn range_expressions() {
assert_eq!(
expr_of("1..5"),
ExprKind::Range {
start: Box::new(Expr::new(num(TNumber::Int(1)), Span::new(1, 3))),
end: Some(Box::new(Expr::new(num(TNumber::Int(5)), Span::new(1, 6)))),
kind: RangeKind::Inclusive,
}
);
assert_eq!(expr_of("1..<5").kind_of_range_kind(), RangeKind::Exclusive);
assert_eq!(
expr_of("1..*5").kind_of_range_kind(),
RangeKind::SizeLimited
);
assert_eq!(
expr_of("1.."),
ExprKind::Range {
start: Box::new(Expr::new(num(TNumber::Int(1)), Span::new(1, 3))),
end: None,
kind: RangeKind::SizeLimited, }
);
}
trait RangeHelper {
fn kind_of_range_kind(&self) -> RangeKind;
}
impl RangeHelper for ExprKind {
fn kind_of_range_kind(&self) -> RangeKind {
match self {
ExprKind::Range { kind, .. } => *kind,
_ => panic!("expected range"),
}
}
}
#[test]
fn relational_in_parens() {
assert_eq!(
expr_of("(a > b)"),
ExprKind::Paren(Box::new(Expr::new(
ExprKind::Gt(
Box::new(Expr::new(ident("a"), Span::new(1, 4))),
Box::new(Expr::new(ident("b"), Span::new(1, 8))),
),
Span::new(1, 4)
)))
);
}