Dewdrop is the reference compiler and standard library for Dew, a statically typed programming language that compiles to WebAssembly GC.
If those words are new to you, that is okay:
- A compiler turns source code written by people into code a computer runtime can execute.
- Statically typed means Dew checks many mistakes, such as passing a string where a number is required, before the program runs.
- WebAssembly, usually shortened to Wasm, is a portable binary format supported by standalone runtimes and JavaScript runtimes.
- WebAssembly GC, or WasmGC, lets compiled programs use managed objects such as structs, arrays, strings, enums, and closures.
- WASI is a standard interface through which a Wasm program can request basic host services such as reading standard input or writing standard output.
Dewdrop is written in MoonBit and uses Starshine to validate and encode deterministic WasmGC modules.
Project status: Dew is under active development. The implemented subset is substantial, but this is not yet a stable language release. Syntax, APIs, package formats, and generated Wasm may change. Check
docs/roadmap.mdbefore depending on a planned feature.
- Try Dew: start with
Getting started, then usetools/dew check,run,build, andtest. - Learn the language: read
A beginner tour of Dew syntax; usedocs/spec.mdfor exact and evolving rules. - Use the CLI and packages: see
Command reference,Building a small package, anddocs/research/compiler-cli.md. - Use the standard library: see
std/README.mdfor module ownership, APIs, and source-generation rules. - Contribute to the compiler: read
Contributing with TDD,docs/roadmap.md, andagent-todo.md. - Study implementation details: browse
docs/research/for design notes, measurements, and coverage findings.
The README is the practical guide; the specification is authoritative for language and compiler behavior; the roadmap records project status; and research notes explain individual implementation decisions.
Everyone's reasonable contributions are welcome. You do not need to be a compiler expert to report a clear bug, improve an explanation, add a focused test, reduce a failing example, or propose a carefully scoped change.
AI-generated code, tests, documentation, and research are welcome. However, AI makes it very easy to produce ten times more AI slop than a reviewer can safely verify.
If you use AI, you are still responsible for the entire contribution:
- Read and understand every changed line.
- Verify that APIs, syntax, and claims actually exist in this repository.
- Remove invented abstractions, fake tests, unnecessary comments, and speculative compatibility code.
- Keep the change small enough for a person to review carefully.
- Run the relevant tests and report exactly what you ran.
- Add tests that would fail if the implementation were wrong.
- Do not ask reviewers to debug a large generated patch that you have not validated yourself.
AI assistance is not a substitute for understanding the change. Because generated patches often look plausible even when they are subtly wrong, expect AI-heavy changes to receive more scrutiny, not less.
Dewdrop strongly prefers test-driven development, usually abbreviated TDD.
TDD is a small loop:
- Red: Write a focused test that describes the behavior you want. Run it and confirm that it fails for the expected reason.
- Green: Make the smallest implementation change that causes the test to pass.
- Refactor: Improve the code without changing its behavior, running the test again as you work.
For example, if you are fixing an enum-pattern compiler bug:
- Add the smallest Dew program that reproduces the bug.
- Confirm that the compiler or runtime test fails.
- Fix the parser, semantic phase, lowering phase, or backend.
- Confirm that the focused test passes.
- Run the wider validation suite to catch regressions.
TDD matters especially in a compiler because a change that fixes one program can silently break another program, alter generated Wasm, change type identity, or make compilation nondeterministic. Documentation-only edits and short-lived research spikes do not always need a new automated test, but executable behavior usually does.
Good issues are focused and actionable. Please include:
- What you tried.
- What you expected.
- What actually happened.
- A small source example when possible.
- The exact command you ran.
- Relevant error output.
- Your operating system and runtime/tool versions when the problem may be environment-specific.
Feature requests are welcome, but explain the real use case rather than only naming a feature. Very broad demands, duplicate reports without new information, and reports that cannot be reproduced may be closed or narrowed.
Be excellent to each other. This repository does not have a separate formal code of conduct. Treat contributors and users with patience, honesty, and respect. Disagreement about technical decisions is normal; personal attacks and abusive behavior are not useful here.
Dew is designed around these priorities, in order:
- Fast generated programs.
- Low memory use.
- Predictable and deterministic compilation.
- Compiler phases with clear, immutable boundaries.
- A familiar expression-oriented language that remains practical to compile to WasmGC.
“Deterministic compilation” means that compiling the same inputs with the same compiler should produce the same output bytes. Stable output makes builds reproducible and makes compiler changes easier to review.
Dewdrop currently includes, among other things:
- Fixed-width signed, unsigned, and floating-point numbers.
- Booleans, tuples, structs, enums, aliases, pattern matching, and Boolean
value is Pattern(binding)tests with branch-local bindings. - Traits, implementations, methods, static dispatch, and nominal/scalar/SIMD runtime trait values using typed WasmGC dictionaries.
- Generic bounds and recursive trait evidence across local, imported, callback, and erased runtime boundaries.
- Generic functions, structs, and enums using deterministic Wasm carrier specialization.
- Postfix
derive(Eq),derive(Debug),derive(Hash), andderive(Show)for structs and enums, including conditional generic prerequisites and imported execution. - Ambient deterministic
Debugoutput through bounded WASI writes. - First-class functions and closures, including escaping bounded callbacks that capture runtime trait evidence.
- Immutable and mutable local variables, including captured mutable variables.
- Multi-file modules and statically linked multi-module programs.
- Strict UTF-8
StringandStringViewvalues. - Arbitrary
Bytesvalues, consuming string/byte builders, deterministic allocation-free text hashing, explicit lexicographic String/StringView/Bytes comparators, and strict UTF-8 code-point iterators. - Explicit
Ordering/Comparatorevidence for deterministic ordered collections. - Carrier-specialized
FixedArray, growableArray, bounded/growable circular buffers, comparator-drivenBinaryHeap,PriorityQueue, red-black trees, ordered maps, ordered sets, and builder-backed Unit/primitive and derivedShowformatting, and explicitDisposablecleanup evidence, and deterministic LIFOdefer, scopedusing, and fixed-point primitive constant folding and immutable-summary scalar inlining, Core 3 scalar tail recursion and terminal branch calls, private body-local and proven direct-parameter/result unit-enum tag specialization plus packed single-I32-payload enum parameter/result specialization, bounded immutable local lifetime coalescing including shared scalar, exact-reference, and erased-generic sources, deterministic physical slot reuse for compatible noninterfering locals in linear bodies, and immutable scalar-field and exact nested-reference common-subexpression elimination and fresh, pure-gap sole-use, repeated-same-field, ordered/reordered/one-missing source-local and multi-missing and mixed alias/direct synthesized scalar/fresh/local/module-reference/generic component-local, fresh-if/match-join, and nonescaping reference/generic box, direct/let-bound/stable-guard fresh tuple/struct-variant, constructor-resolved alternative, constant/stable-composite-or-payload guard and catchall selection and binding-forwarding/projection and tuple/struct scalar-transform, and nested aggregate payload, and exact trait-object escape elimination, double-endedDeque, LIFOStack, FIFOQueue, and mutableMapandSetcollections with explicit iterators. - Explicit scalar
dew.std.mathpolicies for checked and wrapping integer arithmetic, exact float bits, deterministic NaN selection, signed zero, classification, rounding, and square root. - Portable
dew.std.ioReader/Writer contracts, bounded transfer helpers, typed errors, and host-free in-memory byte streams, kept separate from WASI adapters. - Explicit
dew.std.encoding.utf8zero-copy encoding, strict decoding with malformed byte offsets, and deterministic lossy decoding. - Strict Dew-native
dew.std.jsonvalues, bounded Reader/Writer facades, exact number lexemes, Unicode escape validation, duplicate-key rejection, deterministic compact serialization, and source-level SIMD scanning. - Standard
dew.std.encodeanddew.std.decodeevidence, bounded Semantic Versioning 2.0.0, and deterministic fallible codecs fordew.json,dew.lock,dew.modules.json, and installed-package capsules. dew.std.testingassertion helpers for Boolean, equality, ordering, Option, and Result checks above deterministic compiler-owned test metadata and trap handling.- Generated
dew.std.wasm.intrinsicsaccess to every backend inline scalar, conversion, memory, packed-lane, SIMD operation, and the bounds-checked GC-backedBytesSIMD bridge, with declaration/backend parity checks. - Native SIMD and allocation-free packed-lane operations.
- WASI Preview 1 standard input and output.
- Source-level tests with deterministic compiler-produced metadata.
- Portable one-shot SHA-256 in
dew.std.integrity.sha256, checked against the NIST CAVS short-message corpus. - Capability-based relative paths, filesystem, and process contracts with WASI Preview 1 and Facet 0.1 adapters.
- Deterministic WAT snapshots and Wasm execution tests.
- File-aware deterministic diagnostics with line/column positions, excerpts, carets, and related-location labels.
Not every familiar language feature exists yet. General #annotation(...) metadata, a Dew source formatter, TOML/YAML, HTTP, broader secret-key cryptography, and several broader tooling/library milestones remain planned. Postfix derive(Eq), derive(Debug), derive(Hash), and derive(Show) are implemented, as are explicit Show, Disposable, defer, using, growable arrays, collection iterators, iterator combinators, allocation-free Bloom filters, scalar math policies, portable byte I/O abstractions, strict JSON, standard testing helpers, and generated WebAssembly intrinsic parity. Consult the roadmap for the current boundary.
The basic path is:
Dew source (.dew)
|
v
Dewdrop compiler
|
v
validated WebAssembly GC module (.wasm)
|
v
WasmGC runtime + WASI host
Dewdrop emits a core WasmGC module, not a native operating-system executable. A runtime must instantiate that module and provide any imported WASI functions.
A normal executable Dew module exports a zero-argument function named main. If it has non-constant module-level initialization, it also exports __dew_init. A Dew-aware host must:
- Compile or load the
.wasmfile. - Provide its WASI Preview 1 imports.
- Instantiate it.
- Call
__dew_init()exactly once if that export exists. - Call
main().
The included Node host at tools/dew-run.mjs follows this sequence.
The easiest current path is to build Dewdrop from its source checkout and use the included tools/dew command. There is not yet a published, versioned Dew compiler release that this tutorial can install globally.
You will need:
- Git to clone the repositories.
- A Bash-compatible shell for the checked-in wrapper scripts. On Windows, using WSL is the simplest current route.
- MoonBit to build the compiler.
- Python 3 for the command-line wrapper, generators, and snapshot tools.
- A current stable Node.js release for the repository's reference Wasm/WASI host and JavaScript tests.
- wasm-tools for rendering, parsing, and inspecting Wasm.
- Go for parts of the complete differential validation setup.
For ordinary experimentation, Bash, MoonBit, Python, Node, and wasm-tools are the important tools. The complete contributor suite has more dependencies than compiling one small Dew file. The snapshot suite requires the exact wasm-tools release recorded in tools/module-snapshots/WASM_TOOLS_VERSION.
Confirm that your shell can find them:
git --version
moon version
python3 --version
node --version
wasm-tools --versionUse current stable tool releases. Dew targets evolving WasmGC functionality, so a very old runtime may reject otherwise valid output.
Starshine is an official Dewdrop Git submodule and MoonBit workspace member. Clone both repositories in one command:
git clone --recurse-submodules https://github.com/jtenner/dewdrop.git
cd dewdropFor an existing Dewdrop checkout, initialize or refresh the pinned Starshine revision with:
git submodule update --init --recursiveThe pinned Starshine raw WasmGC FFI can be checked or built with:
tools/starshine-ffi.sh check
tools/starshine-ffi.sh buildRun the fast validation loop:
tools/check.sh --quickThis checks formatting, generated sources, CLI behavior, and the native MoonBit test target. It is the best first command because a failure here usually points to a missing tool or an incompatible checkout.
The complete suite is:
tools/check.shThe complete suite is much larger. It exercises native MoonBit, WasmGC, JavaScript, classic Wasm, standard-library tests, differential UTF/SWAR/WASI checks, Starshine validation, Node execution, and Wago snapshot execution. Full validation runs the four MoonBit target suites concurrently in isolated persistent target directories, uses a release native UTF parity generator, and builds the snapshot compiler once before running independent fixtures concurrently. Logs remain deterministic. Set DEW_CHECK_TARGET_JOBS=1 and DEW_SNAPSHOT_JOBS=1 to force serial target and fixture execution.
Create a file named hello.dew in the Dewdrop checkout:
open dew.std.wasi
pub fn main() -> U32 {
wasi_fd_write(1u32, b"Hello from Dew!\n")
}
What each line means:
open dew.std.wasimakes the public names from the WASI standard module available without a module prefix.pubmakesmainvisible outside its Dew module, which allows the host to call it.fndeclares a function.main()takes no parameters.-> U32says the function returns an unsigned 32-bit integer.wasi_fd_writewrites bytes to a file descriptor.1u32is the conventional WASI file descriptor for standard output.b"..."creates aBytesvalue. The\nescape adds a newline.- The final expression in a block is the block's value, so the number of bytes written becomes the function result.
Dew requires explicit function return types. Newlines are significant in several declarations and block forms, so do not add semicolons as if this were C or JavaScript.
tools/dew check hello.dewcheck parses the program, resolves names, checks types and traits, plans its runtime representation, and reports compiler errors without writing a final program file.
Use check frequently while learning. It gives you a faster feedback loop than waiting until the end to build and run everything.
tools/dew run hello.dewYou should see:
Hello from Dew!
tools/dew run currently compiles a temporary Wasm file and executes it with the repository's Node-based WASI host.
tools/dew build hello.dew -o hello.wasmRun that file with the included host:
node tools/dew-run.mjs hello.wasmThis separates compilation from execution. It is useful when testing a different runtime or embedding the module in another host program. Native compilation stores parser events, frozen interfaces, optional inferred bodies and declaration families, optional type layouts and WasmGC fragments, and exact successful check/HIR/lowering/Wasm results in one 16-byte-aligned .dew/cache/packs/v1-*.dwp file. The compiler reads the pack once, scans contiguous entry headers, and decodes only selected entries. Exact warm results bypass all compiler phases. Entries use BLAKE3-256 owner, content, producer-context, and payload digests; their U32 header lanes are lookup hints only. Full digests and exact owners remain authoritative. Body and planning phase reuse stays opt-in, but exact program reuse is enabled by default because it has a measured end-to-end win. The MoonBit bootstrap command also owns the verified whole-build cache and compiler fingerprint. Cache-pack hashing, decoding, validation, and lookup stay in MoonBit. tools/dew is only a small shell launcher for the MoonBit bootstrap executable. Use --no-build-cache to force the native compiler, --no-cache-pack to use legacy per-artifact files, --no-program-cache to bypass exact pack results while retaining phase entries, --no-parse-event-cache to force parsing, --no-interface-cache to disable frozen-interface reuse, --body-cache or DEW_BODY_CACHE=1 to enable complete-module reuse, --body-family-cache or DEW_BODY_FAMILY_CACHE=1 to enable both body layers, --no-body-cache to disable them, --plan-cache or DEW_PLAN_CACHE=1 to enable layout/fragment reuse, --no-plan-cache to disable it, and --cache-report to report each cache layer.
Binary .wasm files are not intended to be read directly. WAT is a text representation of Wasm:
tools/dew build --emit wat hello.dew -o hello.watOpen hello.wat in a text editor. You do not need to understand all of it yet. Looking at WAT is mostly useful for compiler development, debugging ABI issues, and confirming whether a change added allocations, imports, types, or instructions.
You can also inspect earlier compiler representations:
tools/dew build --emit hir hello.dew -o hello.hir
tools/dew build --emit lowering hello.dew -o hello.lowering- HIR is the compiler's resolved high-level representation.
- Lowering is closer to the operations the backend will emit.
- WAT is readable WebAssembly.
- Wasm is the final binary output and the default build format.
Use a maintained stable Wasm runtime while bootstrapping Dew code. Avoid basing important work on an abandoned host or an unexplained browser experiment.
Good runtime categories include:
- Wasmtime as a standalone Wasm runtime and embedding library.
- Node.js with a WASI host. Node is the reference JavaScript runtime used by this repository.
- Bun with its WebAssembly and Node-compatibility facilities, after confirming the required WasmGC and WASI behavior in its current stable release.
- Deno with its WebAssembly APIs and an appropriate WASI host, again verifying the required features in the installed stable release.
Dewdrop's current host boundary uses WasmGC plus WASI Preview 1. A runtime must support the features actually present in your module. Support can differ by release, so use a smoke test instead of assuming that every runtime exposes the same WASI API.
- Start with
tools/dew run, which uses the tested Node host. - Build a persistent module with
tools/dew build ... -o program.wasm. - Run the module with
node tools/dew-run.mjs program.wasm. - Add Wasmtime, Bun, or Deno as a second host only after the Node path works.
- Confirm that the second host provides
wasi_snapshot_preview1.fd_readandfd_writewhen your program needs them. - Confirm that it calls optional
__dew_initbeforemain. - Keep a tiny runtime smoke test in your project so runtime upgrades fail visibly.
A plain runtime command may assume a WASI command module with an exported _start. Dew currently exports main and optional __dew_init instead. If a runtime's command-line interface cannot express that sequence, use its embedding API or a small Dew-aware adapter based on tools/dew-run.mjs.
The return value of main is currently less important than its observable output and traps. The included host calls main but does not use its result as an operating-system exit code.
The examples below focus on implemented behavior. For exact and evolving rules, see docs/spec.md.
A line comment starts with //:
// This is a comment.
let answer = 42
Dew has fixed-width scalar types:
| Kind | Common types | Example literals |
|---|---|---|
| Signed integers | I8, I16, I32, I64 |
42, 42i64 |
| Unsigned integers | U8, U16, U32, U64 |
42u8, 42u32, 42u64 |
| Floating point | F32, F64 |
1.5f32, 1.5f64 |
| Boolean | Bool |
true, false |
| No meaningful value | Unit |
Usually produced by statements or an empty result |
| Never returns normally | Never |
Produced by operations such as unreachable() |
Dew intentionally distinguishes number widths. This makes storage and generated Wasm behavior predictable.
fn add(left: I32, right: I32) -> I32 {
left + right
}
The last expression is returned automatically. You may also use return:
fn absolute(value: I32) -> I32 {
if value < 0 {
return -value
} else {}
value
}
Every function must declare its return type, including functions returning Unit.
Use let for a name whose value will not be replaced:
fn answer() -> I32 {
let base = 40
let extra = 2
base + extra
}
Prefer immutable values unless mutation makes the code clearer.
Use let mut when the binding must be assigned a new value:
fn count_once() -> I32 {
let mut count = 0
count = count + 1
count
}
Mutation belongs to the local binding. General field assignment and every collection style are not automatically supported just because local assignment exists.
if is an expression, so it can produce a value:
fn classify(value: I32) -> I32 {
if value < 0 {
-1
} else {
1
}
}
Both branches must be compatible with the expected result type.
A struct groups named fields:
struct Point {
x: I32
y: I32
}
fn sum(point: Point) -> I32 {
point.x + point.y
}
fn example() -> I32 {
let point = Point::{
x: 20
y: 22
}
sum(point)
}
Struct fields are newline-delimited rather than comma-delimited. Fields are
immutable by default. Add mut before a field name to allow writes:
struct Box<t> {
mut value: t
}
fn replace(box: Box<I32>) -> Unit {
box.value = 42
}
A field write is a block item. It evaluates the receiver first and the new value second, once each. Struct values are references, so aliases observe the same write. Writing an immutable field is a compile error.
A struct or enum may append derive(Eq), derive(Debug), derive(Hash), or
derive(Show) after its closing brace. Generated implementations visit fields and payloads in source order and
require the corresponding trait evidence for every stored value. Generic owner
parameters referenced by stored types receive conditional bounds and execute
through static evidence specialization; phantom parameters remain unconstrained.
Derived implementations use ordinary coherence and visibility rules, survive
frozen cross-module interfaces, and conflict with handwritten evidence rather
than silently overriding it.
An enum describes a value that can be one of several variants:
enum Choice {
First(I32)
Second(I32)
}
fn read(choice: Choice) -> I32 {
match choice {
First(value) => value
Second(value) => value
}
}
match checks the variant and can extract its payload. For a closed enum, the compiler can diagnose a missing case.
Option<T> represents either a value or the absence of a value:
fn read_or_zero(value: Option<I32>) -> I32 {
match value {
Some(item) => item
None => 0
}
}
This is safer than inventing a special number such as -1 to mean “missing.”
A trait describes behavior a type can provide. An implementation connects that behavior to a type. Dew uses trait evidence for operators, methods, generic APIs, hashing, indexing, and other static dispatch. An ordinary trait implementation must be owned by its package through either the trait declaration or the target's nominal head; primitive targets belong to dew.std. Explicit foreign impl supplies private module-local evidence when neither side is owned and is never exported.
A bare trait name in a value position denotes an erased runtime trait value. Passing a nominal reference value to an expected trait type constructs a small WasmGC envelope containing the erased receiver and a shared immutable typed-function-reference dictionary. Scalar, packed, and SIMD values receive one carrier-specific snapshot box only at that erased boundary. Calls load the source-ordered method slot and use call_ref; calls whose concrete implementation is statically known remain direct and allocate nothing for dispatch. Linked imported traits and implementations use the same representation. Closed generic specializations substitute symbolic bound evidence recursively. Transparent tail and explicit-return parameter selectors then directize to the caller's concrete carrier with no envelope or dictionary, while genuinely dynamic generic branches materialize one exact dictionary and evidence-aware implementation-method specialization, including recursive prerequisites and imported providers. Cross-module trait method/vtable/object layouts are canonicalized by declaration identity. A public root generic whose symbolic bounds are consumed by erased trait construction or direct bound methods/operators now exports an all-reference fallback with one trailing eqref vtable parameter per source-ordered bound; an external Wasm consumer may construct a structurally equivalent typed dictionary and pass it directly. Bound calls load the typed method slot and use call_ref without constructing an intermediate trait object. Direct symbolic evidence forwards through private, recursive, and imported generic call chains using hidden trailing arguments at each specialization. Statically selected concrete generic implementations forward their runtime prerequisites into the exact implementation-method specialization the same way. Escaping bounded function references use immutable closure subtypes that capture source-ordered caller dictionaries and invoke the exact local or imported target specialization through a private environment-first trampoline. When an erased concrete implementation has caller-local prerequisites, Dew wraps the concrete receiver and ordered dictionaries in one immutable capture object; the ordinary typed vtable remains shared, and its adapter extracts the receiver and evidence before directly calling the exact implementation specialization. Internal calls through public or imported wrappers may still directize when transitive summaries prove every discarded prelude total and effect-free; the public dynamic body remains available, while trapping, mutating, recursive, escaping, or unknown flows stay dynamic.
Generic parameters accept ordered bounds such as t: Eq + Debug. Functions, trait methods, and implementation methods use the same generic header. A method-local parameter may shadow a containing trait or impl spelling; the immediate method scope wins while stable identities keep both declarations distinct. The compiler retains and executes bound identities through evidence-aware static specialization for closed calls; dictionaries are reserved for actual runtime trait-value boundaries.
Inference normally determines call type arguments. Use exact turbofish syntax when a parameter cannot be inferred or an overload must be constrained:
let value = identity::<I32>(42)
let converted = receiver.convert::<String>(input)
An explicit list must supply every generic parameter on the selected function. On a method it supplies the method-local parameters; containing impl parameters remain inferred.
Functions can be stored in variables and passed around. A lambda is an anonymous function:
fn make_counter(start: I32) -> fn() -> I32 {
let mut current = start
fn() -> I32 {
current = current + 1
current
}
}
The returned lambda captures current. Because current is mutable, the compiler stores it in one shared runtime cell so later calls see the updated value. A named bounded generic function can also escape from a generic API: Dew captures the caller's immutable trait dictionaries in the same closure representation, preserving exact evidence across returns, callback parameters, and module boundaries. Non-escaping callback calls can directize through immutable local aliases and transitive public/imported forwarding wrappers; returned, captured, multiply invoked, trapping, or unknown callback uses retain the closure ABI.
Dew separates text from arbitrary bytes:
"hello"is a strict UTF-8String.b"hello"isBytes.StringViewis a shared view into valid string data.Bytesmay contain data that is not valid UTF-8.
WASI reads and writes bytes, so beginner output examples commonly use b"...".
Suppose the regular code is in math.dew:
fn add(left: I32, right: I32) -> I32 {
left + right
}
Put its tests in a file whose name ends exactly in _test.dew, such as math_test.dew:
test "adds two numbers" {
assert(add(20, 22) == 42, "20 + 22 should equal 42")
}
dew.std.testing also provides expect_option_some, expect_result_ok, and expect_result_err when a test needs the generic payload value rather than only a shape assertion. Wrong variants preserve the supplied failure message and trap deterministically.
Run both files as one test module:
tools/dew test math.dew math_test.dewUseful test filters include:
# List discovered tests without running them.
tools/dew test --list math.dew math_test.dew
# Run one exact display name.
tools/dew test --name "adds two numbers" math.dew math_test.dew
# Run tests whose stable identity contains some text.
tools/dew test --filter "adds" math.dew math_test.dewA failed assert(condition, message) reports its dynamic message through the test WASI host and traps the test. The runner continues according to the test metadata and reports a nonzero status for failures.
Tests may also declare an expected runtime trap. See existing files under tests/module-snapshots/tests/ for current syntax and examples.
For one file, passing the .dew path directly is simplest. As a project grows, use a package manifest.
A package normally looks like this:
hello-package/
├── dew.json
└── src/
├── main.dew
└── main_test.dew
A minimal dew.json is:
{
"name": "@example/hello",
"version": "0.1.0",
"dependencies": {}
}The package name @example/hello derives the Dew module name example.hello. Source files are discovered in deterministic sorted order from src/**/*.dew. Files ending in _test.dew are included only by dew test.
From the package directory, invoke the compiler without listing source files:
/path/to/dewdrop/tools/dew check
/path/to/dewdrop/tools/dew run
/path/to/dewdrop/tools/dew build -o hello.wasm
/path/to/dewdrop/tools/dew testIf the package has dependencies, dew.json records requested dependency versions while dew.lock records exact resolved versions, sources, materialized paths, SHA-256 integrity, and expected interface fingerprints. A successful source-backed resolution publishes a verified content-addressed capsule under .dew/cache/packages/; if that locked dependency tree is later removed, the CLI can atomically restore it from the capsule and produce byte-identical Wasm. Registry lookup and first-time network installation remain roadmap work.
You can compute a package's canonical integrity value with:
tools/dew package-integrity path/to/dew.jsonSign in to the registry once on each computer:
tools/dew login
tools/dew whoamiYour permanent public username owns the matching package scope automatically. For example, the user @jtenner can publish package names such as @jtenner/hello without an administrator grant. Existing reserved scopes and scopes owned by another user remain protected.
Test the package before publishing because published versions are immutable:
tools/dew test --manifest path/to/dew.json
tools/dew publish --manifest path/to/dew.jsonThe registry creates your matching scope during the first successful publish. An administrator must grant any additional team or organization scope.
Most beginners should begin with one module. When you need explicit multi-module compiler inputs, a dew.modules.json graph names the root module and lists modules and files in deterministic order:
{
"root": "example.main",
"modules": [
{
"name": "example.library",
"files": ["library.dew"]
},
{
"name": "example.main",
"files": ["main.dew"]
}
]
}In main.dew, open the library module:
open example.library
pub fn main() -> I32 {
answer()
}
Then check or build the graph:
tools/dew check --manifest dew.modules.json
tools/dew build --manifest dew.modules.json -o program.wasmdew.modules.json is useful for compiler fixtures and explicit graphs. Convention-based packages use dew.json and, when needed, dew.lock.
Imports must appear before ordinary declarations in each source file.
Use open to bring a module's public names into unqualified lookup:
open dew.std.map
Use import ... as @alias for exact qualified access:
import example.library as @library
fn read(value: @library.Item) -> I32 {
match value.message {
@library.Message::Value(item) => item
}
}
Aliases qualify public values, functions, types, traits, enum constructors and patterns, and static implementation methods. Exact qualification does not add the selected names to unqualified lookup.
Declarations are visible throughout their owning module by default. Add pub when another module or the Wasm host must see the declaration:
fn helper() -> I32 {
42
}
pub fn main() -> I32 {
helper()
}
Files do not create private namespaces. Multiple files assigned to one module contribute to one shared module namespace.
tools/dew check path/to/program.dewChecks the program without writing final output. Compiler failures use stable logical paths, one-based byte line/column positions, source excerpts, carets, and related-location labels where available:
path/to/program.dew:2:3: error: UnknownValueName(2, 0, 25)
|
2 | missing_value
| ^
tools/dew build path/to/program.dew -o program.wasmBuilds deterministic, Starshine-validated Wasm.
tools/dew build path/to/program.dew -o program.wasm --optimize speed
tools/dew run path/to/program.dew --optimize speed
tools/dew test path/to/program.dew path/to/program_test.dew --optimize speed
tools/dew build --optimize speed --print-passes--optimize selects none, speed, speed-deep, or O4s. Build, run, and test
use none by default. speed uses 11 passes. speed-deep uses 50 ordered passes,
with Heap2Local and SSANoMerge before inlining and one local cleanup phase.
tools/dew build path/to/program.dew -o program.wasm --optimize speed-deep
tools/dew build --optimize speed-deep --print-passesOn 16 local benchmarks, speed-deep uses about 0.7% less time and 1.7% fewer bytes
than speed; it uses 8.7% less time and 14.2% fewer bytes than the pinned stock
O4s. The optimizer itself takes more time: about 1.18 s versus 0.12 s across the 15
distinct input modules. Byte hashing remains slower than O4s. See the full measurements, pass
order, and remaining pass faults.
--print-passes prints the exact order and optimization levels as JSON without
compiling a source file.
Optimization supports Wasm and WAT output. HIR and lowering output cannot use it.
To optimize an existing module, including a compiler Wasm artifact:
tools/dew optimize input.wasm -o output.wasmThis command defaults to speed. Use --optimize none to copy the module.
For an exact custom order, repeat --starshine-pass. Repeated passes are kept.
A custom order cannot be combined with --optimize.
tools/dew build path/to/program.dew -o program.wasm \
--starshine-pass precompute --starshine-pass inlining \
--starshine-pass local-cse --starshine-pass vacuumThe CLI builds the pinned native Starshine optimizer when needed.
DEW_STARSHINE=/absolute/path/to/cmd.exe selects another executable for local
experiments. The build cache includes its binary contents, optimization levels,
and ordered passes. A failed optimization keeps the last good output, prints
the raw input path, and does not publish a successful cache entry. Test metadata
is preserved.
See the pipeline measurements for the selected order, runtime results, size changes, and remaining pass faults.
tools/dew run path/to/program.dewBuilds a temporary Wasm module and executes it with the Node reference host.
tools/dew test path/to/program.dew path/to/program_test.dewBuilds a self-describing test module and runs selected tests.
tools/dew clean
tools/dew clean --dry-runRemoves all managed build artifacts: the configured .dew/cache, MoonBit _build, target, .tmp, and the legacy .dew-cache. The parent .dew metadata directory remains. Explicit output paths passed with -o are user files and are not removed. --dry-run prints every selected path without changing it.
tools/dew build --emit hir path/to/program.dew -o program.hir
tools/dew build --emit lowering path/to/program.dew -o program.lowering
tools/dew build --emit wat path/to/program.dew -o program.wat
tools/dew build --emit wasm path/to/program.dew -o program.wasmNormal commands load standard-library source from deterministic package roots. Advanced compiler/bootstrap work may use:
--package-root PATHto add an ordered package root.DEW_PACKAGE_ROOTSto configure ordered package roots through the environment.--bootstrap-stdto use generated fallback standard sources.--no-default-preamblefor focused bootstrap/compiler tests where the ambient preamble must be disabled.
Most users should not need these flags.
The runtime must be able to see main:
pub fn main() -> Unit {
}
This is invalid:
fn answer() {
42
}
Write:
fn answer() -> I32 {
42
}
Many Dew declarations and fields are newline-delimited:
struct Point {
x: I32
y: I32
}
Follow existing fixtures if parser diagnostics are unclear.
Use a normal string for valid text data:
let greeting = "hello"
Use bytes for WASI and arbitrary binary data:
let encoded = b"hello\n"
The roadmap intentionally includes unfinished work. An unchecked item is not an available feature. The execution-only unfinished backlog is in agent-todo.md.
A .wasm file does not automatically know how to access your terminal. Provide the expected WASI imports and call __dew_init before main when initialization exists. Start with:
node tools/dew-run.mjs program.wasmA practical contribution usually follows this sequence.
Good scopes include:
- One parser error.
- One type-checking rule.
- One lowering operation.
- One generated Wasm instruction shape.
- One standard-library operation.
- One documentation inconsistency.
Avoid combining unrelated cleanup, redesign, and feature work in one patch.
The repository contains several test styles:
- MoonBit unit and white-box tests under
src/. - Dew standard-library tests under
std/tests/. - End-to-end compiler fixtures under
tests/module-snapshots/. - Runtime and ABI consumers under
tests/abi-consumers/. - Performance limits under
tests/performance-budgets/.
Copy the smallest relevant pattern rather than inventing a new test harness.
Run the narrowest command that demonstrates the missing behavior. For one module snapshot:
tools/module-snapshots/run.sh --fixture feature/nameFor a MoonBit package, run the relevant target and package rather than the entire project when possible. End-to-end CLI behavior also has sorted initial suites under tests/compile-pass, tests/compile-fail, and tests/run-pass; run them with:
python3 tools/cli-fixtures.pyA failing test should be diagnostic. It should show the behavior that is wrong, not hide a crash or silently skip because an optional tool is absent.
Dewdrop prioritizes runtime performance first and low memory use second. Preserve deterministic ordering and immutable phase boundaries. Do not replace a measured, allocation-conscious path with a convenient abstraction without measuring the effect.
Ordinary test runs never rewrite expected files. To intentionally regenerate a focused compiler fixture:
tools/module-snapshots/run.sh --update --fixture feature/nameRead the WAT and JSON diff. Snapshot regeneration is not proof that a change is correct; it only records the new behavior.
At minimum, run:
tools/check.sh --quickBefore a broad compiler/backend contribution is considered ready, run:
tools/check.shFor the normal native Dewdrop test lane, run:
tools/test-native.shRun the large compiler stress cases separately:
tools/test-stress-native.shRun complete compiler and backend integration cases separately:
tools/test-integration-native.shRun the pinned Starshine tests only when the Starshine integration changes:
tools/test-starshine-native.shDo not use moon test --target native as the normal Dewdrop command. Moon runs
that command for both members of moon.work, including the complete Starshine
test suite.
If you cannot run the full suite, say so clearly and list what remains unverified.
When behavior changes, update the relevant specification, roadmap, research note, or tutorial in the same contribution. Research and measurements belong in docs/research/.
src/tokenizer/ raw source cursor, WTF-8 decoding, and streaming lexer
src/parser/ forward-only parser, syntax events, and the parse-event artifact
src/semantic/ collection, name resolution, inference, flow, layouts,
lowering, interface freezing, specialization, and linking
src/standard_sources/ generated portable bootstrap source bytes and path registry
src/standard_loader/ on-disk dew.std selection and the persistent parse-event,
interface, body, planning, and program-output caches
src/cache_binary/ aligned artifact-pack container, canonical binary codecs, and
portable BLAKE3-256
src/compile_request/ versioned binary host/compiler request codec
src/core_linker/ deterministic Core Wasm static linker for --link-wasm providers
src/backend/ deterministic Starshine/WasmGC planning and emission
src/compiler_driver/ shared collect/analyze/lower/link/emit orchestration
src/dew_cli/ compiler-facing command implementation
src/dew_bootstrap/ build-cache, compiler-fingerprint, and CLI dispatch behind tools/dew
std/ Dew standard-library sources
std/tests/ direct standard-library tests written in Dew
tests/ snapshots, CLI fixtures, ABI consumers, and budgets
tools/ compiler wrappers, generators, hosts, and test runners
docs/spec.md language and compiler specification
docs/roadmap.md completed history and planned work
docs/architecture.md compiler phases, package ownership, and arena boundaries
docs/compile-request.md versioned host/compiler request protocol
docs/decisions/ accepted cross-cutting architecture decisions
agent-todo.md unfinished execution backlog only
docs/research/ design notes, experiments, and measurements
Dewdrop processes source code in stages:
bytes from the source file
-> decoded source units
-> tokens such as names, numbers, and punctuation
-> parsed declarations and expressions
-> collected module identities
-> resolved names and types
-> checked traits, patterns, and control flow
-> lowered executable operations
-> linked whole-program plans
-> validated and encoded WasmGC
Each stage tries to consume an immutable result from the previous stage and produce a new result for the next one. This makes compiler behavior easier to reason about and supports incremental compilation.
The current compiler runs compiler jobs sequentially because MoonBit cannot execute them in parallel. Internal parallel scheduling is deferred until Dewdrop is self-hosted and Dew provides parallel computation. The frontend is deliberately streaming and forward-only. It does not keep every possible parse tree or repeatedly rewind input. The backend assigns deterministic identities and indices so output does not depend on hash-map iteration or scheduling order.
- Dew programs are statically linked into one Wasm module.
- The selected root module controls public Wasm exports.
- Compiler-owned standard code is imported as frozen Dew module interfaces and dead code is removed before final indexing.
- Generic code uses deterministic physical-carrier specializations, with erased
eqreffallbacks at supported exported boundaries. - Generic boundary adapters may recursively convert nested structs, enums, and structural function values.
- First-class functions use typed Wasm references and a flattened WasmGC closure representation.
- Public ABI metadata is stored in a versioned
dew.abicustom section. - Static cost information is stored in a versioned
dew.metricscustom section. - Standard and versioned dependency interfaces use content-addressed persistent caching.
- WASI Preview 1 byte I/O uses bounded staging through one reusable linear-memory page.
These details are important for compiler and host authors. Beginners can safely use tools/dew check, run, build, and test without understanding all of them.
docs/spec.md— detailed language and compiler specification.docs/roadmap.md— implemented milestones and future plans.docs/architecture.md— compiler phases, package ownership, identities, arenas, and validation contracts.docs/compile-request.md— versioned bootstrap-host/compiler request protocol.docs/decisions/— accepted cross-cutting architecture decisions.agent-todo.md— unfinished execution backlog.docs/research/— design investigations and measurements.std/README.md— standard-library source organization.
Dewdrop is licensed under the Apache License 2.0. Third-party attribution is recorded in THIRD_PARTY_NOTICES.md.