pub mod token;
pub mod lexer;
pub mod ast;
pub mod parser;
pub mod typecheck;
pub mod codegen;
pub mod loader;
pub fn compile(source: &str) -> Result<Vec<u8>, CompileError> {
let tokens = lexer::lex(source)?;
let module = parser::parse(&tokens)?;
let typed = typecheck::check(&module)?;
let wasm = codegen::emit(&typed)?;
Ok(wasm)
}
#[derive(Debug, Clone)]
pub struct CompileError {
pub message: String,
pub span: Option<Span>,
pub code: Option<u16>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(code) = self.code {
write!(f, "{}: ", crate::error_codes::fmt_label(code))?;
}
if let Some(span) = self.span {
write!(f, "{} [{}..{}]", self.message, span.start, span.end)
} else {
write!(f, "{}", self.message)
}
}
}
impl std::error::Error for CompileError {}
impl CompileError {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into(), span: None, code: None }
}
pub fn at(message: impl Into<String>, span: Span) -> Self {
Self { message: message.into(), span: Some(span), code: None }
}
pub fn at_code(code: u16, message: impl Into<String>, span: Span) -> Self {
Self { message: message.into(), span: Some(span), code: Some(code) }
}
pub fn new_code(code: u16, message: impl Into<String>) -> Self {
Self { message: message.into(), span: None, code: Some(code) }
}
pub fn with_code(mut self, code: u16) -> Self {
self.code = Some(code);
self
}
}
impl From<String> for CompileError {
fn from(s: String) -> Self { Self::new(s) }
}
#[cfg(test)]
mod tests {
use super::{compile, lexer, parser, typecheck};
use crate::error_codes as codes;
fn compile_err(src: &str) -> super::CompileError {
lexer::lex(src)
.and_then(|toks| parser::parse(&toks))
.and_then(|m| typecheck::check(&m))
.and_then(|t| super::codegen::emit(&t))
.expect_err("expected a compile error")
}
#[test]
fn compile_errors_carry_their_lh0xxx_code() {
let e = compile_err("fn frame(t: i32) { let x = true + 1; host::display::present(); }");
assert_eq!(e.code, Some(codes::TYPE_MISMATCH), "{e}");
assert!(e.to_string().starts_with("LH0204:"), "surfaced: {e}");
let e = compile_err("fn frame(t: i32) { host::display::clear(NOPE); host::display::present(); }");
assert_eq!(e.code, Some(codes::UNDEFINED_VARIABLE), "{e}");
let e = compile_err("fn (t: i32) {}");
assert_eq!(e.code, Some(codes::UNEXPECTED_TOKEN), "{e}");
let e = compile_err("fn frame(t: i32) { 5 = 9; host::display::present(); }");
assert_eq!(e.code, Some(codes::INVALID_ASSIGN_TARGET), "{e}");
let e = compile_err("fn frame(t: i32) { host::display::nope(1); host::display::present(); }");
assert_eq!(e.code, Some(codes::UNKNOWN_FUNCTION), "{e}");
let e = compile_err("fn frame(t: i32) { let x = `; }");
assert_eq!(e.code, Some(codes::UNEXPECTED_BYTE), "{e}");
let e = compile_err("fn frame(t: i32) { let x = true as i32; host::display::clear(x); host::display::present(); }");
assert_eq!(e.code, Some(codes::BAD_CAST), "{e}");
assert!(e.to_string().starts_with("LH0"), "surfaced: {e}");
}
#[test]
fn const_resolves_and_is_order_independent() {
assert!(compile(
"fn frame(t: i32) { host::display::clear(W); host::display::present(); } const W: i32 = 256;"
)
.is_ok());
assert!(compile(
"const A: i32 = 2; const B: i32 = A * 3; fn frame(t: i32) { host::display::clear(B); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn frame(t: i32) { host::display::clear(NOPE); host::display::present(); }"
)
.is_err());
}
#[test]
fn casts_between_numbers() {
assert!(compile(
"fn frame(t: i32) { let x = t as f64; let y = x as i32; host::display::clear(y + (3.7 as i32)); host::display::present(); }"
)
.is_ok());
}
#[test]
fn arrays_literal_and_index() {
assert!(compile(
"fn frame(t: i32) { let pal = [16711680, 65280, 255]; host::display::clear(pal[t % 3]); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn frame(t: i32) { let x = 5; host::display::clear(x[0]); host::display::present(); }"
)
.is_err());
assert!(compile(
"fn frame(t: i32) { let mut a = [1, 2, 3]; a[0] = 9; host::display::clear(a[0]); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn frame(t: i32) { let mut a = [0, 0, 0, 0]; a[t % 4] = t; host::display::clear(a[t % 4]); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn frame(t: i32) { let mut a = [1, 2, 3]; a[0] = true; host::display::present(); }"
)
.is_err());
assert!(compile(
"fn frame(t: i32) { let a = [1, 2, 3]; a[0] = 9; host::display::present(); }"
)
.is_err());
assert!(compile(
"fn frame(t: i32) { let mut x = 5; x[0] = 9; host::display::present(); }"
)
.is_err());
}
#[test]
fn array_params_and_repeat_init() {
assert!(compile(
"fn sum3(a: [i32; 3]) -> i32 { a[0] + a[1] + a[2] } \
fn frame(t: i32) { let g = [10, 20, 30]; host::display::clear(sum3(g)); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn set0(a: [i32; 3], v: i32) { a[0] = v; } \
fn frame(t: i32) { let mut g = [0, 0, 0]; set0(g, 7); host::display::clear(g[0]); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn frame(t: i32) { let mut g = [0; 64]; g[5] = 9; host::display::clear(g[5]); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn frame(t: i32) { let g = [t * 2; 8]; host::display::clear(g[3]); host::display::present(); }"
)
.is_ok());
assert!(compile(
"fn frame(t: i32) { let g = [0; 0]; host::display::present(); }"
)
.is_err());
assert!(compile(
"fn f(a: [bool; 2]) {} fn frame(t: i32) { host::display::present(); }"
)
.is_err());
assert!(compile(
"fn frame(t: i32) { let g = [true; 4]; host::display::present(); }"
)
.is_err());
}
#[test]
fn array_return_type_is_rejected() {
let e = compile("fn mk(v: i32) -> [i32; 3] { [v, v, v] } fn frame(t: i32) { let a = mk(1); host::display::clear(a[0]); host::display::present(); }")
.expect_err("array return must be rejected");
assert_eq!(e.code, Some(codes::UNSUPPORTED_FEATURE), "{e}");
assert!(compile(
"fn frame(t: i32) { host::display::present(); } fn mk() -> [i32; 2] { [1, 2] }"
)
.is_err());
assert!(compile(
"fn fill(a: [i32; 3], v: i32) -> i32 { a[0] = v; a[0] } \
fn frame(t: i32) { let mut g = [0, 0, 0]; host::display::clear(fill(g, 9)); host::display::present(); }"
)
.is_ok());
}
}
#[cfg(all(test, feature = "native"))]
mod array_write_run_proof {
use super::compile;
#[test]
fn emits_wasm_for_node_proof() {
let out_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("scripts")
.join(".array-write-proof");
std::fs::create_dir_all(&out_dir).expect("create proof dir");
let cases: &[(&str, &str)] = &[
(
"single.wasm",
"fn frame(t: i32) { let mut a = [0, 0, 0, 0]; a[2] = 42; host::display::clear(a[2]); host::display::present(); }",
),
(
"loopfill.wasm",
"fn frame(t: i32) { let mut a = [0, 0, 0, 0, 0]; for i in 0..5 { a[i] = i * 10; } host::display::clear(a[3]); host::display::present(); }",
),
(
"loopfill_t.wasm",
"fn frame(t: i32) { let mut a = [0, 0, 0, 0, 0]; for i in 0..5 { a[i] = i * 10; } host::display::clear(a[t]); host::display::present(); }",
),
(
"overwrite.wasm",
"fn frame(t: i32) { let mut a = [0, 0]; a[0] = 7; a[0] = 99; host::display::clear(a[0]); host::display::present(); }",
),
(
"param_read.wasm",
"fn sum(a: [i32; 3]) -> i32 { a[0] + a[1] + a[2] } \
fn frame(t: i32) { let g = [3, 4, 5]; host::display::clear(sum(g)); host::display::present(); }",
),
(
"param_shared_write.wasm",
"fn set1(a: [i32; 3], v: i32) { a[1] = v; } \
fn frame(t: i32) { let mut g = [0, 0, 0]; set1(g, 77); host::display::clear(g[1]); host::display::present(); }",
),
(
"repeat_fill.wasm",
"fn frame(t: i32) { let g = [9; 16]; host::display::clear(g[7]); host::display::present(); }",
),
(
"repeat_then_write.wasm",
"fn frame(t: i32) { let mut g = [5; 8]; g[2] = 88; host::display::clear(g[t]); host::display::present(); }",
),
];
for (file, src) in cases {
let wasm = compile(src).unwrap_or_else(|e| panic!("compile {file}: {e}"));
std::fs::write(out_dir.join(file), &wasm).unwrap_or_else(|e| panic!("write {file}: {e}"));
}
}
}