Unfish Docs & Downloads Official Guide โ€ข v2.1.0
Ctrl+K
๐Ÿก Unfish Play ๐Ÿ’ป Studio IDE ๐Ÿ  Portal โญ GitHub

๐Ÿ“ฅ Official Downloads & Toolchain (v2.1.0)

Unfish is packaged with zero external dependencies. Everything you need โ€” compiler, virtual machines, interactive debugger, formatter, and language server โ€” is bundled in a single self-contained executable.

โšก One-Line Universal Install

Paste this command into your terminal to install the latest prebuilt Unfish binary to ~/.unfish/bin:

curl -fsSL https://ashokwebs.github.io/unfish/install.sh | bash

๐Ÿ“ฆ Official Packages & Artifacts

๐Ÿง
Unfish CLI Engine
Linux x86_64 Standalone
v2.1.0
The complete unified CLI toolchain containing the AST interpreter, 59-opcode stack VM, 256-register VM, C99 AOT compiler, WebAssembly emitter, LSP server, debugger, and standard library.
โฌ‡๏ธ Download Tarball (289 KB)
๐Ÿงฉ
VS Code Extension
Visual Studio Code VSIX
v2.1.0
Rich syntax highlighting, bracket matching, indentation rules, code formatting, and Language Server Protocol (LSP) client support for VS Code & VSCodium.
โฌ‡๏ธ Download Extension (.vsix)
โšก
Single-Header Runtime
unfish_runtime.h (ANSI C99)
C99
The standalone single-header C99 runtime with open-addressed hash tables, garbage-collected value types, and memory arenas. Embed Unfish AOT compiled code into any C project.
โฌ‡๏ธ Download Header (141 KB)
๐Ÿ—๏ธ
Complete Source Code
Git Repository & Tests
MIT
Pure ANSI C99 source code with zero dependencies. Includes 21 unit test suites, stress suites, 104 differential conformance tests, and 13 benchmarks. Builds with make in 2 seconds.
โฌ‡๏ธ Download Source (.tar.gz)

๐Ÿ› ๏ธ Extension Installation Guide

Install VS Code Extension from Terminal
code --install-extension unfish-language-2.1.0.vsix

โšก Quick Start & CLI Reference

Once installed, the unified unfish binary provides commands across the entire software development lifecycle:

Command Description
unfish run <file>Execute script via tree-walking AST interpreter
unfish run --vm <file>Execute script via 59-opcode Stack Virtual Machine (2xโ€“6x faster)
unfish run --regvm <file>Execute script via 256-register computed-goto VM
unfish build [-o bin] <file>Compile script directly into native standalone C99 binary (25โ€“60x speedup)
unfish build --wasm <file>Compile script into WebAssembly binary module
unfish format -i <file>Format source code to canonical Unfish style in place
unfish debug <file>Launch interactive step debugger with breakpoints and stack traces
unfish check [--strict] <file>Validate syntax and gradual type annotations
unfish lspStart Language Server Protocol (JSON-RPC 2.0)
unfish replStart interactive read-eval-print loop
unfish playground [--port <p>]Launch local web playground server
unfish studio [--port <p>]Launch full local browser Studio IDE

๐Ÿ› ๏ธ Installation & Build Guide

Unfish requires only a standard C99 compiler (such as gcc or clang) and standard POSIX make. It has zero external dependencies beyond libc and -lm.

Building from Source

Terminal Shell (Bash)
git clone https://github.com/ashokwebs/unfish.git
cd unfish
make -j4
./bin/unfish version

Running the Test Suites

Verification Commands
# Run all 21 unit test suites and the 104-test differential conformance suite
make test

# Verify zero memory leaks under AddressSanitizer & UndefinedBehaviorSanitizer
make test-asan

# Run multi-tier performance benchmarks
make bench

๐Ÿก What is Unfish?

Unfish is a real, general-purpose programming language, runtime, compiler, and educational computing environment written in clean ANSI C99 with zero external dependencies.

It is designed to provide an unbroken path of intellectual ascent: from visual block programming, into clean indentation-based text programming, to algorithms, data structures, runtime architecture, garbage collection, bytecode virtual machines, and low-level systems programming.

"Hide complexity until the learner is ready โ€” never hide it forever."

Five Execution Engines in Lockstep

Unfish implements five distinct execution backends, kept in lockstep by a differential test suite that requires byte-identical output from every one of them:

  1. Tree-Walking AST Interpreter: Immediate startup, direct AST evaluation, ideal for REPL and visual block editing.
  2. Bytecode Virtual Machine (--vm): 59-opcode stack VM with lexical upvalue capture cells, exception unwinding, and 2xโ€“6x speedup over AST tree-walking.
  3. Register-Based Bytecode VM (--regvm): 256-register, 3-address VM with computed-goto dispatch.
  4. Native C99 AOT Compiler (build): Direct transpilation into standalone ANSI C99 with single-header runtime, measured at 25โ€“60x faster than AST.
  5. WebAssembly Target (build --wasm): Portable WASM binary running in browsers and edge runtimes.

๐Ÿš€ Your First Program

Unfish source code uses the .unfish or .uf extension. Here is a program demonstrating string interpolation, structs, and list transformations:

first_program.unfish

๐Ÿ’ป REPL & Developer Toolchain

The unfish executable provides a unified interface across the development lifecycle:

CommandPurpose
unfish run <file>Execute script using tree-walking AST interpreter
unfish run --vm <file>Execute script using Stack Bytecode VM
unfish run --regvm <file>Execute script using Register Bytecode VM
unfish build -o <bin> <file>Compile program to native executable binary
unfish build --wasm <file>Compile program to WebAssembly module
unfish format -i <file>Format source code to canonical style in place
unfish debug <file>Launch interactive step debugger with breakpoints
unfish studioLaunch the browser IDE & visual blocks canvas
unfish lspStart Language Server Protocol server for VS Code
unfish replLaunch interactive Read-Eval-Print Loop

๐Ÿ”ฌ Learn the Computer through Unfish

A defining mission of Unfish is to teach computer systems by making every layer of language execution visible and tangible. Type any code below and click through the tabs to observe how human-readable text transforms into tokens, syntax trees, bytecode instructions, and runtime memory.

Interactive Architectural Pipeline Explorer

          

01. Hello World & Text Output

In Unfish, printing to standard output is done with say (appends a newline) or the print(value) function (no trailing newline). Unfish also features Python-style string interpolation via f"...".

tut01_output.uf

02. Values & Primitive Types

Unfish runtime values are represented internally by a 16-byte tagged union (UfValue). The primitive types are:

  • Numbers โ€” 64-bit IEEE 754 floating-point numbers (42, 3.14159, 0xFF, 1e6).
  • Strings โ€” Contiguous UTF-8 encoded text with length tracking and string interning.
  • Booleans โ€” true or false.
  • Null โ€” Represents absence of value (null).
tut02_values.uf

03. Variables & Lexical Scope

Variables in Unfish are declared with the let keyword. Once declared, variables can be reassigned with =:

tut03_variables.uf

04. Expressions & Operators

Unfish parses expressions using Vaughan Pratt's top-down operator precedence table. It supports standard arithmetic (+, -, *, /, %), comparisons, logical operators (and, or, not), and the forward pipe operator (|>).

tut04_operators.uf

05. Conditional Logic

Blocks in Unfish are delimited using off-side indentation (4 spaces) introduced with a colon :.

tut05_conditions.uf

06. Loops & Iteration

Unfish supports three loop constructs:

  • while <condition>: โ€” Loop while condition remains truthy.
  • for item in <collection>: โ€” Iterate over arrays, strings, or map keys.
  • repeat <count> times: โ€” Dedicated beginner-friendly fixed count loop.
tut06_loops.uf

07. Functions & Default Parameters

Functions are declared with function or fn and can include default parameter values:

tut07_functions.uf

08. Higher-Order Functions

Functions can be passed as arguments or returned from other functions. Unfish provides map, filter, reduce, sort, and zip out of the box:

tut08_higher_order.uf

09. Closures & Functional Pipelines

Functions in Unfish capture variables from their enclosing lexical environments. The pipe operator (|>) allows chaining function calls cleanly without nested parentheses.

tut09_closures.uf

10. Arrays, Maps & Byte Buffers

Unfish features dynamic lists, hash maps with string keys, and contiguous binary byte buffers for low-level systems programming:

tut10_collections.uf

11. Destructuring & Rest Patterns

Unpack values from arrays or maps into named variables using let [a, b, ...rest] = ...:

tut11_destructuring.uf

12. Structs & Object Methods

Unfish provides lightweight structs with instance fields and methods bound to self:

tut12_structs.uf

13. Enums & Pattern Matching

Enums in Unfish are tagged sum types whose variants can carry data, matched with match ... when ...:

tut13_enums.uf

14. Error Recovery (try / catch / finally)

Robust exception handling allows recovering from runtime errors or user-thrown exceptions with structured try ... catch err: ... finally: ...:

tut14_errors.uf

15. Memory Model & Garbage Collector

The Unfish runtime is designed for predictable low latency and tiny memory footprints:

  • Tagged Unions (16 bytes): All stack values fit inside UfValue containing a 64-bit payload and an 8-bit type tag.
  • Arena Allocators: Parsing AST nodes and compilation bytecode chunks use fast linear bump arenas freed in O(1) time.
  • Mark-Sweep GC: Objects on the heap (strings, arrays, maps, instances, fibers) are managed by an exact tri-color mark-sweep garbage collector.

16. VM & Native Compilation

Unfish lets you choose your execution strategy depending on performance requirements:

TierBackendSpeedupUse Case
Tier 1AST Tree-Walker1x (baseline)Immediate REPL, scripting, visual blocks
Tier 2Stack VM (59 opcodes)2xโ€“6xFast bytecode execution, debug traces
Tier 3Register VM (256 regs)4xโ€“10xHigh-throughput computed goto dispatch
Tier 4Native C99 AOT25xโ€“60xStandalone standalone binaries, embedded microcontrollers
Tier 5WebAssembly15xโ€“35xBrowser playgrounds, edge worker runtimes

17. Cooperative Fibers & CSP Channels

Concurrency in Unfish is based on lightweight green threads (fibers) and Communicating Sequential Processes (CSP) channels, operating with deterministic scheduling and zero race conditions.

tut17_concurrency.uf

18. Traits & Polymorphic Interfaces

Traits define abstract behavioral contracts that structs implement to enable polymorphic dispatch. Traits allow you to write generic algorithms that operate on any type providing required methods.

tut18_traits.uf

19. Pipelines & List Comprehensions

Unfish features point-free pipeline chaining via the |> operator and concise declarative transformations using list and map comprehensions.

tut19_pipelines.uf

20. Systems Programming: Byte Buffers & Bitwise Math

Unfish combines high-level syntax with direct low-level memory buffers (buffer) and 64-bit bitwise operators for wire-level networking and binary protocol framing.

tut20_systems_buffer.uf

21. Asynchronous Coroutines (Async / Await)

Asynchronous programming in Unfish is powered by async fn and await, desugaring to cooperative coroutine fibers and channel-based rendezvous under the centralized runtime scheduler.

tut21_async_await.uf

22. Developer Tooling, Debugger & Language Server

Unfish is equipped with a rich, self-contained developer toolchain built directly into the unified unfish executable:

๐Ÿ› ๏ธ Language Server (LSP)

Implements LSP 3.17 over standard I/O for VS Code, Neovim, and Sublime with diagnostics, hover, completions, and formatting.

unfish lsp

๐Ÿž Interactive Debugger

Step-by-step interactive CLI debugger supporting breakpoints, frame inspection, local variable inspection, and bytecode stepping.

unfish debug app.uf

โœจ Code Formatter

Canonical, deterministic indentation and syntax formatter with in-place rewriting.

unfish format -i app.uf

๐Ÿงช Conformance Test Runner

High-speed test runner executing test suites with ANSI progress and microsecond execution metrics.

unfish test tests/

๐Ÿ“ฆ Package Manager

Dependency management, project scaffolding, and semantic version validation.

unfish pkg init my_app

โšก Native AOT Transpiler

Transpiles Unfish source into standalone, dependency-free ANSI C99 or bare-metal ARM firmware.

unfish emit-c -o app.c app.uf

Core Built-in Functions

All core functions are available globally in every Unfish program without needing an import statement.

FunctionSignatureDescription
say(val)(Any) -> nullPrints value followed by a newline to standard output
print(val)(Any) -> nullPrints value without a trailing newline
len(coll)(Any) -> NumberReturns length of string, array, or byte buffer
type_of(val)(Any) -> StringReturns type name: "number", "string", "channel", etc.
push(arr, val)(Array, Any) -> nullAppends value to dynamic array
pop(arr)(Array) -> AnyRemoves and returns last element
range(a, [b, step])(Number, ...) -> ArrayGenerates range of numbers
concat(a, b)(Array, Array) -> ArrayNon-destructively merges two arrays
flatten(arr)(Array) -> ArrayFlattens one level of nested arrays
zip(a, b)(Array, Array) -> ArrayCombines two arrays element-wise into pairs
pad_start(s, len, [p])(String, Number, [String]) -> StringPads string on the left to target length
pad_end(s, len, [p])(String, Number, [String]) -> StringPads string on the right to target length
trim(s)(String) -> StringRemoves leading and trailing whitespace
trim_start(s)(String) -> StringRemoves leading whitespace
trim_end(s)(String) -> StringRemoves trailing whitespace
chars(s)(String) -> ArrayReturns array of single-character strings
count(s, sub)(String, String) -> NumberCount of non-overlapping occurrences
clock()() -> NumberHigh-resolution wall-clock timestamp in seconds
sqrt(x)(Number) -> NumberSquare root of non-negative number
pow(base, exp)(Number, Number) -> NumberExponentiation calculation
round(x)(Number) -> NumberRounds number to nearest integer
assert(cond, [msg])(Any, [String]) -> nullRaises AssertionError if condition is falsy

Module: sys

Access operating system parameters, environment variables, and process control. Import with import sys or from sys import platform, exit.

FunctionSignatureDescription
sys.platform()() -> StringReturns host OS name: "linux", "darwin", "windows"
sys.exit(code)(Number) -> neverTerminates process with integer exit code
sys.args()() -> ArrayArray of CLI argument strings passed to script
sys.cwd()() -> StringCurrent working directory path string
sys.env(key)(String) -> String|nullReturns environment variable value, or null
sys.set_env(k, v)(String, String) -> BooleanSets host environment variable
sys.exec(cmd)(String) -> NumberExecutes shell command via system()

Module: fs (Sandboxed Filesystem)

File I/O and directory operations. Import with import fs.

FunctionSignatureDescription
fs.read_text(path)(String) -> String|nullReads entire file contents as UTF-8 string
fs.write_text(path, text)(String, String) -> BooleanOverwrites file with text
fs.append_text(path, text)(String, String) -> BooleanAppends text to end of file
fs.exists(path)(String) -> BooleanReturns true if file or directory exists
fs.delete_file(path)(String) -> BooleanDeletes file at path
fs.list_dir(path)(String) -> Array|nullReturns array of entry filenames in directory
fs.mkdir(path)(String) -> BooleanCreates new directory
fs.remove_dir(path)(String) -> BooleanRemoves empty directory
fs.is_file(path)(String) -> BooleanReturns true if path is a regular file
fs.is_dir(path)(String) -> BooleanReturns true if path is a directory
fs.file_size(path)(String) -> Number|nullReturns file size in bytes

Module: time

High-resolution wall clocks, timestamps, and delays. Import with import time.

FunctionSignatureDescription
time.clock()() -> NumberHigh-resolution wall clock in fractional seconds
time.sleep(sec)(Number) -> nullPauses execution for specified seconds
time.timestamp()() -> NumberUNIX epoch timestamp in seconds
time.format(ts)(Number) -> StringFormats timestamp to human-readable string
time.iso(ts)(Number) -> StringFormats timestamp to ISO 8601 string

Module: random

Pseudo-random number generation, shuffling, and seeding. Import with import random.

FunctionSignatureDescription
random.random()() -> NumberReturns float in [0.0, 1.0)
random.random_int(min, max)(Number, Number) -> NumberReturns integer in [min, max] inclusive
random.seed(val)(Number) -> nullSeeds the PRNG state
random.shuffle(arr)(Array) -> ArrayReturns randomly shuffled array copy

Module: json

Fast JSON parsing and serialization into Unfish maps and arrays. Import with import json.

FunctionSignatureDescription
json.parse(str)(String) -> AnyParses JSON text into Unfish value
json.stringify(val)(Any) -> StringSerializes Unfish value into JSON string

Module: testing

Assertions and test suite framework. Run tests via unfish test.

FunctionSignatureDescription
testing.assert(cond, [msg])(Any, [String]) -> nullAsserts truthy condition
testing.assert_eq(a, b, [msg])(Any, Any, [String]) -> nullAsserts value equality
testing.assert_throws(fn, [msg])(Function, [String]) -> nullAsserts function raises an error

๐Ÿ“œ Formal Grammar & Language Specification

Unfish syntax is formally specified in Extended Backus-Naur Form (ISO/IEC 14977). The language utilizes lexical indentation (the off-side rule), structured expression pipelines, algebraic data types, and pattern matching.

Lexical Note: Indentation in Unfish is significant. The lexer tracks leading whitespace on non-blank lines using an internal indentation stack. An increase in indentation emits a virtual TOKEN_INDENT, while a decrease emits one or more TOKEN_DEDENT tokens. Newlines within parentheses (...), brackets [...], and braces {...} are ignored.

Syntactic Grammar in ISO/IEC 14977 EBNF

(* Unfish Formal Language Grammar *)

Program
    = { Statement | TopLevelDecl } ;

TopLevelDecl
    = StructDecl
    | EnumDecl
    | ImportStmt
    | FunctionDecl ;

StructDecl
    = "struct" , Identifier , ":" , NEWLINE ,
      INDENT , { StructField | StructMethod } , DEDENT ;

StructField
    = Identifier , [ "=" , Expression ] , NEWLINE ;

StructMethod
    = ( "fn" | "function" ) , Identifier , "(" , [ ParameterList ] , ")" , ":" , Block ;

EnumDecl
    = "enum" , Identifier , ":" , NEWLINE ,
      INDENT , { VariantDecl } , DEDENT ;

VariantDecl
    = Identifier , [ "(" , IdentifierList , ")" ] , NEWLINE ;

ImportStmt
    = "import" , Identifier , [ "as" , Identifier ] , NEWLINE ;

FunctionDecl
    = ( "fn" | "function" ) , Identifier , "(" , [ ParameterList ] , ")" , ":" , Block ;

ParameterList
    = Parameter , { "," , Parameter } ;

Parameter
    = Identifier , [ "=" , Expression ] ;

Block
    = NEWLINE , INDENT , { Statement } , DEDENT ;

Statement
    = LetDecl
    | Assignment
    | IfStmt
    | WhileStmt
    | ForStmt
    | MatchStmt
    | TryCatchStmt
    | SayStmt
    | ReturnStmt
    | BreakStmt
    | ContinueStmt
    | ExpressionStmt ;

LetDecl
    = "let" , ( Identifier | DestructuringPattern ) , [ "=" , Expression ] , NEWLINE ;

DestructuringPattern
    = "[" , [ PatternList ] , "]"
    | "{" , [ MapPatternList ] , "}" ;

Assignment
    = PrimaryExpr , ( "=" | "+=" | "-=" | "*=" | "/=" | "%=" ) , Expression , NEWLINE ;

IfStmt
    = "if" , Expression , ":" , Block ,
      { "elif" , Expression , ":" , Block } ,
      [ "else" , ":" , Block ] ;

WhileStmt
    = "while" , Expression , ":" , Block ;

ForStmt
    = "for" , Identifier , "in" , Expression , ":" , Block ;

MatchStmt
    = "match" , Expression , ":" , NEWLINE ,
      INDENT , { MatchArm } , DEDENT ;

MatchArm
    = "when" , Pattern , ":" , Block ;

Pattern
    = Identifier , [ "(" , PatternList , ")" ]
    | Literal
    | "_" ;

TryCatchStmt
    = "try" , ":" , Block ,
      "catch" , [ Identifier ] , ":" , Block ,
      [ "finally" , ":" , Block ] ;

SayStmt
    = "say" , Expression , NEWLINE ;

ReturnStmt
    = "return" , [ Expression ] , NEWLINE ;

ExpressionStmt
    = Expression , NEWLINE ;

(* Expression Hierarchy with Pratt Precedence Climbing *)

Expression
    = PipelineExpr ;

PipelineExpr
    = LogicalOrExpr , { "|>" , ( Identifier | CallExpr ) } ;

LogicalOrExpr
    = LogicalAndExpr , { ( "or" | "||" ) , LogicalAndExpr } ;

LogicalAndExpr
    = EqualityExpr , { ( "and" | "&&" ) , EqualityExpr } ;

EqualityExpr
    = RelationalExpr , { ( "==" | "!=" ) , RelationalExpr } ;

RelationalExpr
    = AdditiveExpr , { ( "<" | "<=" | ">" | ">=" ) , AdditiveExpr } ;

AdditiveExpr
    = MultiplicativeExpr , { ( "+" | "-" ) , MultiplicativeExpr } ;

MultiplicativeExpr
    = UnaryExpr , { ( "*" | "/" | "%" ) , UnaryExpr } ;

UnaryExpr
    = ( "-" | "!" | "not" ) , UnaryExpr
    | PrimaryExpr ;

PrimaryExpr
    = Literal
    | Identifier
    | LambdaExpr
    | "(" , Expression , ")"
    | ArrayLiteral
    | MapLiteral
    | PrimaryExpr , "(" , [ ArgumentList ] , ")"
    | PrimaryExpr , "[" , Expression , "]"
    | PrimaryExpr , "." , Identifier ;

LambdaExpr
    = ( "fn" | "function" ) , "(" , [ ParameterList ] , ")" , ":" , ( Expression | Block ) ;

ArrayLiteral
    = "[" , [ Expression , { "," , Expression } ] , "]" ;

MapLiteral
    = "{" , [ MapEntry , { "," , MapEntry } ] , "}" ;

MapEntry
    = ( StringLiteral | Identifier ) , ":" , Expression ;

Pratt Operator Precedence & Associativity Table

PrecedenceOperator CategoryOperatorsAssociativityGrammar Rule
1 (Lowest)Assignment=, +=, -=, *=, /=, %=RightAssignment
2Forward Pipeline|>LeftPipelineExpr
3Logical ORor, ||LeftLogicalOrExpr
4Logical ANDand, &&LeftLogicalAndExpr
5Equality==, !=LeftEqualityExpr
6Relational<, <=, >, >=LeftRelationalExpr
7Additive+, -LeftAdditiveExpr
8Multiplicative*, /, %LeftMultiplicativeExpr
9Unary Prefix- (neg), !, notRightUnaryExpr
10Function Call & Indexf(...), arr[i], obj.propLeftPrimaryExpr
11 (Highest)Primary & Grouping(...), [...], {...}, LiteralsNonePrimaryExpr

โš™๏ธ 59-Opcode Stack VM ISA Specification

The Unfish Virtual Machine is a bytecode-interpreted stack machine implemented in pure ANSI C99. It evaluates 16-byte tagged values (UfValue) on an operand stack with dynamic bounds checking, lexical upvalues for closures, and frame-pointer call unwinding.

Binary Bytecode Container (.ufc Format)

Precompiled Unfish bytecode modules are structured in a binary format with fixed endianness:

+-----------------------------------------------------------------------+
| Magic Bytes: 0x55 0x46 0x56 0x4D ("UFVM")               (4 bytes)     |
+-----------------------------------------------------------------------+
| Version: Major (u8), Minor (u8), Patch (u8), Flags (u8) (4 bytes)     |
+-----------------------------------------------------------------------+
| Constant Pool Count: uint32_t                           (4 bytes)     |
+-----------------------------------------------------------------------+
| Constant Pool Entries: [Type: u8, Length: u32, Data: bytes ...]       |
+-----------------------------------------------------------------------+
| Bytecode Instruction Stream: [Opcode: u8, Operands: var-length ...]   |
+-----------------------------------------------------------------------+
| Source Line Debug Mapping Table: [Offset: u32, Line: u32 ...]         |
+-----------------------------------------------------------------------+

Complete 59-Opcode Instruction Table

Hex Mnemonic Operands Stack Transition Description
0x00OP_CONSTANTu16 const_idx[] -> [val]Pushes constant from pool onto operand stack
0x01OP_NULLโ€”[] -> [null]Pushes null literal
0x02OP_TRUEโ€”[] -> [true]Pushes boolean true
0x03OP_FALSEโ€”[] -> [false]Pushes boolean false
0x04OP_POPโ€”[val] -> []Discards top stack value
0x05OP_DUPโ€”[val] -> [val, val]Duplicates top stack value
0x06OP_LOAD_LOCALu16 slot[] -> [val]Reads value from local stack frame slot
0x07OP_STORE_LOCALu16 slot[val] -> [val]Writes top stack value into local slot
0x08OP_LOAD_GLOBALu16 const_name[] -> [val]Resolves global symbol by constant string name
0x09OP_STORE_GLOBALu16 const_name[val] -> [val]Updates existing global symbol value
0x0AOP_DEFINE_GLOBALu16 const_name[val] -> []Defines new symbol in global environment table
0x0BOP_GET_UPVALUEu8 upval_idx[] -> [val]Loads closed variable from closure upvalues array
0x0COP_SET_UPVALUEu8 upval_idx[val] -> [val]Assigns value to closure upvalue
0x0DOP_CLOSUREu16 fn_const[] -> [closure]Wraps function prototype with captured upvalues
0x0EOP_CLOSE_UPVALUEโ€”[val] -> []Closes open upvalues pointing into exiting stack frame
0x0FOP_ADDโ€”[a, b] -> [a + b]Numeric addition or string concatenation
0x10OP_SUBโ€”[a, b] -> [a - b]Numeric subtraction
0x11OP_MULโ€”[a, b] -> [a * b]Numeric multiplication
0x12OP_DIVโ€”[a, b] -> [a / b]Floating point division
0x13OP_MODโ€”[a, b] -> [a % b]Modulo division remainder
0x14OP_NEGโ€”[a] -> [-a]Arithmetic negation
0x15OP_NOTโ€”[a] -> [!a]Logical boolean inversion
0x16OP_EQโ€”[a, b] -> [bool]Value equality comparison
0x17OP_NEQโ€”[a, b] -> [bool]Value inequality comparison
0x18OP_LTโ€”[a, b] -> [bool]Less than comparison
0x19OP_LTEโ€”[a, b] -> [bool]Less than or equal comparison
0x1AOP_GTโ€”[a, b] -> [bool]Greater than comparison
0x1BOP_GTEโ€”[a, b] -> [bool]Greater than or equal comparison
0x1COP_JUMPu16 offset[] -> []Unconditional forward instruction jump
0x1DOP_JUMP_IF_FALSEu16 offset[cond] -> [cond]Jumps if top value is falsey (keeps value on stack)
0x1EOP_JUMP_IF_ARGu8 idx, u16 off[] -> []Jumps if argument index was supplied at call site
0x1FOP_LOOPu16 offset[] -> []Unconditional backward jump for loops
0x20OP_CALLu8 argc[fn, a1..an] -> [res]Invokes function or closure with argc arguments
0x21OP_CALL_SPREADโ€”[fn, arr] -> [res]Invokes function with unpacked array arguments
0x22OP_RETURNโ€”[res] -> []Pops call frame and yields result to caller
0x23OP_BUILD_ARRAYu16 count[i1..in] -> [arr]Constructs dynamic array with count elements
0x24OP_ARRAY_PUSHโ€”[arr, item] -> [arr]Appends item to array
0x25OP_ARRAY_EXTENDโ€”[arr1, arr2] -> [arr1]Extends array with elements of another array
0x26OP_ARRAY_SLICEu16 start[arr] -> [slice]Extracts subarray starting at given index
0x27OP_ASSERT_ARRAYโ€”[val] -> [val]Throws error if top value is not an array
0x28OP_ASSERT_MAPโ€”[val] -> [val]Throws error if top value is not a map
0x29OP_ARRAY_GET_SAFEu16 idx[arr] -> [elem]Reads array index without throwing; returns null if out of range
0x2AOP_MAP_GET_SAFEโ€”[map, key] -> [elem]Reads map key without throwing; returns null if key missing
0x2BOP_MAP_RESTu16 ex_cnt, ...[map] -> [rest_map]Destructuring map rest pattern (excludes specified keys)
0x2COP_BUILD_MAPu16 pair_cnt[k1,v1..kn,vn] -> [map]Constructs hash map from key-value pairs
0x2DOP_MAP_SETโ€”[map, k, v] -> [map]Inserts or updates key-value pair in map
0x2EOP_MAP_EXTENDโ€”[m1, m2] -> [m1]Shallow merges keys from m2 into m1
0x2FOP_INDEX_GETโ€”[coll, idx] -> [val]Generic subscript indexing (array index or map key)
0x30OP_INDEX_SETโ€”[coll, idx, val] -> [val]Generic subscript mutation
0x31OP_ITER_GETโ€”[coll, idx] -> [val]Fast indexed access for loop iterators
0x32OP_SAYโ€”[val] -> []Converts value to string and outputs with newline
0x33OP_STRUCT_DEFu16 sdef_idx[] -> [sdef]Registers struct type definition schema
0x34OP_INSTANCEu16 sdef_idx[f1..fn] -> [inst]Instantiates struct with initialized field values
0x35OP_PUSH_TRYu16 catch_off[] -> []Pushes exception handler entry onto runtime catch stack
0x36OP_POP_TRYโ€”[] -> []Removes top exception handler on try block success
0x37OP_RETHROWโ€”[err] -> []Reraises caught exception to outer handler
0x38OP_AWAITโ€”[prom] -> [res]Suspends fiber until asynchronous promise resolves
0x39OP_MATCH_SHAPEu8 shape, u16 count[val] -> [bool]Tests a match pattern's shape: array length, map-or-instance, or field count
0x3AOP_MATCH_FIELDu16 index[val] -> [field]Positional field of a struct instance or enum value (null if absent)

๐Ÿš€ 256-Register VM Architecture (RegVM)

While the stack VM provides a compact bytecode footprint, real-world interpretation often incurs high memory traffic due to constant operand pushes and pops. The Unfish RegVM provides a high-throughput alternative based on a 3-address register architecture.

Run RegVM: Execute any Unfish script directly using the Register VM by passing the --regvm flag:
unfish run --regvm script.uf

32-Bit Fixed Instruction Formats

All RegVM instructions are encoded as 32-bit words for optimal cache line alignment and CPU branch prediction:

Format ABC:  [ Opcode : 8b ] [ Reg A : 8b ] [ Reg B : 8b ] [ Reg C : 8b ]
Format ABx:  [ Opcode : 8b ] [ Reg A : 8b ] [ Constant/Offset Bx : 16b  ]
Format AsBx: [ Opcode : 8b ] [ Reg A : 8b ] [ Signed Offset sBx : 16b   ]

Stack VM vs. Register VM Trace Comparison

Consider the arithmetic statement: let result = (a * b) + (c - d)

Stack VM (6 Instructions)

OP_LOAD_LOCAL  0    # push a
OP_LOAD_LOCAL  1    # push b
OP_MUL              # pop b, pop a, push (a*b)
OP_LOAD_LOCAL  2    # push c
OP_LOAD_LOCAL  3    # push d
OP_SUB              # pop d, pop c, push (c-d)
OP_ADD              # pop (c-d), pop (a*b), push sum
OP_STORE_LOCAL 4    # store into result

Register VM (3 Instructions)

ROP_MUL  r4, r0, r1   # r4 = r0 * r1
ROP_SUB  r5, r2, r3   # r5 = r2 - r3
ROP_ADD  r4, r4, r5   # r4 = r4 + r5 (result)

Register Windowing & Calling Convention

Each execution fiber maintains an activation register file of 256 registers. Upon invoking a function:

  • The caller places function arguments into consecutive registers starting at r(A+1).
  • The call instruction ROP_CALL A, B, C shifts the callee's frame base pointer to rA.
  • Inside the callee, argument values map directly to r0, r1, ... without copying.
  • Upon return, ROP_RETURN A transfers the return value back to destination register rC.

๐Ÿ”Œ C99 AOT & Host Embedding Guide

Unfish was engineered from line 1 to be embeddable into host C and C++ applications. The entire runtime is self-contained in unfish_runtime.h with zero external dependencies beyond standard C99.

Compiling to Native Standalone C99

You can transpile any Unfish source code directly into portable ANSI C99 source code:

# Transpile Unfish to C99 source file
unfish emit-c -o game_logic.c game_logic.uf

# Compile directly to a native binary using GCC or Clang
gcc -O3 game_logic.c -lm -o game_logic

Embedding Unfish in a C Host Application

Here is a complete, working host program that embeds Unfish, executes scripts, and registers a custom C function callable from Unfish:

/* host_application.c โ€” Embedding Unfish in C */
#include "unfish_runtime.h"

/* Custom native function exposed to Unfish */
static UfVal native_ocean_depth(void* env, size_t argc, UfVal* args) {
    (void)env;
    double trench_id = (argc > 0 && uf_is_num(args[0])) ? uf_to_num(args[0]) : 0.0;
    printf("[Host C] Fetching bathymetric depth for trench #%.0f\n", trench_id);
    return uf_num(10994.0); /* Mariana Trench depth in meters */
}

int main(void) {
    printf("=== Starting Host Application with Embedded Unfish ===\n");

    /* 1. Initialize host runtime context */
    UfRuntime* rt = uf_runtime_new();

    /* 2. Register native C function into Unfish globals */
    uf_register_native(rt, "get_ocean_depth", native_ocean_depth);

    /* 3. Execute Unfish script from string */
    const char* script =
        "let depth = get_ocean_depth(1)\n"
        "say f'Submersible reached depth: {depth} meters!'\n"
        "let pressure = (depth / 10.0) + 1.0\n"
        "say f'Ambient pressure: {round(pressure)} atmospheres'\n";

    UfValue result = uf_eval_string(rt, script);

    /* 4. Cleanup runtime memory */
    uf_runtime_free(rt);
    printf("=== Unfish runtime cleanly terminated ===\n");
    return 0;
}

Bare-Metal & Microcontroller Deployment (-DUF_EMBEDDED)

For microcontrollers (ARM Cortex-M, ESP32, RISC-V) without standard POSIX memory allocators, compile with -DUF_EMBEDDED. Unfish replaces dynamic malloc with a fixed static buffer and routes text output to a custom UART transmitter:

/* Direct serial output to UART hardware */
void uart_send_char(char c) {
    while (!(USART1->SR & USART_SR_TXE));
    USART1->DR = c;
}

int main(void) {
    /* Route Unfish say statements to hardware UART */
    uf_set_putchar(uart_send_char);

    /* Runs fully within 256 KB static RAM buffer */
    uf_run_compiled_embedded();
    while (1);
}

โš–๏ธ Language Comparison Matrix

Unfish combines the syntax elegance of Python, the lightweight embeddability of Lua, and the concurrency ergonomics of Go into a unified, zero-dependency C99 engine.

Language Feature Unfish (v2.1.0) Python (3.12) Lua (5.4) Go (1.22) Rust (1.77)
Primary Paradigm Multi-paradigm (Structs, Enums, Pattern Matching) Object-Oriented, Dynamic Prototype-based, Procedural Compiled Procedural, Structural Compiled Systems, Ownership
Syntax Style Indentation-based (Off-side rule, colons) Indentation-based (Off-side rule, colons) Keyword-delimited (do...end) C-style Curly braces {} C-style Curly braces {}
Value Representation 16-byte Tagged Union (UfValue) Heap-allocated PyObject* (28+ bytes) 16-byte Tagged TValue Typed struct / Interface fat pointer Zero-cost statically typed enums/structs
Memory Management Arenas + Generational Mark-and-Sweep GC Reference Counting + Cycle GC Incremental Mark-and-Sweep GC Concurrent Tri-color Tracing GC Compile-time Ownership & Borrow Checker
Concurrency Primitive Cooperative Fibers & Typed Channels (CSP) Async/Await Event Loop (GIL) Coroutines (yield / resume) Goroutines & Channels (M:N Preemptive) OS Threads / async runtimes (Tokio)
Execution Backends 5 Backends: AST, Stack VM, RegVM, C99 AOT, WASM Bytecode Interpreter (CPython) Single Register VM Native Machine Code AOT Native Machine Code AOT (LLVM)
C Embedding Single C99 Header (unfish_runtime.h) Complex C API (Python.h, requires Lib) Clean C API (lua.h) Cgo (high context-switch overhead) C FFI (extern "C")
Standalone Binary Size 289 KB (CLI, VM, Debugger, LSP) ~30โ€“50 MB (CPython + stdlib) ~280 KB (lua interpreter) ~2โ€“10 MB (Go runtime binary) ~300 KB โ€“ 5 MB
Startup Time < 2 ms ~30โ€“45 ms < 2 ms ~1โ€“3 ms < 1 ms
External Dependencies Zero (Pure ANSI C99) C runtime, libffi, openssl, zlib Zero (ANSI C) Zero (Self-contained Go runtime) LLVM backend, libc