use super::*; use crate::sexpr; fn parse(source: &str) -> Expr { let doc = sexpr::read(source).expect("reads"); from_sexpr(&doc.values[0], source).expect("valid expression") } fn parse_err(source: &str) -> Diagnostic { let doc = sexpr::read(source).expect("reads"); from_sexpr(&doc.values[0], source).expect_err("expected an error") } #[test] fn literals_and_paths() { assert!(matches!(parse("42"), Expr::Int(42, _))); assert!(matches!(parse("true"), Expr::Bool(true, _))); let Expr::Path(segments, _) = parse("form.busy") else { panic!() }; assert_eq!(segments, ["form", "busy"]); } #[test] fn nary_folds_left() { let Expr::Binary(BinOp::Add, lhs, _, _) = parse("(+ 1 2 3)") else { panic!() }; assert!(matches!(*lhs, Expr::Binary(BinOp::Add, ..))); } #[test] fn unary_minus_vs_binary() { assert!(matches!(parse("(- x)"), Expr::Unary(UnOp::Neg, ..))); assert!(matches!(parse("(- x 1)"), Expr::Binary(BinOp::Sub, ..))); } #[test] fn if_and_not() { assert!(matches!(parse("(if hovered 1 0)"), Expr::If(..))); assert!(matches!(parse("(not busy)"), Expr::Unary(UnOp::Not, ..))); let err = parse_err("(if x 1)"); assert!(err.message.contains("exactly 3")); } #[test] fn unknown_head_parses_as_a_call() { // Non-operator heads are handler invocations; whether the name is a // declared handler — and whether a call is legal in this position — // is validation's decision, with spans intact. let Expr::Call(name, args, _) = parse("(frobnicate x)") else { panic!("expected a call"); }; assert_eq!(name, "frobnicate"); assert_eq!(args.len(), 1); } #[test] fn templates_scan_refs_with_file_spans() { let source = r#""Count: {count}!""#; let Expr::Str(template, _) = parse(source) else { panic!() }; assert_eq!( template.segments, vec![ Segment::Literal("Count: ".into()), Segment::Ref("count".into(), sexpr::Span::new(9, 14)), Segment::Literal("!".into()), ] ); // The span points exactly at `count` in the source. assert_eq!(&source[9..14], "count"); } #[test] fn template_escapes() { let Expr::Str(template, _) = parse(r#""{{literal}} \{also} {x}""#) else { panic!() }; assert_eq!( template.segments, vec![ Segment::Literal("{literal} {also} ".into()), Segment::Ref("x".into(), sexpr::Span::new(22, 23)), ] ); } #[test] fn template_errors() { assert!(parse_err(r#""{unclosed""#).message.contains("unclosed")); assert!(parse_err(r#""{}""#).message.contains("empty")); assert!( parse_err(r#""{a b}""#) .message .contains("interpolations hold a name") ); } #[test] fn literalness() { assert!(parse("(+ 1 (* 2 3))").is_literal()); assert!(parse(r#""plain""#).is_literal()); assert!(!parse(r#""{count}""#).is_literal()); assert!(!parse("(if hovered 1 0)").is_literal()); } #[test] fn referenced_names_are_collected() { let mut names = Vec::new(); parse(r#"(if pressed "{a}" "{b}")"#).referenced_names(&mut names); let just_names: Vec<_> = names.iter().map(|(n, _)| n.as_str()).collect(); assert_eq!(just_names, ["pressed", "a", "b"]); }