Skip to content

sb2bg/walrus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

566 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Walrus Logo

🦭 Walrus Programming Language

Build Test

A fast, expressive programming language with a clean syntax and powerful features, built in Rust.

Overview

Walrus is a dynamically-typed programming language that combines simplicity with power. It runs on a bytecode virtual machine, with a mark-and-sweep garbage collector and comprehensive support for modern programming paradigms.

Key Features

Execution & Tooling

  • Bytecode VM: High-performance stack-based virtual machine with call frames and tail call optimization
  • Disassembler: Inspect generated bytecode and analyze program structure

Language Features

  • First-Class Functions: Functions as values with mutable lexical closures
  • Structs & Methods: Object-oriented programming with struct definitions and static methods
  • Comprehensive Data Types: integers, floats, strings, booleans, lists, tuples, dictionaries, ranges
  • Control Flow: if/else conditionals, while loops, for loops with iterators, break/continue
  • Format Strings: Embedded expressions within string literals (f-strings)
  • Short-Circuit Evaluation: Truthiness-based and/or operators with short-circuit semantics
  • Recursion: Full support for recursive functions with tail call optimization
  • String Manipulation: Slicing, indexing, and string operations
  • Collection Operations: List/dict concatenation, negative indexing, in-place modification

Virtual Machine Architecture

  • Stack-Based Execution: Efficient push/pop operations for expression evaluation
  • Call Frame Stack: Proper function call semantics with frame pointers (not child VMs)
  • Shared Locals Vector: Memory-efficient local variable storage across frames
  • Tail Call Optimization: TailCall opcode reuses frames for recursive functions
  • 80+ Opcodes: Comprehensive instruction set including specialized zero-operand variants

Compiler Optimizations

  • Constant Folding: Arithmetic and boolean expressions evaluated at compile time
  • Specialized Opcodes: LoadConst0, LoadLocal0-3, LoadGlobal0-3 for common indices
  • Stack Operations: Dup, Swap, Pop2, Pop3 for efficient manipulation
  • Memory-Optimized: u32 indices reduce instruction size from 16 to 12 bytes
  • Forward Declaration: Two-pass compilation for mutual recursion support

Memory Management

  • Mark-and-Sweep GC: Automatic garbage collection with configurable thresholds
  • Arena Allocation: Separate DenseSlotMap arenas for each heap type
  • Memory Tracking: Byte-level allocation tracking triggers GC intelligently
  • String Interning: Via strena for deduplicated string storage
  • Custom Allocator: mimalloc for improved allocation performance

std/core Functions

Function Description
core.len(x) Get length of string, list, tuple, dict, or module
core.iter(x) Create an iterator for an iterable value
core.str(x) Convert value to string
core.type(x) Get type name of a value
core.input(prompt) Read user input with prompt
core.gc() Manually trigger garbage collection (VM mode)
core.heap_stats() Get heap statistics as a dict (VM mode)
core.gc_threshold(n) Configure GC allocation threshold (VM mode)

core is auto-imported by the prelude in every file. You only need an explicit import if you want a different alias:

import "std/core" as c;

Standard Library

Walrus provides a standard library through an import system. Modules are imported using the import statement and return a module value backed internally by a hash map.

import "std/io";
import "std/sys" as system;
import "std/math";
import "std/net";
import "std/http";

Package Imports (@name)

Walrus supports package-style imports with pinned lock entries.

1. Declare dependencies in Walrus.toml

[package]
name = "my-app"
version = "0.1.0"

[dependencies]
greeter = { version = "1.2.3", path = "./deps/greeter" }

Current scope: local path dependencies.

2. Generate lockfile

walrus --sync-lock

This creates or updates Walrus.lock with pinned package versions and resolved paths.

Example Walrus.lock:

version = 1

[packages.greeter]
version = "1.2.3"
path = "deps/greeter"

3. Import and use

import @greeter as g;

println(g.message);
println(g.double(21));

Imported modules/packages are module values (map-backed internally):

  • Preferred: dot access (g.message, g.double(21)).
  • Use indexing (g["name"]) only when the member name is dynamic or not a valid identifier.

Behavior Notes

  • If Walrus.toml is present, import @name resolves through Walrus.lock.
  • If the manifest version and lock version differ for a package, execution fails with a pin mismatch error.
  • If no Walrus.toml is found, Walrus falls back to resolving @name as ./name/main.walrus relative to the importing file.

std/io - File I/O Operations

Function Description
io.file_open(path, mode) Open a file, returns a handle. Modes: "r", "w", "a", "rw"
io.file_read(handle) Read entire file contents as string
io.file_read_line(handle) Read a single line (returns void at EOF)
io.file_write(handle, content) Write string to file, returns bytes written
io.file_close(handle) Close a file handle
io.file_exists(path) Check if a file exists (returns bool)
io.read_file(path) Convenience: read entire file in one call
io.write_file(path, content) Convenience: write entire file in one call

Example: File Operations

import "std/io";

// Simple read/write (convenience functions)
io.write_file("hello.txt", "Hello, Walrus!");
let content = io.read_file("hello.txt");
println(content);  // Hello, Walrus!

// Handle-based operations (for larger files or streaming)
let handle = io.file_open("data.txt", "w");
io.file_write(handle, "Line 1\n");
io.file_write(handle, "Line 2\n");
io.file_close(handle);

// Reading with handles
let reader = io.file_open("data.txt", "r");
let line = io.file_read_line(reader);
while line != void {
    println(line);
    line = io.file_read_line(reader);
}
io.file_close(reader);

std/sys - System Operations

Function Description
sys.env_get(name) Get environment variable (returns void if not set)
sys.args() Get command line arguments as a list
sys.cwd() Get current working directory
sys.exit(code) Exit the program with the given status code

Example: System Information

import "std/sys";

// Environment variables
let home = sys.env_get("HOME");
if home != void {
    println(f"Home directory: {home}");
}

// Command line arguments
let args = sys.args();
println(f"Program: {args[0]}");
if core.len(args) > 1 {
    println(f"Arguments: {args[1..]}");
}

// Current directory
println(f"Working directory: {sys.cwd()}");

// Exit with status
if core.len(args) < 2 {
    println("Usage: walrus script.walrus <args>");
    sys.exit(1);
}

std/net - TCP Networking (VM mode)

Function Description
net.tcp_bind(host, port) Bind a TCP listener and return a listener handle
net.tcp_accept(listener) Accept one incoming connection and return a stream handle
net.tcp_connect(host, port) Connect to a host/port and return a stream handle
net.tcp_local_port(listener) Return the listener's local bound port (useful with port 0)
net.tcp_peer_addr(stream) Return the remote peer address for a connected stream
net.tcp_stream_local_addr(stream) Return the local socket address for a connected stream
net.tcp_set_read_timeout(stream, ms_or_void) Set or clear the read timeout on a stream
net.tcp_set_write_timeout(stream, ms_or_void) Set or clear the write timeout on a stream
net.tcp_set_nodelay(stream, enabled) Enable or disable TCP_NODELAY
net.tcp_shutdown(stream, how) Shutdown read, write, or both sides of a TCP stream
net.tcp_read(stream, max_bytes) Read up to max_bytes bytes (returns void at EOF)
net.tcp_read_line(stream) Read one line (returns void at EOF)
net.tcp_write(stream, content) Write UTF-8 content and return bytes written
net.tcp_close(stream) Close a stream handle
net.tcp_close_listener(listener) Close a listener handle

Example: Loopback Round Trip

import "std/net" as net;

let listener = net.tcp_bind("127.0.0.1", 0);
let port = net.tcp_local_port(listener);

let client = net.tcp_connect("127.0.0.1", port);
let server = net.tcp_accept(listener);

net.tcp_write(client, "ping\n");
println(net.tcp_read_line(server)); // ping

net.tcp_write(server, "pong\n");
println(net.tcp_read_line(client)); // pong

net.tcp_close(client);
net.tcp_close(server);
net.tcp_close_listener(listener);

std/http - HTTP Helpers (VM mode)

std/http is backed by Hyper HTTP primitives (hyper::http) plus httparse for HTTP/1 request parsing.

Function Description
http.parse_request_line(line) Parse request line into { ok, method, target, path, query, version }
http.parse_query(query) Parse query string into a dict
http.parse_query_pairs(query) Parse query string into a decoded list of [key, value] pairs
http.normalize_path(path) Normalize path (collapse repeated slashes, trim trailing slash)
http.match_route(pattern, path) Route matching with :param segments and trailing * wildcard
http.status_text(status) Return status reason phrase (for example 404 -> "Not Found")
http.response(status, body) Build an HTTP/1.1 response string with default headers
http.response_with_headers(status, body, headers) Build response with caller-provided headers dict
http.make_response(status, body) Build a response object {status, body, headers, header_pairs}
http.make_response_with_headers(status, body, headers) Build a response object with headers
http.serialize_response(response) Serialize a response object back into an HTTP/1.1 response string
http.read_request(stream, max_body_bytes) Read/parse one HTTP request (void on EOF, { ok: false, error } on malformed input)

Example: Minimal HTTP Echo

import "std/net" as net;
import "std/http" as http;

let listener = net.tcp_bind("127.0.0.1", 8081);
let stream = net.tcp_accept(listener);

let req = http.read_request(stream, 8192);
if req != void and req["ok"] {
    let body = "echo\n" + req["body"] + "\n";
    let response = http.response_with_headers(200, body, {"x-walrus": "http"});
    net.tcp_write(stream, response);
}

net.tcp_close(stream);
net.tcp_close_listener(listener);

HTTP Benchmark Snapshot

Representative local loopback run using benchmarks/http_bench/run_http_bench.py: ab -k, 10,000 requests per scenario, 1,000 warmup requests, concurrency 32, peak RSS sampled from the server process.

Target GET /health GET /users/42/posts/7 POST /echo Peak RSS
Walrus 23,441 req/s, p95 2 ms, p99 2 ms 21,285 req/s, p95 2 ms, p99 2 ms 17,633 req/s, p95 3 ms, p99 5 ms 23.42 MiB
Flask + Waitress 5,740 req/s, p95 11 ms, p99 21 ms 6,806 req/s, p95 8 ms, p99 13 ms 6,953 req/s, p95 8 ms, p99 9 ms 44.23 MiB
FastAPI + Uvicorn 6,680 req/s, p95 6 ms, p99 11 ms 5,883 req/s, p95 7 ms, p99 13 ms 9,528 req/s, p95 4 ms, p99 5 ms 59.62 MiB

On this run, Walrus delivered roughly 3.5x FastAPI/Uvicorn's throughput on GET /health while using under half its memory. See benchmarks/http_bench/README.md for methodology and rerun instructions.

std/math - Math and Random Utilities

Function Description
math.pi() Returns π
math.e() Returns Euler's number e
math.tau() Returns τ (2π)
math.inf() Returns positive infinity
math.nan() Returns NaN
math.abs(x) Absolute value
math.sign(x) Sign of x as -1, 0, or 1
math.min(a, b) Smaller of two numbers
math.max(a, b) Larger of two numbers
math.clamp(x, lo, hi) Clamp value to a range
math.floor(x) Floor
math.ceil(x) Ceiling
math.round(x) Round to nearest integer value
math.trunc(x) Truncate fractional part
math.fract(x) Fractional part
math.sqrt(x) Square root (x must be >= 0)
math.cbrt(x) Cube root
math.pow(x, y) x raised to y
math.hypot(x, y) Euclidean norm sqrt(x*x + y*y)
math.sin(x) Sine (radians)
math.cos(x) Cosine (radians)
math.tan(x) Tangent (radians)
math.asin(x) Inverse sine (x in [-1, 1])
math.acos(x) Inverse cosine (x in [-1, 1])
math.atan(x) Inverse tangent
math.atan2(y, x) Inverse tangent with quadrant awareness
math.exp(x) e^x
math.ln(x) Natural log (x > 0)
math.log2(x) Base-2 log (x > 0)
math.log10(x) Base-10 log (x > 0)
math.log(x, base) Logarithm with custom base (x > 0, base > 0, base != 1)
math.lerp(a, b, t) Linear interpolation between a and b
math.degrees(r) Convert radians to degrees
math.radians(d) Convert degrees to radians
math.is_finite(x) Returns true if finite
math.is_nan(x) Returns true if NaN
math.is_inf(x) Returns true if infinite
math.seed(n) Seed RNG for deterministic random sequences
math.rand_float() Random float in [0.0, 1.0)
math.rand_bool() Random boolean (50/50)
math.rand_int(a, b) Random integer in [a, b] (inclusive)
math.rand_range(a, b) Random float in [a, b)

Example: Math + Random

import "std/math";

println(math.pi());
println(math.sqrt(49));
println(math.pow(2, 10));
println(math.lerp(10, 20, 0.25)); // 12.5

math.seed(1337);
let id = math.rand_int(1000, 9999);
println(f"ID: {id}");
println(math.rand_bool());

let angle = math.radians(45);
println(math.sin(angle));

Developer Experience

  • Debugger Support: Breakpoints, step-through execution, and stack inspection
  • Stack Traces: Human-readable call stack on errors (truncated for deep recursion)
  • Comprehensive Error Messages: Detailed error reporting with source locations
  • Benchmark Suite: 20+ benchmarks comparing against Python

JIT Compilation (Optional)

Walrus includes an optional Just-In-Time (JIT) compiler powered by Cranelift that can dramatically speed up hot loops by compiling them to native machine code.

Performance

Benchmark VM JIT Speedup
10K iterations × sum(0..1000) 0.68s 0.01s ~68x

Building with JIT Support

# Build with JIT feature enabled
cargo build --release --features jit

# Or add to your Cargo.toml
[features]
jit = ["cranelift-codegen", "cranelift-frontend", "cranelift-jit", "cranelift-module", "cranelift-native"]

Using the JIT

# Run with JIT enabled
./target/release/walrus program.walrus --jit

# Show JIT profiling statistics
./target/release/walrus program.walrus --jit --jit-stats

# Disable profiling (baseline comparison)
./target/release/walrus program.walrus --no-jit-profile

What Gets JIT Compiled

The JIT compiler targets hot integer range loops that follow specific patterns. A loop becomes "hot" after 1000 iterations.

JIT-Compatible Patterns:

// Pattern 1: Sum accumulation (acc += i)
let sum = 0;
for i in 0..n {
    sum = sum + i;
}

// Pattern 2: Simple iteration with reassignment
let result = 0;
for i in 0..n {
    result = result + some_value;
}

// Pattern 3: Count iterations
let count = 0;
for _ in 0..n {
    count = count + 1;
}

// Pattern 4: Simple println of loop variable
for i in 0..n {
    println(i);  // Single println of integer is JIT-compiled
}

// Pattern 5: Sum with println
let sum = 0;
for i in 0..n {
    sum = sum + i;
    println(i);  // Can be combined with accumulation
}

NOT JIT-Compatible (falls back to interpreter):

// Function calls inside loop
for i in 0..n {
    result = compute(i);  // Contains Call opcode
}

// Multiple print operations per iteration
for i in 0..n {
    print(i);     // Multiple prints = not JIT-compatible
    print(" ");   // String printing not yet supported
}

// Complex operations (method calls, string ops, etc.)
for i in 0..n {
    list.push(i);  // Method calls / heap operations
}

How It Works

  1. Hot-Spot Detection: The VM profiles loop iterations and function calls
  2. Type Analysis: Tracks runtime types to ensure type stability
  3. Bytecode Analysis: Examines loop body for JIT-compatible operations
  4. Native Code Generation: Uses Cranelift to compile to native machine code
  5. External Callbacks: Print operations call back into Rust for I/O
  6. Seamless Execution: JIT code reads/writes the same local variables as the interpreter

Example JIT Output (with --jit-stats):

Hot-spot Statistics:
  Total tracked regions: 3
  Hot loops: 1
  Hot functions: 1
  JIT compiled regions: 1
  Hottest spot: loop@5..5 (402000x)

Type Profile: 4 locations observed
JIT Stats: 1 functions compiled

Generated Native Code

For a simple sum loop, the JIT generates optimized native code:

// Walrus: for i in 0..n { sum = sum + i; }
// Cranelift IR:
block1(i, acc):
    cmp i < end
    branch to body or exit
block2:
    acc = acc + i
    i = i + 1
    jump block1
block3:
    return acc

Installation

Prerequisites

  • Rust (latest stable version)

Build from Source

git clone https://github.com/sb2bg/walrus.git
cd walrus
cargo build --release

Releases

Releases are automated from GitHub Actions:

  • When Cargo.toml's version changes on master, the Release workflow creates a GitHub release with tag v<version>.
  • If the version did not change (or the tag already exists), the workflow exits without creating a release.

Run

# Start the interactive REPL
cargo run --

# Execute a file
cargo run -- file.walrus

# Compile mode (VM execution)
cargo run -- -c file.walrus

# Disassemble bytecode
cargo run -- -d file.walrus

# View help
cargo run -- --help

VS Code (LSP + Syntax Highlighting)

Walrus now includes a language server and a VS Code extension scaffold in vscode/walrus.

# Build the language server
cargo build --release --bin walrus-lsp

# Install extension dependencies
cd vscode/walrus
npm install

Then open vscode/walrus in VS Code and press F5 to launch an Extension Development Host. Open any .walrus file to get:

  • Parse diagnostics
  • Rich hover for symbols, keywords, and standard library APIs
  • Scoped completion (locals, functions, structs, modules, keywords)
  • Signature help for function and standard library calls
  • Document symbols (nested outline)
  • Go-to-definition
  • Find references
  • Rename symbol
  • Document highlights (read/write)
  • Syntax highlighting

Quick Start

Hello World

println("Hello, World!");

Comments

Walrus supports both single-line and multi-line comments:

// This is a single-line comment

/* This is a multi-line comment
     that can span multiple lines */

let x = 42; // Comments can appear after code

/* Multi-line comments can also be inline */ let y = 84;

Variables & Types

let number = 42;
let pi = 3.14159;
let name = "Walrus";
let active = true;
let items = [1, 2, 3, 4, 5];
let pair = ("key", 42);
let data = {"key": "value", "count": 10};

One-element tuples require a trailing comma: (value,). Dictionary iteration yields (key, value) tuples. Dictionary keys are limited to immutable primitive values.

Truthiness

Walrus conditions and short-circuit logic are truthiness-based.

Falsy values:

  • false
  • void
  • 0 and 0.0
  • Empty string/list/dict/module/tuple/range

Truthy values:

  • Everything else

and/or short-circuit and return operand values. not returns a boolean.

Format Strings

let name = "Alice";
let age = 30;
let score = 95.5;

// Basic interpolation
println(f"Hello, {name}!");

// Multiple expressions
println(f"User: {name}, Age: {age}");

// Arithmetic expressions
println(f"Next year you'll be {age + 1}");

// Complex expressions
let items = [1, 2, 3];
println(f"List has {core.len(items)} items: {items}");
println(f"Score: {score * 100 / 100}%");

Functions

fn factorial : n {
        if n <= 1 {
                return 1;
        }
        return n * factorial(n - 1);
}

let result = factorial(5);  // 120

Anonymous Functions

let square = : x {
        return x * x;
};

let result = square(7);  // 49

Nested named and anonymous functions close over lexical variables by reference. Captures remain alive after the defining function returns and can be mutated by sibling closures:

fn make_counter : initial {
        let value = initial;
        return : {
                value += 1;
                return value;
        };
}

let next = make_counter(10);
println(next()); // 11
println(next()); // 12

Extracting an instance method also preserves its receiver, so let callback = value.method; can be passed around and called later without supplying self explicitly.

Structs

struct Point {
        fn new : x, y {
                return {"x": x, "y": y};
        }

        fn distance : p1, p2 {
                let dx = p1["x"] - p2["x"];
                let dy = p1["y"] - p2["y"];
                return (dx * dx + dy * dy) ** 0.5;
        }
}

let p1 = Point.new(3, 4);
let p2 = Point.new(0, 0);
let dist = Point.distance(p1, p2);  // 5.0

Control Flow

// Conditionals
if score >= 90 {
        println("A");
} else if score >= 80 {
        println("B");
} else {
        println("C");
}

// While loop
let i = 0;
while i < 5 {
        println(i);
        i = i + 1;
}

// For loop with range
for x in 0..10 {
        println(x);
}

// For loop with list
for item in [1, 2, 3, 4, 5] {
        println(item);
}

FizzBuzz Example

for n in 1..101 {
        let result = "";

        if n % 3 == 0 {
                result += "Fizz";
        }

        if n % 5 == 0 {
                result += "Buzz";
        }

        if result == "" {
                result = n;
        }

        println(result);
}

Quicksort Example

fn quicksort : arr {
        if core.len(arr) <= 1 {
                return arr;
        }

        let pivot = arr[0];
        let less = [];
        let equal = [];
        let greater = [];

        for x in arr {
                if x < pivot {
                        less.push(x);
                } else if x == pivot {
                        equal.push(x);
                } else {
                        greater.push(x);
                }
        }

        return quicksort(less) + equal + quicksort(greater);
}

let sorted = quicksort([64, 34, 25, 12, 22, 11, 90, 88]);

Architecture

Virtual Machine

The Walrus VM is a stack-based bytecode interpreter with proper call frames:

  • 80+ Opcodes: Comprehensive instruction set for all language features
  • Call Frame Stack: Each function call pushes a frame with return address, frame pointer, and bytecode reference
  • Shared Locals Vector: All frames share a single locals vector, indexed by frame pointer + local index
  • Global Variables: Stored in indexed module/global tables for cross-frame persistence
  • Lexical Upvalues: Captured locals are promoted to GC-traced mutable cells
  • Tail Call Optimization: Recursive tail calls reuse the current frame instead of pushing new ones
  • Automatic GC: Collection triggered by allocation count or memory threshold

Bytecode Compiler

  • Two-Pass Compilation: Forward declarations for mutual recursion support
  • Constant Folding: Evaluates 1 + 2 * 3 at compile time
  • Jump Patching: Forward references resolved after compilation
  • Loop Context Tracking: Break/continue with proper local cleanup
  • Scope Management: Depth tracking distinguishes local vs. global variables
  • Symbol Tables: Separate tracking for locals and globals with indices

Garbage Collector

The mark-and-sweep GC traces from roots and frees unreachable objects:

  • Root Collection: Stack, locals, globals, and constants are all roots
  • Configurable Thresholds: Default 1024 allocations or 8MB estimated memory
  • Memory Estimation: Tracks approximate bytes for lists, dicts, functions, etc.
  • Statistics Tracking: Total bytes freed, collection count available via core.heap_stats()
  • Manual Trigger: core.gc() for explicit collection

Memory Management

  • Arena-Based Heap: Separate DenseSlotMap arenas for lists, tuples, dicts, functions, iterators, structs
  • String Interning: Equal strings share one arena entry through a reverse lookup table
  • Generation-Indexed Keys: Type-safe references prevent use-after-free
  • Shared Heap Values: Values are small generation-indexed keys; mutable collections use identity semantics

Development

Running Tests

# Run test suite
./scripts/run-tests.sh

# Run benchmarks (Python vs Walrus comparison)
cd benchmarks && ./run_benchmarks.sh

Building Optimized

# Release build
./scripts/build-optimized.sh

# Platform-specific
./scripts/build-linux-optimized.sh
./scripts/build-windows-optimized.sh

Profiling

# Memory profiling with dhat
./scripts/dhat.sh

# CPU profiling (macOS)
./scripts/instruments.sh

Examples

Check out the showcase.walrus file for a comprehensive demonstration of all language features, including:

  • Bank account system
  • Game character battles
  • Prime number generation
  • Text processing and palindromes
  • Mathematical sequences
  • Higher-order functions

Run it with:

cargo run -- showcase.walrus

Additional example projects:

  • examples/json_parser/ - A simple recursive-descent JSON parser project in Walrus.
  • examples/pathfinding_visualizer/ - A deterministic A* maze pathfinding demo with ASCII search snapshots.
  • examples/terminal_roguelike/ - A simulation-style ASCII roguelike demo using structs, RNG, file I/O, and VM try/catch.

Contributing

Contributions are welcome! Areas of interest:

  • Standard library expansion
  • Performance optimizations
  • Additional language features
  • Documentation improvements

Please check our issue tracker for open tasks.

License

Walrus is licensed under the MIT License. See LICENSE for details.

Acknowledgments

Built with:


Happy Coding with Walrus! 🦭

About

Garbage collected, bytecode compiled, JIT programming language.

Topics

Resources

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages