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
| Syntax | Interpolation | Use case |
|---|---|---|
"..." | Yes — {expr} (escape braces with \{/\} or {{/}}) | Dynamic strings with embedded expressions |
'...' | No — all literal | Static 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:
importstatementstypedefinitionsfnandopdeclarations and definitions- A
mainfunction 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>
| Command | Status | Purpose |
|---|---|---|
run <file.rg> | ✅ Active | Compile and execute a Ridgun source file |
check <file.rg> | ✅ Active | Type-check and analyze static strategies without running |
compile <file.rg> [-o out.rgc] | ✅ Active | Emit .rgc bytecode artifact |
build <file.rg> [-o binary] [--target arch] | ✅ Active | Build standalone native ELF64 executable |
build --lib <file.rg> [-o out.rglib] | ✅ Active | Build reusable Ridgun library (.rglib & .so) |
build --so <file.rg> [-o out.so] | ✅ Active | Build native ELF64 shared library (ET_DYN .so) |
version / --version | ✅ Active | Show compiler version and target architecture |
help / --help | ✅ Active | Show 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 likeop ::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.rgfilenames with spaces (src/test/ridgun/test core language.rg) are supported natively byridguncandstable.
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
typeandwith:typeonly introduces a type declaration at the top level when followed byUpperName =. Everywhere else (fn ::andop ::signatures, operator words, parameter patterns, local variables, and record field names),typeandwithare 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, Resdeclares two return values || expr -> varblock — Binds sub-computations to result names (whereis 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.
| Precedence | Category | Examples |
|---|---|---|
| Highest | Symbolic operators | *, /, +, -, % |
| Function application | f x | |
| Comparison | ==, !=, <, > | |
| Logical | and, or, xor, not | |
| Lowest | Natural language operators | add 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).
1. The Recommended Idiomatic Ridgun Pipeline Style (2b)
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 callx 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 andop ::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 agor-> ask to agpasses 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(wherevaris a local variable name rather than a declared function), it bindsvar(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 infn ::/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 ... Tnand 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 Intdenotes 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 T2never conflicts with type constructors or operator keywords. - The
=>(or->) arrow separates input types from the return type.
Built-in Types
| Type | Description |
|---|---|
Int | 64-bit signed integer (-9223372036854775808 .. 9223372036854775807) |
Real | Real number |
Bool | Boolean |
Char | Single Unicode character (UTF-8) |
String | Text string (UTF-8) |
Complex | Complex number |
Tensor | Mutable N-dimensional tensor (Real elements, CPU) |
Tensor_GPU | Mutable N-dimensional tensor (Real elements, GPU) |
[!NOTE] All text in Ridgun is UTF-8 encoded by default.
Charrepresents a single Unicode codepoint,Stringis a sequence ofChar.
Int Literals (FR-COMP-016)
- A decimal literal (
42,1_000_000,64_KB) must lie in0 .. 9223372036854775807after its unit suffix is applied. A larger literal is a compile error:integer literal '<text>' is out of range for Int (-9223372036854775808 .. 9223372036854775807). 9223372036854775808is valid only after a unary minus:-9223372036854775808is theIntminimum.0 - 9223372036854775808is the same compile error.- A hexadecimal literal (
0xFF00,0x7FFF_FFFF_FFFF_FFFF) is the 64-bit two's-complement bit pattern of its digits:0x8000000000000000is theIntminimum and0xFFFFFFFFFFFFFFFFis-1. More than 16 significant hex digits is a compile error. str_to_intreturns the exact 64-bit value. It ignores leading and trailing ASCII whitespace and every_, accepts one+or-sign (after the prefix for0x), and returns0for text that is not a number or does not fit inInt.
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:
| Operator | Meaning | Example |
|---|---|---|
and | Logical conjunction | a and b |
or | Logical disjunction | a or b |
xor | Exclusive or | a xor b |
not | Logical negation | not 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 infn ::/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 | Noneis pre-registered as a built-in polymorphic ADT when not already declared by the user, sotry receive from agreturnsSome vorNonewithout 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:
- Mandatory parentheses: Every lambda must be enclosed in parentheses
(... => ...). Unparenthesizedx => expr(such asf = x => x + 1) is a compile-time error (lambda expression must be enclosed in parentheses: '(x => expr)'). - Zero-parameter lambdas (
(=> expr)): Ridgun forbids()before=>in function signatures (fn :: main => ()). Consistently, a zero-parameter lambda is written(=> expr)(invoked asthunk ()), and its type in signatures is(=> Type). Writing(() => expr)or(() => Type)is a compile-time error. - Multiple parameters vs. tuple destructuring (
(a b => expr)vs.((a b) => expr)):(a b => expr)is a 2-parameter lambda (such asfold (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. - 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]
- 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 afn ::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
typeandexpand typesum 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 writingInt IntorString 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 (withoutexpand fn): re-declaring an existingfn ::orop ::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>.
- Multi-File Modules (
module :: <name>):
- Multiple files declaring the same
module :: <name>header (for example,orm.rgandorm_changeset.rgboth starting withmodule :: orm) share a single logical module namespaceormand see each other's types, constructors, record fields, and functions directly.
- 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).
- 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 againstM's own import graph in three priority tiers: - Tier 1 — Own Module (
M): Declarations inMitself (across all files sharingmodule :: M) always take highest priority. - Tier 2 — Direct Imports of
M: IfMdoes not define the name,ridguncsearches modules directly imported byM(import D). If a single direct importDdefines the name, it resolves unambiguously toD'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 ofMdefine the same name andMdoes not define it, unqualified references are rejected as ambiguous (__ambiguous__.<name>) and must be qualified (d1.<name>vsd2.<name>). - Tier 3 — Transitive Imports of
M: Consulted only when neither Tier 1 nor Tier 2 defines the name. Consequently, a direct import inM(import json) cleanly shadows a transitive helper (codec.json get strimported internally bysolstice) without forcingsolsticeorMto rename or qualify its calls. - Isolation from Unimported Sibling Modules and the Root Application: Declarations defined only in modules that
Mhas 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 libraryM— are invisible to unqualified lookup insideM(__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 Semantics | Meaning |
|---|---|---|
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 priormut x = ...declaration is a compile-time error. - Mutable declaration (
mut x = <rhs>) & Reassignment (x = <new_rhs>): mut x = <rhs>declaresxas mutable in the current scope and initializes it.- Subsequent reassignments to
xin the same scope are written simply asx = <new_rhs>(without repeatingmut). - Under
RMM2, reassigningx = <new_rhs>increments the new structure (+1) before decrementing the old structure (-1, cascading to its children if its counter reaches0).
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
Constructors, with or without arguments, also match inside tuple and cons patterns (FR-COMP-017). Each tuple element is a constructor applied to the sub-patterns up to the next ,, : or ):
type Step = Ask Int | End Bool | Stop
fn :: picked (Step, Int) => Int
picked (End _, _) = 1
picked (Ask n, _) = n
picked (Stop, k) = k
When a call matches no clause (every pattern fails, or no guard of the last clause holds and there is no otherwise), the program writes runtime error: no clause of function '<name>' matches its arguments to standard error and exits with status 1. It never returns a zero value.
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 -> varlines beneath an expression form a parallel Directed Acyclic Graph (DAG). - The compiler analyzes variable dependencies across
||lines (xandyhave no dependencies and run in parallel in Wave 1;zdepends onxandyand runs in Wave 2 beforex + y + zis 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 / Operator | Signature | Purpose |
|---|---|---|
new channel | () => Channel | Create an unbounded typed channel |
new bounded channel | Int => Channel | Create a bounded channel with backpressure capacity |
send through | T Channel => () | Send a value into a channel (blocks if bounded and full) |
receive from | Channel => T | Receive the next value from a channel |
try receive from | Channel => Option | Non-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-in | Signature / Syntax | Purpose |
|---|---|---|
agent <fn> | (In => Out) => Agent In Out | Spawn a stateless FIFO agent |
agent <fn> <init_state> | (S In => S Out) S => Agent In Out | Spawn 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 S | Spawn a fire-and-forget sink agent with no output queue |
agent :: <name> = agent sink ... | Top-level declaration | Declare 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) => Out | Send a message and wait for this caller's reply |
receive from <ag> | (Agent In Out) => Out | Wait for and return the next reply for the calling thread |
try receive from <ag> | (Agent In Out) => Option Out | Non-blocking poll returning Some v or None |
stop <ag> / agent alive <ag> | Agent => () / Agent => Bool | Gracefully stop an agent or check if it is still running |
agent flush <ag> / agent state <ag> / agent dropped <ag> | Inspection & testing | Drain 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
| Function | Type | Purpose |
|---|---|---|
echo | String... => () | Print strings with spaces and newline |
out | String => String | Print 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:
| Operator | Purpose |
|---|---|
assert Int equals Int | Integer equality |
assert Real equals Real | Real equality |
assert String equals String | String equality |
assert Bool is true | Boolean is true |
assert Bool is false | Boolean 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
| Element | Syntax | Purpose |
|---|---|---|
| Comment | -- text | Single-line comment |
| Function declaration | fn :: name types => return | Declare a function's type signature |
| Function definition | name params = body | Define a function's implementation |
| Operator declaration | op :: words types words => return | Declare a natural language operator |
| Operator definition | words params words = body | Define an operator's implementation |
| Multiple returns | => type, type | Declare multiple return values |
| Variadic parameters | Type... | Accept one or more arguments of a type |
| Tensor literal | Tensor [[1, 2], [3, 4]] | Create an N-dimensional tensor |
| Tensor indexing | t[0, 1] or t[0][1] | Access tensor elements |
| Tensor mutation | t[0, 1] = 5.0 | Mutate tensor elements in place |
| Einstein contraction | A_ij @ B_jk | Tensor contraction over shared indices |
| Hadamard operation | A_ij * B_ij | Element-wise arithmetic with index alignment |
echo | echo "a" "b" "c" | Print strings to terminal |
out | "text" out | Output string to terminal |
| Immutable local binding | x = expr | Bind an immutable local variable (= supports (none), |, ||) |
| Mutable local binding | mut x = expr | Declare a mutable local variable (reassigned via x = new_expr) |
| Guard block | | cond = expr | Conditional guard branch after = (supports nested |) |
| Parallel DAG block | || expr -> var | Parallel sub-computations with named DAG results |
| Parallel pipe block | || expr | Parallel execution returning a tuple |
| Operator overloading | op :: type + type => type | Overload existing operators for new types |
| Bool literals | True, False | Boolean primitive values |
| Logical operators | and, or, xor, not | Word-based lowercase logical operators |
| Entry point | fn :: 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.rgsource files are 100% equivalent toridgunc. The rules below define the canonical aesthetic layout applied bystable formatand GNU Emacsridgun-mode(C-c C-f).
- Alignment Group per Signature (
fn ::/op ::):
- Each
fn ::orop ::declaration starts an alignment group spanning its signature, its pattern-matching clauses, and its|| expr -> varbindings, ending at the next top-level declaration (fn,op,type,import,global,context,shared,hardware,@).
- Column Alignment of LHS Tokens ($C_0 = 6$):
fn ::andop ::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 than18characters 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.
- 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 (
> 4Parameters or> 80Columns): If a declaration has more than4parameter slots or its aligned signature line would exceed80characters, horizontal column padding is disabled for that group and tokens are separated by a single space (" ") while preserving column-6 clause indentation.
- Structural Indentation (2-Space Offset):
- Top-level declarations (
fn,op,type,import,global,context,shared,hardware,@): column0. - Function/operator clauses under a signature: column
6. - Guards (
| cond = expr) and parallel pipes (|| expr/|| expr -> var):prev_indent + 2(column8inside a clause, orindent + 2inside 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-in | Prefix & Pipeline Forms | Execution Model |
|---|---|---|
map | xs map f / xs -> map f / map f xs | Sequential: evaluates f x strictly left-to-right on the calling thread. |
| ` | map` | |
filter | xs filter p / xs -> filter p / filter p xs | Sequential: evaluates predicate p x strictly left-to-right on the calling thread. |
| ` | filter` | |
fold | xs fold init f / xs -> fold init f / fold f init xs | Sequential 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)
- Verbatim File & Byte Stream I/O (
FR-VM-001):
read file : String => Stringreads file contents verbatim (preserving\r\nand exact EOF newline presence/absence, and never running the compiler's.rgimportpreprocessor on data files).load rg source : String => Stringis used exclusively by the compiler toolchain when loading.rgsource files withimportsplicing.read bytes : String => [Int]andwrite bytes : String [Int] => ()read and write raw binary byte streams (0..255, including0x00NUL and0x80..0xFF).
- 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.
- Cross-Platform PAL I/O, Polling, Process Primitives, Target ADTs & Declarative
with timeoutQuotas (FR-COMP-007):
- Pre-Declared Target Platform ADTs:
type SysOs = Linux | MacOS | Windows(sys os => SysOs, open to extension viaexpand type SysOs = ...)type SysArch = X86_64 | Arm64(sys arch => SysArch, open to extension viaexpand 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
.rglibSystem Libraries (posix.rglib/io.rglib): sys os => SysOsandsys 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>= 0or-errno, translated per OS target)sys open String Int Int => Int(path, Ridgun PAL normalizedflags(0= read,1= write,2= rdwr,64= create,512= trunc,1024= append,2048= nonblock),mode->fd >= 0or-errno)sys close Int => Int(fd->0or-errno)sys read fd Int Int => String(fd,max_bytes-> string read fromfd)sys write fd Int String => Int(fd,data-> bytes written or-errno)sys poll Int Int Int => Int(fd, Ridgun PAL normalizedevents(1=POLLIN/read,4=POLLOUT/write),timeout_ms->> 0revents when ready,0on timeout,< 0on-errno; usessys remaining mswhentimeout_ms < 0insidewith timeout)sys terminate Int Bool => Int(pid,force-> portable graceful/forced process termination) andsys kill Int Int => Int(pid, normalized signal ->0or-errno)sys call Int Int... => Int(nrplus0..6argumentsa1..a6-> low-level target-OS ABI syscall escape hatch)sys time ms => Intandsys sleep ms Int => ()(monotonic millisecond clock and sleep)sys deadline ms => Intandsys remaining ms => Int(active lexical timeout deadline and remaining milliseconds, or-1when unbounded)- Layer 2 — Declarative
with timeout <duration> doExecution Quota: - Just as
with region 64_KB dosets a lexical byte quota,with timeout 500_ms do(or combinedwith 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
- See
docs/lang/pal-and-system-libraries.mdfor the complete PAL & System Libraries (.rglib) architecture specification.
- Reusable Library Compilation (
.rglib& ELF64.so) and Extensiblestable(FR-COMP-006,FR-TOOL-003):
ridgunc build --lib <file.rg> -o <out.rglib>andridgunc build --so <file.rg> -o <lib.so>(orstable built lib/stable new --lib <name>) compile modules without requiringfn :: main => ()and preserve all exported functions, operators, and types.stablediscovers customfn :: task <words...> => Stringcommands andfn :: plugins => [String]extensions (plugins/*.rg,.stable/plugins/*.rg), listed viastable tasksandstable 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):
- 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 alongsidec, 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)
- 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 (+0on entry,-0on exit). - Internal Local Variables (
x = .../mut x = ...): Receive+1on assignment (x = expr,xs[3] = a). On mutable reassignment (x = new_expr),new_exprreceives+1before oldxreceives-1. - End of Function / Scope Exit:
- Non-returned internal local variables receive
-1first: ifrc == 0, the parent block is freed and cascades-1one by one to each direct child structure (recursively freeing any child whosercreaches0). - A returned internal local variable (
return a) receives-1(or is moved viarg_cmm_move), and even if itsrcis0in 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'sc = choose list cond a badds+1(1 + 1 = 2, shared betweenaandc).
- Policy
RMM3(Reserved):
- Reserved for a subsequent aggressive static optimization policy.
- See
docs/etc/cmm_architecture.mdfor the completeRMM1andRMM2specification.