Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Embedding

The compiler ships as the perry_wit Rust crate and as a standalone WASI 0.2 component. The runtime and standard libraries are precompiled and embedded in the compiler binary, so compilation runs entirely in memory.

Rust

perry_wit::api::compile compiles an entry module, auxiliary ESM modules and WIT packages from byte buffers:

use perry_wit::api::{self, Options, Program, Wit};

let program = Program {
    source: br#"
        export function authorize(token: string): boolean {
            return token.startsWith("Bearer ") && token.length > 16;
        }
    "#
    .as_slice()
    .into(),
    modules: vec![],
};

let wit = Wit {
    application: br#"
        package example:auth@0.1.0;
        world filter {
            export authorize: func(token: string) -> bool;
        }
    "#
    .as_slice()
    .into(),
    dependencies: vec![],
};

let compiled = api::compile(&program, &wit, &Options::default())?;
assert!(!compiled.core.is_empty());
assert!(compiled.component.is_some_and(|component| !component.bytes.is_empty()));

Errors are CompileError values holding PerryDiagnostic records with a source span and a DiagnosticCode.

WebAssembly Component

perry-wit.wasm is a WASI 0.2 component that exports this interface (compiler.wit) next to a wasi:cli/run command:

package perry:compiler@0.1.2;

interface api {
  /// Optional diagnostic name; for an entrypoint, also its logical module identity.
  /// Anonymous single-source compilation needs no name. Names never access host files.
  record source {
    name: option<string>,
    contents: list<u8>,
  }
  /// Dependency modules require package-relative logical identifiers for import resolution.
  record module {
    name: string,
    contents: list<u8>,
  }
  record program {
    source: source,
    modules: list<module>,
  }
  variant wit-package {
    sources(list<source>),
    encoded(list<u8>),
  }
  record wit-input {
    application: wit-package,
    dependencies: list<wit-package>,
  }
  record compile-options {
    %world: option<string>,
    /// Omit component wrapping, retaining the selected WIT world's canonical ABI.
    core-only: bool,
  }
  record component-output {
    bytes: list<u8>,
    stripped: list<u8>,
  }
  record compiled {
    core: list<u8>,
    component: option<component-output>,
    allocation-report: string,
  }
  record artifact {
    name: string,
    contents: list<u8>,
  }
  enum diagnostic-code {
    parse-error,
    unsupported-operator,
    unsupported-spread,
    missing-type-annotation,
    unsupported-type,
    unsupported-expression,
    incompatible-record-layout,
    missing-required-field,
    absence-mismatch,
    interface-mismatch,
    export-mismatch,
    import-signature-mismatch,
    module-resolution,
    invalid-input,
    wit-resolution,
    codegen-failure,
    missing-error-channel,
    io,
    internal-compiler-error,
  }
  enum diagnostic-severity { error, warning, info }
  record source-span {
    file: string,
    start-line: u32,
    start-column: u32,
    end-line: u32,
    end-column: u32,
    start-byte: u64,
    end-byte: u64,
  }
  record diagnostic {
    code: diagnostic-code,
    severity: diagnostic-severity,
    message: string,
    span: option<source-span>,
    notes: list<string>,
    help: option<string>,
  }
  compile: func(program: program, wit: wit-input, options: compile-options) -> result<compiled, list<diagnostic>>;
  /// Raw TypeScript exports without WIT. This differs from compile with core-only,
  /// which still checks a WIT world and emits its canonical ABI.
  compile-core: func(program: program) -> result<list<u8>, list<diagnostic>>;
  generate-sdk: func(wit: wit-input, %world: option<string>) -> result<list<artifact>, list<diagnostic>>;
}

world compiler {
  export api;
}

The playground runs it in a Web Worker. Because the api export operates on in-memory buffers, a server can instantiate it with empty filesystem preopens, fuel limits and memory caps to compile untrusted TypeScript.