Compiled to bytecode, run on a stack virtual machine
Try it in your browser | Quick Start | Features | Examples | Documentation
MiruScriptX is a minimalist, dynamically typed scripting language with a
clean, modern syntax, written from scratch in Rust. Write functions, closures,
loops, arrays, and maps in familiar syntax, split a program across files when it
outgrows one, handle the errors you expect and let the rest stop the program,
then run it from a file or an interactive REPL. Programs use the .miru
extension.
The name carries a lineage. MiruScriptX revives MiruScript, an earlier language by the same author written in C; the X marks the successor.
Programs are compiled to bytecode and run on a stack virtual machine. A tree
walker came first and was replaced in v0.5, once a corpus of golden tests had
frozen its behavior exactly; miru disasm will show you what any program
compiles to.
fn greet(name) {
return "Hello, " + name + "!"
}
let people = ["Aiko", "Ken"]
for name in people {
print(greet(name))
}
|
|
Install the latest release. The script checks the download against the published checksum and refuses if it does not match:
curl -fsSL https://raw.githubusercontent.com/stiven-gjekaj/miruscriptx/main/scripts/install.sh | sh
Prebuilt binaries cover Linux and macOS on x86-64 and arm64, and Windows on x86-64. Or install from crates.io:
cargo install miruscriptx
Or build from source (a recent stable Rust toolchain is all you need):
cargo build --release
Run a program from a file:
miru run examples/greet.miru
Evaluate a short program directly from the command line with -e (or its
long form, --eval):
miru -e 'print(6 * 7)'Or start the REPL and type expressions:
miru
miru> let x = 21
miru> x * 2
42
Reformat a program in the canonical style (add -w to rewrite it in place):
miru fmt examples/greet.miru
For a step-by-step guide, start the wiki at wiki/01-introduction.md.
Runnable programs live in examples/:
| Program | Shows off |
|---|---|
| greet.miru | Functions, arrays, and a loop |
| fib.miru | Recursion |
| fizzbuzz.miru | Control flow and the modulo operator |
| contacts.miru | Maps, lookups, and iteration |
| greeter.miru | Reading a line with input, and handling the end of it |
| transform.miru | Higher-order functions: map, filter, reduce |
| shop.miru + prices.miru | Two files: import, and names that belong to a file |
| recover.miru | try: surviving an error instead of stopping at it |
| guess.miru | A guessing game: random_int, seed, and input in a loop |
| words.miru | Word frequency: read_file, split, and counting in a map |
| dice.miru | Ten thousand rolls, drawn as a histogram |
| keys.miru | Arrow keys: read_key without waiting for a line |
| life.miru | Conway's Life, redrawn in place: clear and sleep |
| snake.miru | Snake: key_ready, joining arrays, and a paced loop |
| pong.miru | Pong: a ball that keeps moving while you hold still |
| tetris.miru | Tetris: turning a piece without trigonometry, and clearing a row |
Run one with miru run examples/contacts.miru.
// Maps, loops, and loop control
let book = {"Aiko": "555-0100", "Ken": "555-0142"}
book["Mai"] = "555-0177"
for name in keys(book) {
if name == "Ken" { continue }
print(name + ": " + book[name])
}
See the language reference for the whole language on one page.
Source becomes tokens, tokens become an abstract syntax tree, the tree is compiled to bytecode, and the bytecode runs on a stack virtual machine.
| Stage | Files | Lines | Responsibility |
|---|---|---|---|
| Lexer | token.rs, lexer.rs | 1509 | Source text to tokens, with line, column, and span tracking |
| Parser | ast.rs, parser.rs | 1957 | Recursive descent plus a Pratt expression parser |
| Runtime model | value.rs, ops.rs, builtins.rs, random.rs | 4737 | Values, operator and indexing rules, the builtin library |
| Bytecode engine | chunk.rs, globals.rs, compiler.rs, vm.rs | 4609 | Compiles the AST to bytecode, runs it on a stack VM, loads modules, and catches errors |
| Diagnostics | suggest.rs | 220 | Chooses the name an error offers back when a program misspells one |
| Formatter | formatter.rs | 813 | Reprints a program in canonical form (miru fmt) |
| CLI and REPL | main.rs, repl.rs, keyboard.rs | 1305 | File runner, fmt and disasm commands, the REPL, and raw-mode key reading |
| Library | lib.rs | 1190 | Ties it together (parse_program, run_source, disassemble_source) |
| Total | 19 files | 16595 | Written from scratch in Rust |
The playground is a separate crate: 775 lines of Rust binding the language to
WebAssembly, and 1672 of hand-written HTML, CSS, and JavaScript. It is counted
apart because it is not the language, and neither its code nor its dependencies
are involved in running a .miru file.
src/ the language (lexer, parser, compiler, VM, CLI, REPL)
playground/ WebAssembly bindings and the in-browser playground
editors/ syntax highlighting for .miru files
examples/ runnable .miru programs
wiki/ step-by-step learning lessons
docs/ language reference, architecture, and roadmap
tests/ golden, language, session, and end-to-end tests
benches/ criterion benchmarks for the bytecode engine
scripts/ build_reference.sh regenerates the single-page reference
|
A guided tour, |
The whole language |
How the interpreter |
Status and what |
|
What a program |
What will not |
cargo test
Unit tests sit next to each module. Beyond those, tests/golden.rs pins a
corpus of programs to the exact result each must produce, values and errors
alike, down to the line and column a caret points at. Those expectations are
literals rather than regenerated, so a test cannot quietly absorb a change in
behavior, which is what made rewriting the engine's hot paths safe.
tests/integration.rs runs the compiled binary against the example programs.
The same checks run in CI, along with cargo fmt --check and
cargo clippy -D warnings.
Benchmark the engine with:
cargo bench
Contributions are welcome. See CONTRIBUTING.md to get started, follow the Code of Conduct, and check SUPPORT.md if you need help. The changelog records what changed between versions.
If you find MiruScriptX useful, you can support its development here.
Released under the MIT License. See LICENSE for the full text, and TERMS.md for the project terms.