plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


plum-wasm-codegen/tests/examples_test.rs
#![allow(non_snake_case)]
use plum_wasm_codegen::compileSource;
use plum_core::AstParser;

fn examplesDir() -> std::path::PathBuf {
    std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples")
}

fn parseFile(name: &str) -> plum_core::ast::Source {
    let path = examplesDir().join(name);
    let src = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e));
    let mut parser = tree_sitter::Parser::new();
    parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
    let tree = parser.parse(&src, None).unwrap_or_else(|| panic!("failed to parse {}", path.display()));
    assert!(!tree.root_node().has_error(), "{} has a parse error", path.display());
    let ap = AstParser::new(&src);
    ap.parseSource(tree.root_node())
}

/// See the identically-named helper in codegen_tests.rs for why both features
/// are needed (part of the wasm-gc migration, docs/superpowers/plans/2026-07-25-wasm-gc-migration.md).
fn gcEngine() -> wasmtime::Engine {
    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    wasmtime::Engine::new(&config).expect("engine with GC config should construct")
}

fn assertCompiles(name: &str) -> Vec<u8> {
    let source = parseFile(name);
    let bytes = compileSource(&source).unwrap_or_else(|e| panic!("{} failed to compile: {}", name, e));
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "{} produced invalid wasm: {:?}", name, result.err());
    bytes
}

/// Examples that stick to currently-supported codegen features (primitives, control
/// flow, classes/methods) must actually compile to valid wasm — not just parse and
/// type-check.
#[test]
fn basicsCompiles() {
    assertCompiles("basics.plum");
}

#[test]
fn controlFlowCompiles() {
    assertCompiles("control_flow.plum");
}

#[test]
fn functionsCompiles() {
    assertCompiles("functions.plum");
}

#[test]
fn typesCompiles() {
    // Only class/trait/enum declarations, no function bodies to lower — should still
    // produce a valid (if unexciting) module.
    assertCompiles("types.plum");
}

#[test]
fn methodsCompilesAndRunsCorrectly() {
    let bytes = assertCompiles("methods.plum");

    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
    let main = instance
        .get_typed_func::<(), i64>(&mut store, "main")
        .expect("main should have signature () -> i64");
    let result = main.call(&mut store, ()).expect("main should not trap");
    // makeCat() -> Cat(age: 3); a = getAge() = 3; b = birthday() = 4;
    // w.innerAge() = inner.age = 3 => 3 + 4 + 3 = 10
    assert_eq!(result, 10);
}

/// match.plum now exercises fully-supported syntax (general enum tag and
/// constructor patterns) and must compile and run correctly end to end.
#[test]
fn matchExampleCompilesAndRunsCorrectly() {
    let bytes = assertCompiles("match.plum");
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
    let main = instance
        .get_typed_func::<(), i64>(&mut store, "main")
        .expect("main should have signature () -> i64");
    let result = main.call(&mut store, ()).expect("main should not trap");
    // describeOption(Some(5)) = 5
    assert_eq!(result, 5);
}

/// closures.plum exercises non-capturing closures, capturing closures (snapshot-by-value),
/// and closures passed directly as call arguments.
#[test]
fn closuresExampleCompilesAndRunsCorrectly() {
    let bytes = assertCompiles("closures.plum");
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
    let main = instance
        .get_typed_func::<(), i64>(&mut store, "main")
        .expect("main should have signature () -> i64");
    // each(|v| v * 3) = 5 * 3 = 15; useCapturingClosure() = cb(5) with offset=100 snapshot
    // captured at creation = 5 + 100 = 105; total = 15 + 105 = 120.
    assert_eq!(main.call(&mut store, ()).expect("main should not trap"), 120);
}

/// strings.plum exercises string interpolation (plain literals, `Str`/`Int`
/// interpolation, and escape sequences) — it has no `main` to execute, so (matching
/// `basicsCompiles`/`controlFlowCompiles`/etc. above for other main-less examples)
/// this only checks that it compiles to a valid module.
#[test]
fn stringsExampleCompiles() {
    assertCompiles("strings.plum");
}

/// io.plum exercises `extern fun` (see `Fn.is_extern`/`FnBody::Extern` in
/// plum-core/src/ast.rs and their handling in plum-wasm-codegen/src/lib.rs) — it
/// declares its own `extern fun printLn(s: Str)` (redeclared locally rather than a
/// real `import std/os`, to keep this fixture self-contained for the single-file
/// parse/check this test and plum-checker's examples test do), which codegen turns
/// into a genuine wasm `(import "plum" "printLn" ...)` instead of a compiled body.
/// Runs it for real, providing the import as a host function that records what
/// it's called with, to prove both the wasm-level import wiring AND the actual
/// bytes handed across the host boundary are correct.
#[test]
fn ioExampleCompilesAndRunsCorrectly() {
    let bytes = assertCompiles("io.plum");
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());

    let printed: std::sync::Arc<std::sync::Mutex<Vec<String>>> = Default::default();
    let printed_for_closure = printed.clone();
    let import_ty = module
        .imports()
        .find(|imp| imp.module() == "plum" && imp.name() == "printLn")
        .expect("io.plum should import plum::printLn")
        .ty()
        .func()
        .cloned()
        .expect("plum::printLn import should be a function");
    let print_ln = wasmtime::Func::new(&mut store, import_ty, move |mut caller, params, _results| {
        let wasmtime::Val::AnyRef(Some(s)) = &params[0] else {
            return Err(wasmtime::Error::msg("printLn expects a Str argument"));
        };
        let arr = s.unwrap_array(&caller)?;
        let len = arr.len(&caller)?;
        let mut buf = vec![0u8; len as usize];
        arr.copy_to_i8_slice(&mut caller, &mut buf)?;
        printed_for_closure.lock().unwrap().push(String::from_utf8_lossy(&buf).into_owned());
        Ok(())
    });

    let instance = wasmtime::Instance::new(&mut store, &module, &[wasmtime::Extern::Func(print_ln)])
        .expect("module should instantiate");
    let main = instance
        .get_typed_func::<(), ()>(&mut store, "main")
        .expect("main should have signature () -> ()");
    main.call(&mut store, ()).expect("main should not trap");

    assert_eq!(*printed.lock().unwrap(), vec!["hello from plum".to_string(), "hello, world!".to_string()]);
}