Skip to content
Ridgun
English

Reference

Ridgun Language Specification

Overview

Ridgun is a functional, parallel-first language with natural language operators and strong static typing. Its syntax draws inspiration from Haskell and ML family languages while introducing novel constructs for parallelism and human-readable operator definitions.

Key characteristics:

  • Natural language operators — Operators read like English sentences
  • Implicit parallelism — Parallel execution is a syntactic primitive, not a library call
  • Strong static typing — All types are declared in signatures
  • Pattern-style definitions — Functions and operators are defined by equations

Comments

Single-line comments use --:

-- This is a comment

Strings

Ridgun has two string literal types with different interpolation behavior.

Interpolated Strings (double quotes)

Double-quoted strings support inline code interpolation using {expr}. The expression inside braces is evaluated and its result is inserted into the string.

name = "Ridgun"
"Hello, {name}!"                         -- "Hello, Ridgun!"
"Sum is {sum 3 4}"                       -- "Sum is 7"
"Nested: {factorial 5} done"             -- "Nested: 120 done"

To include a literal brace inside "...", either escape it with a backslash (\{ and \}) or double it ({{ and }}):

"Shell expansion: \$\{VAR:-default\}"    -- "Shell expansion: ${VAR:-default}"
"This is {{not interpolated}}"           -- "This is {not interpolated}"
"Use \{ \} or {{}} for literal braces"   -- "Use { } or {} for literal braces"

Raw Strings (single quotes)

Single-quoted strings are raw — no interpolation is performed. All characters are treated as literal text, including braces.

'Hello, {name}!'                         -- "Hello, {name}!"
'{this is just pure chars}'              -- "{this is just pure chars}"
'No {interpolation} here'               -- "No {interpolation} here"

Summary

SyntaxInterpolationUse case
"..."Yes — {expr} (escape braces with \{/\} or {{/}})Dynamic strings with embedded expressions
'...'No — all literalStatic text, templates, regex patterns

Program Structure

All executable code in Ridgun must live inside a function or operator. The main function is the required entry point for program execution.

fn :: main => ()
      main = echo "Hello, Ridgun!"

A Ridgun source file may contain only:

  • import statements
  • type definitions
  • fn and op declarations and definitions
  • A main function as the entry point

No loose top-level expressions are allowed. Everything that runs must be called from main.

CLI Usage

ridgunc <command> [options] <file.rg>
CommandStatusPurpose
run <file.rg>✅ ActiveCompile and execute a Ridgun source file
check <file.rg>✅ ActiveType-check and analyze static strategies without running
compile <file.rg> [-o out.rgc]✅ ActiveEmit .rgc bytecode artifact
build <file.rg> [-o binary] [--target arch]✅ ActiveBuild standalone native ELF64 executable
build --lib <file.rg> [-o out.rglib]✅ ActiveBuild reusable Ridgun library (.rglib & .so)
build --so <file.rg> [-o out.so]✅ ActiveBuild native ELF64 shared library (ET_DYN .so)
version / --version✅ ActiveShow compiler version and target architecture
help / --help✅ ActiveShow usage information

Common options include --strict / -S (enforce strict canonical Ridgun syntax rules) and --target x86_64-linux / arm64-linux. Project-level workflows (build, test, check, format, deps, interactive REPL) are provided by stable.


Functions & Operators

Functions and natural-language/infix operators are declared by a type signature ending in => <return_type>, followed by one or more equations (cases). The leading fn :: and op :: prefixes are optional:

  • Canonical prefixless form (starts at column 0):
  list len [A]    => Int
  list len []     =  0
  list len (_:xs) =  1 + (list len xs)

  swap A with B => (B, A)
  swap x with y =  (y, x)
  • Explicit prefixed form (fn :: / op ::, starts at column 0 with clauses aligned at column 6):
  fn :: sum Int Int => Int
        sum a   b   =  a + b

  fn :: format app err AppErr        => String
        format app err (IoError msg) =  "IO:{msg}"
  • Natural-language spaces in fn ::, built-ins, and filenames (FR-COMP-005):
  • fn :: declarations and calls support multi-word names separated by spaces (fn :: format app err AppErr => String, fn :: project name => String), just like op :: and prefixless declarations.
  • Built-in runtime and test functions can be called with spaces (assert equals a b, str len s, read file p, cmm live blocks) while keeping _ forms backward-compatible.
  • First-class multi-word function references (map double val xs, ||map double val xs, filter is even xs, fold add pair 0 xs) and .rg filenames with spaces (src/test/ridgun/test core language.rg) are supported natively by ridgunc and stable.

Polymorphic Type Variables (A..Z) & Contextual Keywords (type, with)

To guarantee zero lexical ambiguity between operator words and type parameters in both prefixed (op ::) and prefixless signatures:

  • Every type token in a signature starts with an uppercase letter (or [...] / (...)):
  • Single uppercase letters (A..Z) denote polymorphic type variables (e.g., [A], (A, B), (A => B), swap A with B => (B, A)). Repeated occurrences of a type variable across parameters are unified at each call site and substituted into the return type.
  • Multi-character capitalized identifiers (Int, String, Bool, RExpr, Tensor) denote concrete or user-defined types.
  • Every lowercase token (or operator symbol +, -, *, /, %) in a top-level signature is part of the function or mixfix operator name.
  • Contextual keywords type and with: type only introduces a type declaration at the top level when followed by UpperName =. Everywhere else (fn :: and op :: signatures, operator words, parameter patterns, local variables, and record field names), type and with are valid identifiers/words without ambiguity.
  • A top-level signature with one leading lowercase identifier followed only by types before => is a prefix function (fn); any signature with two or more lowercase words or operator symbols interleaved with types before => is a mixfix/infix/postfix operator (op).

The declaration and definition are separate:

  • Declaration (fn ::) — specifies the name, parameter types, and return type
  • Definition — binds parameter names and provides the implementation

Operators

Operators are Ridgun's distinctive feature. They are named with natural language and defined with op ::. Unlike traditional operators (+, -), Ridgun operators are multi-word expressions that read like sentences.

Syntax

op :: <natural language signature with types> -> <return_type>
      <natural language pattern with params> = <body>

Basic Example

-- Declaration: "add [Int] to [Int]" returns an Int
op :: add Int to Int => Int
      add a to b = a + b

Usage would read as: add 3 to 5 → 8

The words between typed parameters (to, and, in, etc.) are keywords of the operator — they are part of the operator's identity and must appear at call sites.

Parallel Operators (||)

Operators can express parallel execution as a first-class concept using ||:

-- Run two configs in parallel with named DAG bindings (|| expr -> var)
op :: run Conf and Conf in parallel => Res, Res
      run a and b in parallel       =  res_a, res_b
        || run my own process a     -> res_a
        || run my own process b     -> res_b

Key elements:

  • Multiple return values — => Res, Res declares two return values
  • || expr -> var block — Binds sub-computations to result names (where is eliminated; || unifies all parallel branches)
  • -> — Assigns the result of a parallel sub-computation to a variable (or tensor slice -> out[i, j])
  • Independent || branches execute concurrently as a parallel DAG based on data dependencies

Direct Parallel Pipe Block (|| expr)

When an expression or function directly returns a tuple of parallel computations without intermediate variable names, || lines appear directly after =:

-- Run two processes in parallel and return their results as a tuple
op :: another way to run two processes Conf Conf in parallel => Res, Res
      another way to run two processes a b in parallel =
        || run my own process a
        || run my own process b

Each || line runs as an independent parallel task. Results are collected in declaration order.

Operator Overloading

Existing operators (including built-in ones like +) can be overloaded for new types:

-- Overload + for Complex numbers
op :: Complex + Complex => Complex
      a + b = (real a) + (real b) + complex_i * ((imaginary a) + (imaginary b))

Precedence

Symbolic operators have the highest precedence — math first.

When symbolic operators (like +, *) appear inside natural language operator bodies, they bind tighter than the surrounding operator keywords. This ensures arithmetic expressions evaluate as expected without extra parentheses.

PrecedenceCategoryExamples
HighestSymbolic operators*, /, +, -, %
Function applicationf x
Comparison==, !=, <, >
Logicaland, or, xor, not
LowestNatural language operatorsadd a to b, run a and b in parallel

Operator Chaining & Idiomatic Left-to-Right Pipelines (->) (FR-COMP-014)

Operators and functions that transform values or sequences can be chained left-to-right so data flows naturally from source to destination without nested right-to-left parentheses (h (g (f x))) or disposable intermediate variables (st1, st2, st3).

Ridgun unifies infix left-operand invocation and explicit -> pipeline stages into the same AST. While all four forms below are valid and equivalent, Form 2b (starting naturally with a subject + operation on the left, chaining subsequent transformations with ->, and binding the final result with -> target) is the recommended idiomatic Ridgun style:

-- Recommended idiomatic style (2b):
xs map (x => x * 2) -> filter (x => x > 6) -> compact limit 14 -> results

-- Equivalent unified forms (all normalize to the exact same AST):
xs -> map (x => x * 2) -> filter (x => x > 6) -> compact limit 14 -> results  -- (2) explicit -> from subject
xs map (x => x * 2) filter (x => x > 6) compact limit 14 -> results           -- (1) contiguous infix chain
map (x => x * 2) xs -> filter (x => x > 6) -> compact limit 14 -> results     -- (3) prefix start + -> chain

Multi-stage pipelines can also be written across indented continuation lines starting with ->:

raw_nums map (x => x * 3)
  -> filter (x => x >= 12)
  -> fold 0 (acc x => acc + x)
  -> total_sum

2. Universal -> First-Argument Rule & Collection Combinators

  • Universal Rule for Functions & Operators: Any value coming from the left of -> (or left of an infix call x op a b) enters as the first argument of the right-hand function or operator:
  • x -> f a b $\equiv$ x f a b $\equiv$ f x a b
  • Works seamlessly for both fn :: declarations and op :: mixfix declarations without duplicating signatures.
  • If a downstream function needs the piped value at a non-first parameter position, an explicit _ placeholder places the piped value at _ (payload -> write file output_path _).
  • Collection Combinators (map, ||map, filter, ||filter, fold): Accept the collection either in traditional prefix position (map f xs, filter g xs, fold f init xs) or from the left of an infix/pipeline stage (xs map f, xs -> map f, xs filter g, xs -> filter g, xs fold init f, xs -> fold init f).
  • Agent Stages (-> send to, -> ask to): Piping into -> send to ag or -> ask to ag passes the left value as the message ([5, 10, 15] -> sum int list -> send to worker_ag).
  • Terminal Variable Binding (-> var): When a pipeline statement in a sequential block ends with -> var (where var is a local variable name rather than a declared function), it binds var (var = <pipeline_result>) for the remainder of the block.

3. Sequence Masks in Pipelines (-> (+ _ _ +) ->)

When a stage returns a multi-value sequence or tuple, a sequence mask stage (-> (+ _ _ +) ->) filters its elements (+ keeps a position, _ discards it) and unpacks the selected tuple directly into a multi-argument downstream function:

parse header packet "v0.9.9" -> (+ _ _ +) -> combine code msg -> status_line

4. _ =-Free Side-Effect Statements & Explicit Unit Discard (-> ())

In sequential blocks, side-effect statements (echo, write file, write bytes, shell exec, log info, print error lines, sys mkdir, etc.) are written directly without _ = boilerplate:

verify strict if enabled strict_mode input_path
s_errs -> format type errors -> print error lines
echo "[ridgunc --strict] {input_path}: {err_c} error(s) found."
sys exit 1

When the final expression of a => () function calls a helper that returns a non-() value (such as shell exec : String => Int), append -> () to evaluate the expression, discard its return value, and yield ():

fn :: exec built run Int => ()
      exec built run 0   =  shell exec "./target/app" -> ()
      exec built run _   =  sys exit 1

5. Point-Free Sequence Operator Chaining

Operators that return sequences can also be chained point-free:

op :: Int Int switch -> Int Int
      a   b   switch =  b   a

op :: Int Int invariant -> Int Int
      a   b   invariant =  a b switch switch

Note: -> and => are both accepted as return type separators in fn :: / op :: headers. In expressions, -> is the left-to-right pipeline and binding operator, while => introduces lexical closures (x => expr).


Type System

Type Signatures

Type signatures appear in declarations after fn :: or op :: (or without a prefix):

fn :: <name> <type1> <type2> ... => <return_type1> <return_type2> ...
op :: <words and types mixed> => <return_type1> <return_type2> ...
  • Parameters are positional; types appear where parameters are expected.
  • Unified Tuple and Sequence Type Syntax (FR-COMP-008):
  • In Ridgun, an argument list T1 T2 ... Tn and a tuple (T1, T2, ..., Tn) represent the same underlying compound type: an ordered, fixed-size sequence of typed values (seq).
  • Therefore, space-separated types and comma-separated tuple types are 100% interchangeable across return types, list brackets, and parenthesized tuples:
  • Return types after => / ->:
      fn :: parse header String => Int Int [String String] String
      -- 100% equivalent to:
      fn :: parse header String => (Int, Int, [(String, String)], String)
  • Lists of tuples ([T1 T2 ... Tn]):
      [String String]        -- equivalent to [(String, String)]
      [Int String Bool]      -- equivalent to [(Int, String, Bool)]
  • Parenthesized tuples ((T1 T2 ... Tn)):
      fn :: fst (A B) => A
            fst (a, _) =  a
  • Why this has zero syntactic ambiguity: Unlike Haskell/ML (where juxtaposition Maybe Int denotes type application), parameterized types in Ridgun use explicit parentheses (Tensor(Float, 3, 3)) and lists use brackets ([T]). Moreover, every type token starts with an uppercase letter (A..Z, Int, String), [, or (, while function and operator words are lowercase. Thus, T1 T2 never conflicts with type constructors or operator keywords.
  • The => (or ->) arrow separates input types from the return type.

Built-in Types

TypeDescription
IntInteger
RealReal number
BoolBoolean
CharSingle Unicode character (UTF-8)
StringText string (UTF-8)
ComplexComplex number
TensorMutable N-dimensional tensor (Real elements, CPU)
Tensor_GPUMutable N-dimensional tensor (Real elements, GPU)

[!NOTE] All text in Ridgun is UTF-8 encoded by default. Char represents a single Unicode codepoint, String is a sequence of Char.

Bool Literals

Bool has two primitive values:

True                                     -- boolean true
False                                    -- boolean false

True and False are constructors — they can be used in expressions, passed as arguments, and pattern-matched:

fn :: is valid Int => Bool
      is valid n =
        | n > 0     = True
        | otherwise = False

Logical Operators

Ridgun uses word-based lowercase logical operators instead of symbolic ones:

OperatorMeaningExample
andLogical conjunctiona and b
orLogical disjunctiona or b
xorExclusive ora xor b
notLogical negationnot a
result = True and False                  -- False
result = True or False                   -- True
result = True xor True                   -- False
result = not False                       -- True

These are infix keyword operators with standard boolean semantics.

Native Collection Types

Lists

Lists are a built-in immutable recursive type with dedicated syntax. No manual ADT definition is needed. Lists are suited for functional data processing, symbolic computation, and recursive algorithms. For mutable numeric computation, see Tensors below.

Type syntax: [type] — a list of elements of the given type.

fn :: sum list [Int] => Int
fn :: reverse [Int] => [Int]

Literal syntax: [elem1, elem2, ...] — a list literal. [] is the empty list.

fn :: my list => [Int]
my list = [1, 2, 3, 4, 5]

Cons-pattern matching: (x:xs) — destructures a list into its head x and tail xs. [] matches the empty list. Use _ as a wildcard that matches but does not bind.

fn :: sum list [Int] => Int
      sum list []     = 0
      sum list (x:xs) = x + sum list xs

fn :: list length [Int] => Int
      list length []     = 0
      list length (_:xs) = 1 + list length xs

Concatenation: :: joins two lists (or sequences).

fn :: append [Int] [Int] => [Int]
      append a b = a :: b

Tensors

Tensors are Ridgun's native mutable N-dimensional numeric type. They generalize vectors, matrices, and higher-dimensional arrays into a single type. Elements are Real by default.

Literal syntax: Tensor followed by nested brackets. The nesting depth determines the dimensionality.

v = Tensor [1, 2, 3]                    -- 1D (vector), shape: (3)
m = Tensor [[1, 2], [5, 3]]              -- 2D (matrix), shape: (2, 2)
t = Tensor [[[1, 2], [3, 4]],            -- 3D tensor, shape: (2, 2, 2)
            [[5, 6], [7, 8]]]

Indexing: Both chained and comma-separated indexing are supported.

val = m[0][1]        -- chained indexing: row 0, col 1
val = m[0, 1]        -- comma indexing: equivalent
row = m[0]           -- slice: entire first row

Mutation (in-place): Tensors are mutable — elements and slices can be modified in place (PyTorch/NumPy style).

m[0, 1] = 5.0                            -- set element
m[0] = Tensor [10, 20]                   -- set entire row

Tensor contraction (@): The @ operator performs Einstein summation. Subscript indices on each operand define the contraction: indices shared across all operands are summed; unique indices survive in the result.

-- A has shape (s, d, f), B has shape (n, d, f)
-- Shared indices: d, f → contracted (summed)
-- Result T has shape (s, n)
T = A_sdf @ B_ndf

-- Equivalent to: np.einsum('sdf,ndf->sn', A, B)

Contraction works across multiple tensors in a single expression:

-- A(a,b,c), B(a,b,c), C(a,b,c,d)
-- Shared: a, b, c → contracted. Result R has shape (d)
R = A_abc @ B_abc @ C_abcd

-- Equivalent to: np.einsum('abc,abc,abcd->d', A, B, C)

Hadamard operations (+, -, *, /): Arithmetic operators with subscript indices perform element-wise operations. Matching indices align dimensions; unmatched indices are broadcast.

-- Element-wise multiply: same shape, same indices
H = A_ij * B_ij                          -- shape (i, j)

-- Broadcasting: B has fewer dimensions
S = A_ij * B_j                           -- B broadcasts along i

-- Element-wise add
C = A_ij + B_ij

-- Element-wise divide
D = A_ij / B_j                           -- B broadcasts along i

This eliminates the need for unsqueeze, expand_dims, or manual reshaping — the indices are the broadcasting rules.

GPU variant: Tensor_GPU places the tensor on GPU memory for accelerated computation.

g = Tensor_GPU [[1, 2], [3, 4]]
result = g_ij @ g_jk                     -- GPU-accelerated matrix multiply

Sequences (seq)

Sequences are Ridgun's core compound type. A seq is an ordered, fixed-size collection of values — essentially a typed tuple. Function and operator parameter lists are implicitly sequences.

-- These parameter types form implicit sequences:
fn :: sum Int Int => Int          -- params: seq(Int, Int)
fn :: process Int Real => Bool   -- params: seq(Int, Real)

Seq binding ((name)) — wrapping a parameter name in parentheses binds all parameters as a single seq variable:

fn :: first of seq Int Int => Int
      first of seq (s)     =  s(+ _)

-- Equivalent without seq binding:
fn :: first of Int Int => Int
      first of s   _   => s

Mask selector (s(+ _)) — extracts elements by mask, where + selects a position and _ skips it:

s(+ _)                -- select 1st, skip 2nd
s(_ +)                -- skip 1st, select 2nd
s(+ _ _ _)            -- select 1st, skip 2nd-4th
s(_ _ _ +)            -- skip 1st-3rd, select 4th

Index selector (s(0 3)) — extracts elements by their zero-based positions:

fn :: extreme points Int Int Int Int => Int Int
      extreme points (s)             =  s(0 3)

Flat join (:) — concatenates two sequences into a single flat sequence:

seq1 = a, b         -- seq(a, b)
seq2 = c, d         -- seq(c, d)
seq1:seq2            -- seq(a, b, c, d)

Nesting (,) — composes sequences as distinct elements (seq of seqs):

seq1, seq2           -- seq(seq(a, b), seq(c, d))

Algebraic Data Types (ADTs)

Ridgun uses algebraic data types for user-defined types. There are no classes, methods, or impl blocks — functions and operators take types as parameters.

Product Types (records) & Record Update Syntax (with)

A type with named fields:

type Conf  = host: String, port: Int, tls: Bool
type Point = x: Real, y: Real

Fields are accessed by name as functions (host c, port c), and updated functionally using with and =:

fn :: get host Conf => String
      get host c    =  host c

-- Functional record update (creates a new Conf with updated fields;
-- mutates in place via Arc::try_unwrap when the record is uniquely owned):
fn :: enable tls Conf => Conf
      enable tls c    =  c with tls = True, port = 443

Sum Types (variants)

A type with multiple constructors, separated by |:

type Result = Ok Int | Err String
type Option = Some Int | None
type Color = Red | Green | Blue

Generic Sum ADTs & Generic Product Records (FR-COMP-013)

User-defined Sum ADTs and Product Records may declare one or more uppercase type parameters (A..Z or capitalized type variable names) between the type name and =:

type Result T E = Ok T | Err E
type Option A   = Some A | None
type Box A      = Box A
type Page T     = items: [T], total: Int
type Pair A B   = fst: A, snd: B
  • Parametric type applications (Result Int String, Page User, [Box Int]) can be used in fn :: / op :: signatures, nested record fields, and ADT constructor payloads.
  • Constructors (Ok 42, Ok "hello", Page [1, 2] 2), pattern matching, field accessors (items p, total p), and functional record updates (p with total = 10) instantiate fresh type variables at each call site.
  • type Option A = Some A | None is pre-registered as a built-in polymorphic ADT when not already declared by the user, so try receive from ag returns Some v or None without requiring boilerplate declarations (FR-COMP-013).

Anonymous Functions & Lexical Closures ((x => expr), (a b => expr) — FR-COMP-013)

Anonymous functions (lambdas) are written inside parentheses with space-separated parameters followed by => and the body expression ((x => expr) or (a b => expr), e.g. map (x => x + offset) xs, fold (acc x => acc + x) 0 xs) and may capture local variables or parameters from their enclosing lexical scope:

fn :: add offset Int [Int] => [Int]
      add offset k   xs    =  map (x => x + k) xs

Five canonical rules govern anonymous functions:

  1. Mandatory parentheses: Every lambda must be enclosed in parentheses (... => ...). Unparenthesized x => expr (such as f = x => x + 1) is a compile-time error (lambda expression must be enclosed in parentheses: '(x => expr)').
  2. Zero-parameter lambdas ((=> expr)): Ridgun forbids () before => in function signatures (fn :: main => ()). Consistently, a zero-parameter lambda is written (=> expr) (invoked as thunk ()), and its type in signatures is (=> Type). Writing (() => expr) or (() => Type) is a compile-time error.
  3. Multiple parameters vs. tuple destructuring ((a b => expr) vs. ((a b) => expr)): (a b => expr) is a 2-parameter lambda (such as fold (acc x => acc + x) 0 xs), whereas ((a b) => expr) is a 1-parameter lambda that destructures a tuple (map ((a b) => a + b) [(1, 2), (3, 4)]). Mixing them up produces a dedicated type error explaining the difference.
  4. Multi-line lambda bodies: When => is at the end of the line, the indented block underneath forms a multi-statement (or | guard) body closed by ):
   map (x =>
     doubled = x * 2
     doubled + offset
   ) [1, 2, 3]
  1. Uppercase types vs. lowercase values: (Conn => Conn) is a function type and (conn => conn) is a lambda value. Writing uppercase types (Conn => Conn) in an expression or lowercase parameters (conn => conn) in a fn :: signature is rejected with a clear compile-time error.

Captured bindings are lifted automatically into partial-application closures managed by RMM1/RMM2.

Recursive Types

Types can reference themselves:

type List = Empty | Cons Int List
type Tree = Leaf Int | Node Tree Tree

Shared Constructor Names Across Types (FR-COMP-005)

Multiple type declarations can reuse the same variant constructor names (such as Error, Var, Lit, None) with the same or different arities. The typechecker disambiguates constructor patterns via the expected parameter type of the column, and resolves constructor calls by argument arity/types and union ADT return types:

type ParseOut = Parsed Int | Error String
type DbOut    = Rows Int   | Error Int String
type MiniExpr = Var String | Lit Int
type MiniPat  = Var String | Lit Int | Wild

Open Type Extensions (expand type) & Automatic fn :: Clause Merging (FR-COMP-005)

Existing Sum ADTs and Product Records can be extended with expand type UpperName = ...:

  • Extending a Sum ADT: appends new variant constructors to the existing type.
  • Extending a Product Record: appends new named fields (field: Type) and auto-generates their field accessor functions.
  • Type-Name Variants Without Redundant Constructor Names: in type and expand type sum declarations, a variant whose name is a bare built-in type (String, Int, Real/Float, Bool, Char) with no extra type arguments after it represents values of that type directly (without writing Int Int or String String). Callers can pass bare values (describe "hej", describe 42) or explicit constructor calls (describe (String "hej"), describe (Int 42)), and equations can match both literals ("admin", 0) and typed patterns ((String s), (Int n)).
  • Automatic fn :: / op :: Clause Merging (without expand fn): re-declaring an existing fn :: or op :: with new pattern clauses automatically merges those clauses into the existing function definition, placing specific constructor/literal pattern clauses before any catch-all (_ / variable) fallback clause:
type Something = String | Int | MyStruct Float Int
expand type Something = Bool

fn :: describe something Something            => String
      describe something "admin"              =  "special_admin"
      describe something 0                    =  "zero_int"
      describe something (String   s)         =  "str:{s}"
      describe something (Int      n)         =  "int:{n}"
      describe something (Bool     b)         =  "bool:{b}"
      describe something (MyStruct ratio cnt) =  "struct:{ratio}:{cnt}"
type AppErr = IoError String | Timeout Int

fn :: format app err AppErr        => String
      format app err (IoError msg) =  "IO:{msg}"
      format app err (Timeout ms)  =  "TO:{ms}"
      format app err _             =  "UNKNOWN"

expand type AppErr = AuthError String | DbError Int String

fn :: format app err AppErr              => String
      format app err (AuthError user)    =  "AUTH:{user}"
      format app err (DbError   code db) =  "DB:{code}:{db}"

type AppConfig        = host: String, port: Int
expand type AppConfig = tls: Bool

Module Namespaces, Qualified Access & Strict Per-Module Import Visibility (FR-COMP-012)

Every .rg file or .rglib bundle defines a module namespace (derived from its file stem or an explicit module :: <name> header) and may import other modules via import <mod> or import <mod> as <alias>.

  1. Multi-File Modules (module :: <name>):
  • Multiple files declaring the same module :: <name> header (for example, orm.rg and orm_changeset.rg both starting with module :: orm) share a single logical module namespace orm and see each other's types, constructors, record fields, and functions directly.
  1. Qualified Access (mod.Type, mod.Constructor, mod.field, mod.fn):
  • Exported types, Sum ADT constructors, Product Record field accessors, record updates (with), and functions can be referenced with a <mod_or_alias>. prefix (http.Config, db.Status, http.Ok 200, http.port cfg, http.check status cfg).
  1. Strict 3-Tier Per-Module Unqualified Name Resolution:
  • Each module M (including the root application module) resolves unqualified types, constructors, record fields, functions, and multi-word calls strictly against M's own import graph in three priority tiers:
  • Tier 1 — Own Module (M): Declarations in M itself (across all files sharing module :: M) always take highest priority.
  • Tier 2 — Direct Imports of M: If M does not define the name, ridgunc searches modules directly imported by M (import D). If a single direct import D defines the name, it resolves unambiguously to D's declaration even if other modules in the compilation unit (transitive dependencies, sibling modules, or the root application) also define the same name. If two or more direct imports of M define the same name and M does not define it, unqualified references are rejected as ambiguous (__ambiguous__.<name>) and must be qualified (d1.<name> vs d2.<name>).
  • Tier 3 — Transitive Imports of M: Consulted only when neither Tier 1 nor Tier 2 defines the name. Consequently, a direct import in M (import json) cleanly shadows a transitive helper (codec.json get str imported internally by solstice) without forcing solstice or M to rename or qualify its calls.
  • Isolation from Unimported Sibling Modules and the Root Application: Declarations defined only in modules that M has not imported (neither directly nor transitively) — including sibling libraries imported by the root application or declarations in the root application itself when compiling an imported library M — are invisible to unqualified lookup inside M (__unimported__.<name>).

Unified = Grammar ((none), |, ||) & Local Variables (FR-COMP-003)

Every definition or binding in Ridgun — whether a function clause (fn ::), an operator clause (op ::), an immutable local variable (x =), a mutable local variable (mut x =), or a guard branch (| cond = / | otherwise =) — ends with = and accepts the exact same four block/expression forms on the right-hand side:

Prefix after =Execution SemanticsMeaning
Inline <expr>Single expression (with optional || expr -> var lines below)Evaluates <expr> directly (resolving any parallel || DAG bindings beneath it).
(none) (indented lines)Sequential (top-to-bottom)Evaluates statements in order (mut, assignments, side effects); the last expression is the result.
| <cond> = ...Conditional (guards & nested decision trees)Evaluates boolean conditions top-to-bottom until the first True branch (or | otherwise =).
|| <expr>Parallel (green-thread DAG / tuple)Evaluates || branches concurrently (|| expr -> var for named DAG nodes, || expr for parallel tuple results).

1. Guards (|) Always Follow = (Functions, Operators, Local Variables, and Nested Decision Trees)

Guard blocks (| cond = ... and | otherwise = ...) always appear behind an =, unifying function clauses, operator clauses, local variable definitions, and nested conditional trees:

Function & Operator Clauses with Guards

fn :: abs Int => Int
      abs n =
        | n < 0     = 0 - n
        | otherwise = n

op :: clamp Int between Int and Int => Int
      clamp x   between lo  and hi  =
        | x < lo    = lo
        | x > hi    = hi
        | otherwise = x

Local Variable Assignment with Guards

Because | is valid after any =, local variables can be bound conditionally without needing an if/else keyword or a helper function:

discount =
  | is_vip      = 20
  | total > 100 = 10
  | otherwise   = 0

Multi-Line Sequential Blocks, Deep Decision Trees, and Nested Closers (|. / ||.)

Each guard branch ends with =, so its body can itself be a sequential block ((none)), a parallel block (||), or another nested | guard block — enabling arbitrary decision trees (3–4+ levels deep) inside | cond = and | otherwise =.

  • Closing nested levels (|. and ||.):
  • In simple 1-level blocks or at the end of a function/tree, no closing token is needed.
  • When a nested | (or ||) sub-block finishes and execution returns to an outer level where more branches or statements follow below it, |. (for |) or ||. (for ||) explicitly closes that nested level.
  • Both inline placement at the end of the last line (| otherwise = val |., the canonical compact style) and placement on the next line (|.) are valid:
fn :: evaluate payment Account Customer Payment => String
      evaluate payment account customer payment =
        | is locked account   = "REJECTED_ACCOUNT_LOCKED"
        | amount payment <= 0 = "ERROR_INVALID_AMOUNT"
        | otherwise =
            | tier customer == "ENTERPRISE" =
                | amount payment > 50000 =
                    | has dual signature payment = "APPROVED_ENTERPRISE_DUAL_SIG"
                    | tenure customer > 10       = "RISK_COMMITTEE_REVIEW"
                    | otherwise                  = "REJECTED_ENTERPRISE_LIMIT" |.
                | country payment != "US" =
                    | is whitelisted (country payment) = "APPROVED_ENTERPRISE_INTL"
                    | otherwise                        = "HELD_COMPLIANCE_INTL" |.
                | otherwise = "APPROVED_ENTERPRISE_STANDARD" |.
            | tier customer == "VIP" =
                | amount payment > 10000 =
                    | has 2fa payment = "APPROVED_VIP_2FA"
                    | otherwise       = "REQUEST_VIP_SMS" |.
                | otherwise = "APPROVED_VIP_DIRECT" |.
            | otherwise =
                | amount payment > 1000 =
                    | has 2fa payment and risk payment < 30 = "APPROVED_RETAIL_2FA"
                    | otherwise                             = "REJECTED_RETAIL_RISK" |.
                | otherwise = "APPROVED_RETAIL_LOW_AMOUNT"

fn :: charge order Payment => Int
      charge order payment =
        || fetch base fee payment     -> base_fee
        || compute risk score payment -> risk_score ||.
        surcharge =
          | risk_score > 80 = 50
          | otherwise       = 0 |.
        base_fee + surcharge

2. Immutable by Default (x = ...) vs Explicit Mutable Variables (mut x = ...)

Inside sequential blocks, local variables follow Option 1:

  • Immutable binding (x = <rhs>):
  • Declares a new immutable local variable x.
  • Attempting to reassign x = ... later in the same scope without a prior mut x = ... declaration is a compile-time error.
  • Mutable declaration (mut x = <rhs>) & Reassignment (x = <new_rhs>):
  • mut x = <rhs> declares x as mutable in the current scope and initializes it.
  • Subsequent reassignments to x in the same scope are written simply as x = <new_rhs> (without repeating mut).
  • Under RMM2, reassigning x = <new_rhs> increments the new structure (+1) before decrementing the old structure (-1, cascading to its children if its counter reaches 0).
fn :: accumulate Int => Int
      accumulate n =
        factor = 2
        mut acc = n * factor
        acc = acc + 10
        acc =
          | acc > 100 = 100
          | otherwise = acc
        acc

3. Pattern Matching on Literals & ADT Constructors

fn :: factorial Int => Int
      factorial 0   =  1
      factorial n   =  n * factorial (n - 1)

fn :: unwrap Result    => Int
      unwrap (Ok n)    =  n
      unwrap (Err msg) =  0

fn :: length List => Int
      length Empty         =  0
      length (Cons _ rest) =  1 + length rest

fn :: sum tree Tree              => Int
      sum tree (Leaf n)          =  n
      sum tree (Node left right) =  sum tree left + sum tree right

Parallelism Primitives (||)

Ridgun unifies all parallel execution under || (where is removed):

Named Parallel DAG Bindings (|| expr -> var)

fn :: compute total Int Int => Int
      compute total a   b   =  x + y + z
        || heavy calc a     -> x
        || heavy calc b     -> y
        || combine x y      -> z
  • All || expr -> var lines beneath an expression form a parallel Directed Acyclic Graph (DAG).
  • The compiler analyzes variable dependencies across || lines (x and y have no dependencies and run in parallel in Wave 1; z depends on x and y and runs in Wave 2 before x + y + z is evaluated).
  • Parallel tensor slice writes (|| expr -> out[i, j]) are also supported.

Direct Parallel Pipe Blocks (|| expr)

op :: run Conf and Conf in parallel => Res, Res
      run a    and b    in parallel =
        || run process a
        || run process b

Each || line runs as a separate parallel green thread, and results are returned as a tuple in declaration order (or joined when placed inside a sequential block).

Typed Channels

For streaming data and producer-consumer topologies across green threads, Ridgun provides typed MPMC channels (Channel):

Built-in Function / OperatorSignaturePurpose
new channel() => ChannelCreate an unbounded typed channel
new bounded channelInt => ChannelCreate a bounded channel with backpressure capacity
send throughT Channel => ()Send a value into a channel (blocks if bounded and full)
receive fromChannel => TReceive the next value from a channel
try receive fromChannel => OptionNon-blocking poll returning Some v or None

Native Agent Model (agent, agent sink, agent :: — FR-COMP-009)

Ridgun provides typed actor-style agents (Agent In Out and fire-and-forget SinkAgent In State) that process messages sequentially in isolated state while running concurrently on background worker threads:

Construct / Built-inSignature / SyntaxPurpose
agent <fn>(In => Out) => Agent In OutSpawn a stateless FIFO agent
agent <fn> <init_state>(S In => S Out) S => Agent In OutSpawn a stateful agent whose step returns (new_state, reply)
agent sink <fn> <init_state> [bounded N drop|block] [drain D](S In => S) S => SinkAgent In SSpawn a fire-and-forget sink agent with no output queue
agent :: <name> = agent sink ...Top-level declarationDeclare a named global sink agent accessible across threads
send <msg> to <ag>In (Agent In Out) => ()Enqueue a message asynchronously
ask <msg> to <ag>In (Agent In Out) => OutSend a message and wait for this caller's reply
receive from <ag>(Agent In Out) => OutWait for and return the next reply for the calling thread
try receive from <ag>(Agent In Out) => Option OutNon-blocking poll returning Some v or None
stop <ag> / agent alive <ag>Agent => () / Agent => BoolGracefully stop an agent or check if it is still running
agent flush <ag> / agent state <ag> / agent dropped <ag>Inspection & testingDrain pending messages, read current state, or read dropped count

I/O Primitives

Ridgun provides built-in functions for terminal output.

echo

Prints an arbitrary number of strings to the terminal, separated by spaces, followed by a newline.

fn :: echo String... => ()

The ... suffix on the last parameter type marks it as variadic — accepting one or more arguments of that type.

echo "hello"                            -- prints: hello
echo "hello" "world" "!"               -- prints: hello world !
echo "result: {result}"                 -- prints: result: 42

out

An operator that writes a string directly to the terminal and returns it (allowing chaining).

op :: String out => String
"hello" out                              -- prints: hello
"step 1 done" out                        -- prints: step 1 done

-- Chaining: out returns the string, so it can be passed onward
fn :: log and process String => String
      log and process s = process (s out) and report

Summary

FunctionTypePurpose
echoString... => ()Print strings with spaces and newline
outString => StringPrint string, return it for chaining

Standard Library

Libraries are imported with import:

import test

Unit Testing (lib/test.rg)

A simple test library using echo for terminal output.

Assertions:

OperatorPurpose
assert Int equals IntInteger equality
assert Real equals RealReal equality
assert String equals StringString equality
assert Bool is trueBoolean is true
assert Bool is falseBoolean is false

Test structure:

import test

fn :: main => ()
      main =
        describe "Arithmetic" does

          test "sum adds correctly" does
            assert (sum 3 4) equals 7

          test "factorial base case" does
            assert (factorial 0) equals 1

Terminal output:

═══ Arithmetic ═══
● sum adds correctly
  ✓ PASS
● factorial base case
  ✓ PASS

Summary of Syntactic Elements

ElementSyntaxPurpose
Comment-- textSingle-line comment
Function declarationfn :: name types => returnDeclare a function's type signature
Function definitionname params = bodyDefine a function's implementation
Operator declarationop :: words types words => returnDeclare a natural language operator
Operator definitionwords params words = bodyDefine an operator's implementation
Multiple returns=> type, typeDeclare multiple return values
Variadic parametersType...Accept one or more arguments of a type
Tensor literalTensor [[1, 2], [3, 4]]Create an N-dimensional tensor
Tensor indexingt[0, 1] or t[0][1]Access tensor elements
Tensor mutationt[0, 1] = 5.0Mutate tensor elements in place
Einstein contractionA_ij @ B_jkTensor contraction over shared indices
Hadamard operationA_ij * B_ijElement-wise arithmetic with index alignment
echoecho "a" "b" "c"Print strings to terminal
out"text" outOutput string to terminal
Immutable local bindingx = exprBind an immutable local variable (= supports (none), |, ||)
Mutable local bindingmut x = exprDeclare a mutable local variable (reassigned via x = new_expr)
Guard block| cond = exprConditional guard branch after = (supports nested |)
Parallel DAG block|| expr -> varParallel sub-computations with named DAG results
Parallel pipe block|| exprParallel execution returning a tuple
Operator overloadingop :: type + type => typeOverload existing operators for new types
Bool literalsTrue, FalseBoolean primitive values
Logical operatorsand, or, xor, notWord-based lowercase logical operators
Entry pointfn :: main => ()Required program entry point

Canonical Source Code Formatting (stable format & Editor Tooling)

Aesthetic Only — Not a Compiler Requirement (FR-TOOL-001): Ridgun's lexer and parser are insensitive to horizontal column alignment. Unaligned and column-aligned .rg source files are 100% equivalent to ridgunc. The rules below define the canonical aesthetic layout applied by stable format and GNU Emacs ridgun-mode (C-c C-f).

  1. Alignment Group per Signature (fn :: / op ::):
  • Each fn :: or op :: declaration starts an alignment group spanning its signature, its pattern-matching clauses, and its || expr -> var bindings, ending at the next top-level declaration (fn, op, type, import, global, context, shared, hardware, @).
  1. Column Alignment of LHS Tokens ($C_0 = 6$):
  • fn :: and op :: are 6 characters wide, so token $0$ of the signature LHS starts at column $C_0 = 6$.
  • Each token slot $i \in [0, N-1]$ before =>/-> (treating "...", '...', [...], and (...) as single tokens) has width $W_i = \max(\text{len}(\text{sig_tok}i), \max{\text{clause} : \text{len}(\text{clause_tok}_i) \le 18} \text{len}(\text{clause_tok}_i))$ and starts at column $C_i = C_{i-1} + W_{i-1} + 1$. Clause patterns wider than 18 characters are treated as outliers: they do not stretch $W_i$ for the rest of the group and simply overflow on their own line with 1 space.
  • Matching definition clauses are indented to column $6$ ($C_0$) so the function name or operator words and every argument pattern align vertically beneath their corresponding signature slots.
  1. Vertical Alignment of =>, =, and -> ($C_{\text{arrow}}$):
  • Let $C_{\text{arrow}} = \max\big(C_{N-1} + W_{N-1} + 1,; \max_{\text{pipe_bind}}(8 + 3 + \text{len}(\text{pipe_lhs}) + 1)\big)$ and $C_{\text{ret}} = C_{\text{arrow}} + \text{len}(\text{arrow}) + 1$.
  • In the signature, => is placed at $C_{\text{arrow}}$ and the return type at $C_{\text{ret}}$.
  • In single-line clauses (<lhs> = <rhs>), multi-line block clauses (<lhs> =), and guard-headed clauses (<lhs> = followed by | cond = ...), = is placed at $C_{\text{arrow}}$ (aligned directly beneath =>); in single-line clauses, <rhs> starts at $C_{\text{ret}}$.
  • In parallel DAG bindings (|| <lhs> -> <rhs>), || starts at column $8$ (6 + 2), -> is placed at $C_{\text{arrow}}$, and <rhs> starts at $C_{\text{ret}}$.
  • Compact Fallback (> 4 Parameters or > 80 Columns): If a declaration has more than 4 parameter slots or its aligned signature line would exceed 80 characters, horizontal column padding is disabled for that group and tokens are separated by a single space (" ") while preserving column-6 clause indentation.
  1. Structural Indentation (2-Space Offset):
  • Top-level declarations (fn, op, type, import, global, context, shared, hardware, @): column 0.
  • Function/operator clauses under a signature: column 6.
  • Guards (| cond = expr) and parallel pipes (|| expr / || expr -> var): prev_indent + 2 (column 8 inside a clause, or indent + 2 inside a local binding or nested guard).
  • Sequential block body lines following a line ending in =: prev_indent + 2.

Sequential & Parallel Higher-Order Collection Operations (FR-COMP-001, FR-COMP-014)

Ridgun provides standard sequential collection combinators (map, filter, fold) alongside explicit multicore parallel combinators prefixed with || (||map, ||filter). All five combinators support both idiomatic left-to-right pipeline/infix syntax (xs map f -> filter g -> fold 0 h -> res) and traditional prefix syntax (map f xs):

Built-inPrefix & Pipeline FormsExecution Model
mapxs map f / xs -> map f / map f xsSequential: evaluates f x strictly left-to-right on the calling thread.
`map`
filterxs filter p / xs -> filter p / filter p xsSequential: evaluates predicate p x strictly left-to-right on the calling thread.
`filter`
foldxs fold init f / xs -> fold init f / fold f init xsSequential left fold: accumulates acc = f acc x from left to right starting from init.

Verbatim File I/O, Byte Streams & POSIX Built-ins (FR-VM-001, FR-VM-002)

  1. Verbatim File & Byte Stream I/O (FR-VM-001):
  • read file : String => String reads file contents verbatim (preserving \r\n and exact EOF newline presence/absence, and never running the compiler's .rg import preprocessor on data files).
  • load rg source : String => String is used exclusively by the compiler toolchain when loading .rg source files with import splicing.
  • read bytes : String => [Int] and write bytes : String [Int] => () read and write raw binary byte streams (0..255, including 0x00 NUL and 0x80..0xFF).
  1. POSIX Filesystem, Process & Security Built-ins (FR-VM-002):
  • Filesystem: list dir, file lstat, sha256 file, sys mkdir, sys chmod.
  • Process & Environment: sys exec, sys chdir, sys getcwd, sys getenv, sys setenv, sys exit.
  • Security & Privilege Separation: sys getuid, sys getgid, sys username, sys user groups, sys lookup uid gid, sys setresgid, sys setgroups, sys setresuid, sys execve, sys read pass.
  1. Cross-Platform PAL I/O, Polling, Process Primitives, Target ADTs & Declarative with timeout Quotas (FR-COMP-007):
  • Pre-Declared Target Platform ADTs:
  • type SysOs = Linux | MacOS | Windows (sys os => SysOs, open to extension via expand type SysOs = ...)
  • type SysArch = X86_64 | Arm64 (sys arch => SysArch, open to extension via expand type SysArch = ...)
  • Duration Unit Literals: Integer literals accept _ms (* 1), _s (* 1000), and _min (* 60000) suffixes (e.g., 500_ms, 5_s, 2_min), complementing byte-unit suffixes (_KB, _MB, _GB).
  • Layer 1 — Cross-Platform PAL Primitives for .rglib System Libraries (posix.rglib / io.rglib):
  • sys os => SysOs and sys arch => SysArch (compile-time target OS and architecture Sum ADTs)
  • sys open mode String String => Int (path, portable mode "r", "w", "a", "rw", "w+", "a+" -> descriptor/handle >= 0 or -errno, translated per OS target)
  • sys open String Int Int => Int (path, Ridgun PAL normalized flags (0 = read, 1 = write, 2 = rdwr, 64 = create, 512 = trunc, 1024 = append, 2048 = nonblock), mode -> fd >= 0 or -errno)
  • sys close Int => Int (fd -> 0 or -errno)
  • sys read fd Int Int => String (fd, max_bytes -> string read from fd)
  • sys write fd Int String => Int (fd, data -> bytes written or -errno)
  • sys poll Int Int Int => Int (fd, Ridgun PAL normalized events (1 = POLLIN/read, 4 = POLLOUT/write), timeout_ms -> > 0 revents when ready, 0 on timeout, < 0 on -errno; uses sys remaining ms when timeout_ms < 0 inside with timeout)
  • sys terminate Int Bool => Int (pid, force -> portable graceful/forced process termination) and sys kill Int Int => Int (pid, normalized signal -> 0 or -errno)
  • sys call Int Int... => Int (nr plus 0..6 arguments a1..a6 -> low-level target-OS ABI syscall escape hatch)
  • sys time ms => Int and sys sleep ms Int => () (monotonic millisecond clock and sleep)
  • sys deadline ms => Int and sys remaining ms => Int (active lexical timeout deadline and remaining milliseconds, or -1 when unbounded)
  • Layer 2 — Declarative with timeout <duration> do Execution Quota:
  • Just as with region 64_KB do sets a lexical byte quota, with timeout 500_ms do (or combined with region 64_KB, timeout 500_ms do) sets a lexical execution deadline (sys time ms + duration_ms) on the current thread context and restores the previous deadline upon block exit:
       with timeout 500_ms do
         rem = sys remaining ms
         rc  = sys poll fd 1 rem
         rc
  1. Reusable Library Compilation (.rglib & ELF64 .so) and Extensible stable (FR-COMP-006, FR-TOOL-003):
  • ridgunc build --lib <file.rg> -o <out.rglib> and ridgunc build --so <file.rg> -o <lib.so> (or stable built lib / stable new --lib <name>) compile modules without requiring fn :: main => () and preserve all exported functions, operators, and types.
  • stable discovers custom fn :: task <words...> => String commands and fn :: plugins => [String] extensions (plugins/*.rg, .stable/plugins/*.rg), listed via stable tasks and stable plugins.

Ridgun Memory Management (RMM): RMM1 (CMM) & RMM2 (FR-COMP-002)

Ridgun manages heap memory (String, [a], (a, b), ADTs, Tensor) deterministically without a tracing Garbage Collector (GC) through Ridgun Memory Management (RMM):

  1. Policy RMM1 — Compiled Memory Management (CMM Linear + Conditional):
  • Applied when control flow and called functions are statically known at compile time over non-nested bindings:
  • Linear CMM (rg_cmm_move / rg_cmm_drop): The compiler determines the exact last-use statement of every local variable at compile time, moving returned variables (rg_cmm_move(&mut lv_x)) and emitting unconditional deallocation (rg_cmm_drop(&mut lv_x)) immediately after a variable's last use when no may-aliases remain live.
  • Conditional CMM (rg_cmm_drop_unless / ptr(a) != ptr(c)): When a static branching call (c = choose list cond a b) returns one of several candidate inputs and one or more candidates remain live alongside c, the compiler builds the Ancestor May-Alias Table (RCmmAlias) and emits a 1-cycle pointer-identity guard at each variable's static last-use statement:
       a = make a ()               -- Allocates M1
       b = make b ()               -- Allocates M2
       c = choose list cond a b    -- b dies here while c is live -> if ptr(b) != ptr(c) { free(b) }
       print list a                -- a dies here while c is live -> if ptr(a) != ptr(c) { free(a) }
       consume list c              -- c dies here (no live peers) -> free(c) (unconditional)
  1. Policy RMM2 — Assignment & Cascaded Structure Counting (Active Policy):
  • Unifies memory management across functions, branching returns (choose list), nested containers (xs = [a, b, ..., z], xs[3] = a), mutable local variables (mut x = ...), and multi-thread execution:
  • Primitive Scalars (Int, Real, Bool, ()): Never counted; copied by value on the stack/registers.
  • Incoming Function Arguments (pats): Never touched (+0 on entry, -0 on exit).
  • Internal Local Variables (x = ... / mut x = ...): Receive +1 on assignment (x = expr, xs[3] = a). On mutable reassignment (x = new_expr), new_expr receives +1 before old x receives -1.
  • End of Function / Scope Exit:
  • Non-returned internal local variables receive -1 first: if rc == 0, the parent block is freed and cascades -1 one by one to each direct child structure (recursively freeing any child whose rc reaches 0).
  • A returned internal local variable (return a) receives -1 (or is moved via rg_cmm_move), and even if its rc is 0 in transit, it is spared so the caller's assignment (c = make pair ()) adds +1 (0 + 1 = 1). When an incoming argument is returned (choose list cond a b), it was untouched inside the function (rc = 1), so the caller's c = choose list cond a b adds +1 (1 + 1 = 2, shared between a and c).
  1. Policy RMM3 (Reserved):
  • Reserved for a subsequent aggressive static optimization policy.
  • See docs/etc/cmm_architecture.md (in the Ridgun repository) for the complete RMM1 and RMM2 specification.