plum
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)) = ¶ms[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()]);
}