lispexp 0.7.0

A pure-Rust reader (lexer + parser) for S-expression syntax across many Lisp dialects
Documentation
//! Options/CharRoles spot-checks.

use lispexp::{CharRoles, Dialect, Options};

#[test]
fn janet_roles_splice_and_mutable() {
    let opts = Options::for_dialect(Dialect::Janet);
    assert_eq!(opts.roles.splice, Some(';'));
    assert_eq!(opts.roles.mutable, Some('@'));
    assert_eq!(opts.roles.quasiquote, Some('~'));
    assert_eq!(opts.roles.short_fn, Some('|'));
}

#[test]
fn clojure_roles_deref_and_meta() {
    let opts = Options::for_dialect(Dialect::Clojure);
    assert_eq!(opts.roles.deref, Some('@'));
    assert_eq!(opts.roles.meta, Some('^'));
    assert_eq!(opts.roles.unquote, Some('~'));
}

#[test]
fn scheme_base_has_no_clojure_extras() {
    let roles = CharRoles::scheme();
    assert_eq!(roles.deref, None);
    assert_eq!(roles.meta, None);
    assert_eq!(roles.splice, None);
    assert_eq!(roles.mutable, None);
    assert_eq!(roles.short_fn, None);
    assert_eq!(roles.quote, Some('\''));
    assert_eq!(roles.quasiquote, Some('`'));
    assert_eq!(roles.unquote, Some(','));
    assert_eq!(roles.splicing_suffix, '@');
}

#[test]
fn clojure_base_extends_scheme_quote_family() {
    let roles = CharRoles::clojure();
    assert_eq!(roles.quote, Some('\''));
    assert_eq!(roles.quasiquote, Some('`'));
    assert_eq!(roles.unquote, Some('~'));
    assert_eq!(roles.deref, Some('@'));
    assert_eq!(roles.meta, Some('^'));
}

#[test]
fn edn_has_no_quote_family_glyphs() {
    let opts = Options::for_dialect(Dialect::Edn);
    assert_eq!(opts.roles.quote, None);
    assert_eq!(opts.roles.quasiquote, None);
    assert_eq!(opts.roles.unquote, None);
    assert_eq!(opts.roles.deref, None);
    assert_eq!(opts.roles.meta, None);
}

#[test]
fn dialect_display_and_from_str_round_trip_over_all() {
    use std::str::FromStr;
    for &dialect in Dialect::ALL {
        let name = dialect.to_string();
        let parsed = Dialect::from_str(&name).unwrap_or_else(|e| {
            panic!("failed to parse Dialect::{dialect:?}'s Display form {name:?}: {e}")
        });
        assert_eq!(parsed, dialect);
    }
}

#[test]
fn dialect_display_is_kebab_case() {
    assert_eq!(Dialect::CommonLisp.to_string(), "common-lisp");
    assert_eq!(Dialect::EmacsLisp.to_string(), "emacs-lisp");
    assert_eq!(Dialect::SchemeSuperset.to_string(), "scheme-superset");
    assert_eq!(Dialect::Scheme.to_string(), "scheme");
}

#[test]
fn dialect_from_str_rejects_unknown() {
    use std::str::FromStr;
    let err = Dialect::from_str("not-a-real-dialect").unwrap_err();
    assert!(err.to_string().contains("not-a-real-dialect"));
}

#[test]
fn dialect_options_matches_for_dialect() {
    assert_eq!(
        Dialect::Janet.options(),
        Options::for_dialect(Dialect::Janet)
    );
}

#[test]
fn scheme_variant_dialects_resolve_to_the_shared_superset() {
    // Gauche/Mosh/Gambit are named entry points to the one shared tolerant
    // .scm reader — they select the same Options as SchemeSuperset (ADR-0027).
    let superset = Options::for_dialect(Dialect::SchemeSuperset);
    for d in [Dialect::Gauche, Dialect::Mosh, Dialect::Gambit] {
        assert_eq!(
            Options::for_dialect(d),
            superset,
            "{d:?} should resolve to the shared scheme_superset reader"
        );
        assert_eq!(d.options(), superset);
    }
}

#[test]
fn scheme_variant_dialects_round_trip_by_name() {
    use std::str::FromStr;
    assert_eq!(Dialect::from_str("gauche").unwrap(), Dialect::Gauche);
    assert_eq!(Dialect::from_str("mosh").unwrap(), Dialect::Mosh);
    assert_eq!(Dialect::from_str("gambit").unwrap(), Dialect::Gambit);
    assert_eq!(Dialect::Gauche.to_string(), "gauche");
    // They are listed in ALL so consumers can enumerate them.
    for d in [Dialect::Gauche, Dialect::Mosh, Dialect::Gambit] {
        assert!(Dialect::ALL.contains(&d), "{d:?} missing from Dialect::ALL");
    }
}