sapphire-lang 0.8.0

Gradually typed scripting language where every value is an object and types are optional, checked at runtime
Documentation
use sapphire::compiler::compile;
use sapphire::lexer::Lexer;
use sapphire::parser::Parser;
use sapphire::vm::{Vm, VmValue};

fn eval(src: &str) -> VmValue {
    let tokens = Lexer::new(src).scan_tokens();
    let stmts = Parser::new(tokens).parse().expect("parse error");
    let func = compile(&stmts).expect("compile error");
    Vm::new(func, std::path::PathBuf::new())
        .run()
        .expect("vm error")
        .expect("empty stack")
}

fn parse_err_msg(src: &str) -> String {
    let tokens = sapphire::lexer::Lexer::new(src).scan_tokens();
    let err = sapphire::parser::Parser::new(tokens)
        .parse()
        .expect_err("expected parse error");
    format!("{}", err)
}

#[test]
fn match_list_literal_element() {
    let result = eval(r#"match [42] { [42] => { "yes" } _ => { "no" } }"#);
    assert_eq!(result, VmValue::Str("yes".into()));
    let no = eval(r#"match [99] { [42] => { "yes" } _ => { "no" } }"#);
    assert_eq!(no, VmValue::Str("no".into()));
}

#[test]
fn match_list_range_element() {
    let b = eval(r#"match [85] { [90..100] => { "A" } [80..89] => { "B" } _ => { "F" } }"#);
    assert_eq!(b, VmValue::Str("B".into()));
}

#[test]
fn match_nested() {
    let src = r#"
outer = match 1 {
  1 => {
    match "a" {
      "a" => { "one-a" }
      _   => { "one-other" }
    }
  }
  _ => { "other" }
}
outer
"#;
    assert_eq!(eval(src), VmValue::Str("one-a".into()));
}

#[test]
fn match_missing_wildcard_parse_error() {
    let msg = parse_err_msg("match 1 { 1 => { \"one\" } }");
    assert!(
        msg.contains("exhaustive") || msg.contains("wildcard") || msg.contains("_"),
        "expected exhaustiveness error, got: {msg}"
    );
}