plum

#treesitter#compiler#wasm

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

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


50effc5Peter John 2026-07-19T19:31:02+05:30
feat(plum-cli): add compile subcommand wiring checker and codegen
plum-cli/Cargo.toml CHANGED
@@ -9,5 +9,9 @@ path = "src/main.rs"
9
9
 
10
10
  [dependencies]
11
11
  plum-core = { path = "../plum-core" }
12
+ plum-checker = { path = "../plum-checker" }
13
+ plum-wasm-codegen = { path = "../plum-wasm-codegen" }
12
14
  clap = { version = "4", features = ["derive"] }
13
15
  anyhow = "1"
16
+ tree-sitter = "0.26"
17
+ tree-sitter-plum = { path = "../tooling/tree-sitter-plum" }
plum-cli/src/main.rs CHANGED
@@ -6,6 +6,7 @@ use anyhow::{Context, Result};
6
6
  use clap::{Parser, Subcommand};
7
7
 
8
8
  use plum_core::format_source;
9
+ use plum_core::AstParser;
9
10
 
10
11
  #[derive(Parser)]
11
12
  #[command(name = "plum", about = "The Plum language toolchain")]
@@ -27,6 +28,14 @@ enum Command {
27
28
  #[arg(long)]
28
29
  stdin: bool,
29
30
  },
31
+ /// Compile a Plum source file to WASM
32
+ Compile {
33
+ /// Source file to compile
34
+ file: std::path::PathBuf,
35
+ /// Output path (default: input with .wasm extension)
36
+ #[arg(short, long)]
37
+ output: Option<std::path::PathBuf>,
38
+ },
30
39
  }
31
40
 
32
41
  fn main() {
@@ -40,6 +49,7 @@ fn run() -> Result<()> {
40
49
  let cli = Cli::parse();
41
50
  match cli.command {
42
51
  Command::Format { file, check, stdin } => cmd_format(file, check, stdin),
52
+ Command::Compile { file, output } => cmd_compile(file, output),
43
53
  }
44
54
  }
45
55
 
@@ -80,3 +90,39 @@ fn cmd_format(
80
90
  }
81
91
  Ok(())
82
92
  }
93
+
94
+ fn cmd_compile(file: std::path::PathBuf, output: Option<std::path::PathBuf>) -> Result<()> {
95
+ let source = fs::read_to_string(&file)
96
+ .with_context(|| format!("failed to read {}", file.display()))?;
97
+
98
+ // Parse
99
+ let mut parser = tree_sitter::Parser::new();
100
+ parser
101
+ .set_language(&tree_sitter_plum::LANGUAGE.into())
102
+ .map_err(|e| anyhow::anyhow!("language error: {e}"))?;
103
+ let tree = parser
104
+ .parse(&source, None)
105
+ .ok_or_else(|| anyhow::anyhow!("parse failed"))?;
106
+ let ap = AstParser::new(&source);
107
+ let ast = ap.parse_source(tree.root_node());
108
+
109
+ // Type check
110
+ if let Err(errors) = plum_checker::check_source(&ast) {
111
+ for e in &errors {
112
+ eprintln!("type error: {}", e.message);
113
+ }
114
+ process::exit(1);
115
+ }
116
+
117
+ // Codegen
118
+ let wasm_bytes = plum_wasm_codegen::compile_source(&ast)
119
+ .map_err(|e| anyhow::anyhow!("codegen error: {e}"))?;
120
+
121
+ // Write output
122
+ let out_path = output.unwrap_or_else(|| file.with_extension("wasm"));
123
+ fs::write(&out_path, &wasm_bytes)
124
+ .with_context(|| format!("failed to write {}", out_path.display()))?;
125
+
126
+ eprintln!("compiled {} → {}", file.display(), out_path.display());
127
+ Ok(())
128
+ }
plum-cli/tests/compile_tests.rs ADDED
@@ -0,0 +1,24 @@
1
+ use std::process::Command;
2
+
3
+ fn plum_bin() -> std::path::PathBuf {
4
+ let mut path = std::env::current_exe().unwrap();
5
+ path.pop(); // remove test binary
6
+ if path.ends_with("deps") {
7
+ path.pop();
8
+ }
9
+ path.push("plum");
10
+ path
11
+ }
12
+
13
+ #[test]
14
+ fn compile_simple_add_produces_wasm() {
15
+ let src_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../test/simple_add.plum");
16
+ let out_path = "/tmp/add_test_output.wasm";
17
+ let status = Command::new(plum_bin())
18
+ .args(["compile", src_path, "-o", out_path])
19
+ .status()
20
+ .expect("failed to run plum compile");
21
+ assert!(status.success(), "plum compile exited with: {}", status);
22
+ let bytes = std::fs::read(out_path).expect("output wasm not found");
23
+ assert_eq!(&bytes[0..4], b"\0asm");
24
+ }
test/simple_add.plum ADDED
@@ -0,0 +1,2 @@
1
+ add(a: Int, b: Int) -> Int =
2
+ a + b