//! #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");
}
