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
3d6f280 1
#![allow(non_snake_case)]
3d6f280 2
use plum_wasm_codegen::compileSource;
0e1801a 3
use plum_core::AstParser;
0e1801a 4
3d6f280 5
fn examplesDir() -> std::path::PathBuf {
0e1801a 6
    std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples")
0e1801a 7
}
0e1801a 8
3d6f280 9
fn parseFile(name: &str) -> plum_core::ast::Source {
3d6f280 10
    let path = examplesDir().join(name);
0e1801a 11
    let src = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e));
0e1801a 12
    let mut parser = tree_sitter::Parser::new();
0e1801a 13
    parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
0e1801a 14
    let tree = parser.parse(&src, None).unwrap_or_else(|| panic!("failed to parse {}", path.display()));
0e1801a 15
    assert!(!tree.root_node().has_error(), "{} has a parse error", path.display());
0e1801a 16
    let ap = AstParser::new(&src);
3d6f280 17
    ap.parseSource(tree.root_node())
0e1801a 18
}
0e1801a 19
5ef4579 20
/// See the identically-named helper in codegen_tests.rs for why both features
5ef4579 21
/// are needed (part of the wasm-gc migration, docs/superpowers/plans/2026-07-25-wasm-gc-migration.md).
5ef4579 22
fn gcEngine() -> wasmtime::Engine {
5ef4579 23
    let mut config = wasmtime::Config::new();
5ef4579 24
    config.wasm_gc(true);
5ef4579 25
    config.wasm_function_references(true);
5ef4579 26
    wasmtime::Engine::new(&config).expect("engine with GC config should construct")
5ef4579 27
}
5ef4579 28
3d6f280 29
fn assertCompiles(name: &str) -> Vec<u8> {
3d6f280 30
    let source = parseFile(name);
3d6f280 31
    let bytes = compileSource(&source).unwrap_or_else(|e| panic!("{} failed to compile: {}", name, e));
0e1801a 32
    let result = wasmparser::validate(&bytes);
0e1801a 33
    assert!(result.is_ok(), "{} produced invalid wasm: {:?}", name, result.err());
0e1801a 34
    bytes
0e1801a 35
}
0e1801a 36
0e1801a 37
/// Examples that stick to currently-supported codegen features (primitives, control
0e1801a 38
/// flow, classes/methods) must actually compile to valid wasm — not just parse and
0e1801a 39
/// type-check.
0e1801a 40
#[test]
3d6f280 41
fn basicsCompiles() {
3d6f280 42
    assertCompiles("basics.plum");
0e1801a 43
}
0e1801a 44
0e1801a 45
#[test]
3d6f280 46
fn controlFlowCompiles() {
3d6f280 47
    assertCompiles("control_flow.plum");
0e1801a 48
}
0e1801a 49
0e1801a 50
#[test]
3d6f280 51
fn functionsCompiles() {
3d6f280 52
    assertCompiles("functions.plum");
0e1801a 53
}
0e1801a 54
0e1801a 55
#[test]
3d6f280 56
fn typesCompiles() {
0e1801a 57
    // Only class/trait/enum declarations, no function bodies to lower — should still
0e1801a 58
    // produce a valid (if unexciting) module.
3d6f280 59
    assertCompiles("types.plum");
0e1801a 60
}
0e1801a 61
0e1801a 62
#[test]
3d6f280 63
fn methodsCompilesAndRunsCorrectly() {
3d6f280 64
    let bytes = assertCompiles("methods.plum");
0e1801a 65
5ef4579 66
    let engine = gcEngine();
0e1801a 67
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
0e1801a 68
    let mut store = wasmtime::Store::new(&engine, ());
0e1801a 69
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
0e1801a 70
    let main = instance
0e1801a 71
        .get_typed_func::<(), i64>(&mut store, "main")
0e1801a 72
        .expect("main should have signature () -> i64");
0e1801a 73
    let result = main.call(&mut store, ()).expect("main should not trap");
0e1801a 74
    // makeCat() -> Cat(age: 3); a = getAge() = 3; b = birthday() = 4;
0e1801a 75
    // w.innerAge() = inner.age = 3 => 3 + 4 + 3 = 10
0e1801a 76
    assert_eq!(result, 10);
0e1801a 77
}
0e1801a 78
fe36a46 79
/// match.plum now exercises fully-supported syntax (general enum tag and
fe36a46 80
/// constructor patterns) and must compile and run correctly end to end.
0e1801a 81
#[test]
3d6f280 82
fn matchExampleCompilesAndRunsCorrectly() {
3d6f280 83
    let bytes = assertCompiles("match.plum");
5ef4579 84
    let engine = gcEngine();
fe36a46 85
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
fe36a46 86
    let mut store = wasmtime::Store::new(&engine, ());
fe36a46 87
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
fe36a46 88
    let main = instance
fe36a46 89
        .get_typed_func::<(), i64>(&mut store, "main")
fe36a46 90
        .expect("main should have signature () -> i64");
fe36a46 91
    let result = main.call(&mut store, ()).expect("main should not trap");
fe36a46 92
    // describeOption(Some(5)) = 5
fe36a46 93
    assert_eq!(result, 5);
0e1801a 94
}
0e1801a 95
6341c74 96
/// closures.plum exercises non-capturing closures, capturing closures (snapshot-by-value),
6341c74 97
/// and closures passed directly as call arguments.
6341c74 98
#[test]
3d6f280 99
fn closuresExampleCompilesAndRunsCorrectly() {
3d6f280 100
    let bytes = assertCompiles("closures.plum");
5ef4579 101
    let engine = gcEngine();
6341c74 102
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
6341c74 103
    let mut store = wasmtime::Store::new(&engine, ());
6341c74 104
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
6341c74 105
    let main = instance
6341c74 106
        .get_typed_func::<(), i64>(&mut store, "main")
6341c74 107
        .expect("main should have signature () -> i64");
6341c74 108
    // each(|v| v * 3) = 5 * 3 = 15; useCapturingClosure() = cb(5) with offset=100 snapshot
6341c74 109
    // captured at creation = 5 + 100 = 105; total = 15 + 105 = 120.
6341c74 110
    assert_eq!(main.call(&mut store, ()).expect("main should not trap"), 120);
6341c74 111
}
6341c74 112
35af6cf 113
/// strings.plum exercises string interpolation (plain literals, `Str`/`Int`
35af6cf 114
/// interpolation, and escape sequences) — it has no `main` to execute, so (matching
3d6f280 115
/// `basicsCompiles`/`controlFlowCompiles`/etc. above for other main-less examples)
35af6cf 116
/// this only checks that it compiles to a valid module.
0e1801a 117
#[test]
3d6f280 118
fn stringsExampleCompiles() {
3d6f280 119
    assertCompiles("strings.plum");
0e1801a 120
}
0000000 121
0000000 122
/// io.plum exercises `extern fun` (see `Fn.is_extern`/`FnBody::Extern` in
0000000 123
/// plum-core/src/ast.rs and their handling in plum-wasm-codegen/src/lib.rs) — it
0000000 124
/// declares its own `extern fun printLn(s: Str)` (redeclared locally rather than a
0000000 125
/// real `import std/os`, to keep this fixture self-contained for the single-file
0000000 126
/// parse/check this test and plum-checker's examples test do), which codegen turns
0000000 127
/// into a genuine wasm `(import "plum" "printLn" ...)` instead of a compiled body.
0000000 128
/// Runs it for real, providing the import as a host function that records what
0000000 129
/// it's called with, to prove both the wasm-level import wiring AND the actual
0000000 130
/// bytes handed across the host boundary are correct.
0000000 131
#[test]
0000000 132
fn ioExampleCompilesAndRunsCorrectly() {
0000000 133
    let bytes = assertCompiles("io.plum");
0000000 134
    let engine = gcEngine();
0000000 135
    let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
0000000 136
    let mut store = wasmtime::Store::new(&engine, ());
0000000 137
0000000 138
    let printed: std::sync::Arc<std::sync::Mutex<Vec<String>>> = Default::default();
0000000 139
    let printed_for_closure = printed.clone();
0000000 140
    let import_ty = module
0000000 141
        .imports()
0000000 142
        .find(|imp| imp.module() == "plum" && imp.name() == "printLn")
0000000 143
        .expect("io.plum should import plum::printLn")
0000000 144
        .ty()
0000000 145
        .func()
0000000 146
        .cloned()
0000000 147
        .expect("plum::printLn import should be a function");
0000000 148
    let print_ln = wasmtime::Func::new(&mut store, import_ty, move |mut caller, params, _results| {
0000000 149
        let wasmtime::Val::AnyRef(Some(s)) = &params[0] else {
0000000 150
            return Err(wasmtime::Error::msg("printLn expects a Str argument"));
0000000 151
        };
0000000 152
        let arr = s.unwrap_array(&caller)?;
0000000 153
        let len = arr.len(&caller)?;
0000000 154
        let mut buf = vec![0u8; len as usize];
0000000 155
        arr.copy_to_i8_slice(&mut caller, &mut buf)?;
0000000 156
        printed_for_closure.lock().unwrap().push(String::from_utf8_lossy(&buf).into_owned());
0000000 157
        Ok(())
0000000 158
    });
0000000 159
0000000 160
    let instance = wasmtime::Instance::new(&mut store, &module, &[wasmtime::Extern::Func(print_ln)])
0000000 161
        .expect("module should instantiate");
0000000 162
    let main = instance
0000000 163
        .get_typed_func::<(), ()>(&mut store, "main")
0000000 164
        .expect("main should have signature () -> ()");
0000000 165
    main.call(&mut store, ()).expect("main should not trap");
0000000 166
0000000 167
    assert_eq!(*printed.lock().unwrap(), vec!["hello from plum".to_string(), "hello, world!".to_string()]);
0000000 168
}