Skip to content

sb2bg/walrus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

542 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 nested function support
  • Structs & Methods: Object-oriented programming with struct definitions and static methods
  • Comprehensive Data Types: integers, floats, strings, booleans, lists, 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, dict, or module
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 data = {"key": "value", "count": 10};

Tuple values exist at runtime (for example, dictionary iteration yields key/value pairs as tuples), but tuple literals are not yet part of the surface syntax.

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

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 Rc<RefCell<Vec<Value>>> for cross-frame persistence
  • 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: All strings stored once via strena::Interner
  • Generation-Indexed Keys: Type-safe references prevent use-after-free
  • Copy-on-Write Semantics: Values are small (key-based), heap data shared until modified

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

License

Stars

Watchers

Forks

Contributors

Languages