//! #44: a `$` in an authored Form-A identifier is rejected at the //! lexer and surfaces through `parse` as `ParseError::Lex`. Pins that //! the reservation reaches the public `parse` entry point (not just //! the internal `tokenize`), so a hallucinating Form-A author cannot //! land a `$` binder in a `Module`. // `parse` and `ParseError` are re-exported at the crate root // (`pub use` in lib.rs); `LexError` is not, but `lex` is a public // module, so it is reachable as `ailang_surface::lex::LexError`. use ailang_surface::lex::LexError; use ailang_surface::{parse, ParseError}; #[test] fn dollar_binder_in_module_is_rejected_at_parse() { // (module bad (fn main (type (fn-type (params) (ret (own (con Int))))) (params) (body (let x$1 7 x$1)))) let src = "(module bad\n (fn main\n (type (fn-type (params) (ret (own (con Int)))))\n (params)\n (body\n (let x$1 7 x$1))))\n"; let err = parse(src).expect_err("authored `$` binder must be rejected at parse"); assert!( matches!(err, ParseError::Lex(LexError::ReservedDollar { ref token, .. }) if token == "x$1"), "expected ParseError::Lex(ReservedDollar(\"x$1\")), got {err:?}", ); } #[test] fn dollar_in_string_in_module_parses_clean() { // The exemption survives at the `parse` level too: `$` inside a // string literal in a real module is fine. let src = "(module dollar_ok\n (fn main\n (type (fn-type (params) (ret (own (con Str)))))\n (params)\n (body \"price: $5\")))\n"; parse(src).expect("string-internal `$` must parse clean"); }