v0.22.0—Bounded nullability checks and practical .NET migration guidance.See what's new

Documentation

Structure Tags

Structure tags define the organization of Calor code: modules, functions, and their boundaries.

Indent-only block structure. Calor uses Python-style significant indentation: a block opens with §M, §F, §CL, §L, §IF, … and ends at the first line that dedents back to the parent column. No closing tags are required. The compiler default is 2 spaces per nesting level (mixing tabs and spaces is rejected). Legacy structural closers (§/M, §/F, §/L, §/I, …) were removed and now raise Calor0830. Bulk-migrate older corpora with calor fix --heal-closers. The expression closers §/C and §/LAM remain valid.


Modules

Modules are like C# namespaces. They group related functions.

Syntax

Calor
§M{name}
  // contents

Example

Calor
§M{Calculator}
  // functions go here

Rules

  • name becomes the C# namespace
  • The module block extends until the next line that dedents back to column 0 (or end of file)
  • A legacy §/M closer raises Calor0830; migrate with calor fix --heal-closers

Functions

Functions are the primary code containers.

Syntax

Calor
§F{name:visibility}
  §I{type:param}       // inputs (0 or more)
  §O{type}             // output (required)
  §E{effects}          // effects (optional)
  §Q condition         // preconditions (0 or more)
  §S condition         // postconditions (0 or more)
  // body

Visibility

ValueMeaningC# Equivalent
pubPublicpublic static
priPrivateprivate static

Examples

Simple function:

Calor
§F{Add:pub}
  §I{i32:a}
  §I{i32:b}
  §O{i32}
  §R (+ a b)

Function with effects:

Calor
§F{PrintSum:pub}
  §I{i32:a}
  §I{i32:b}
  §O{void}
  §E{cw}
  §P (+ a b)

Function with contracts:

Calor
§F{Divide:pub}
  §I{i32:a}
  §I{i32:b}
  §O{i32}
  §Q (!= b 0)
  §R (/ a b)

Async Functions

Async functions use §AF instead of §F and automatically wrap return types in Task<T>.

Syntax

Calor
§AF{name:visibility}
  §I{type:param}       // inputs (0 or more)
  §O{type}             // output (auto-wrapped to Task<T>)
  // body with §AWAIT expressions

Examples

Simple async function:

Calor
§AF{FetchDataAsync:pub}
  §I{str:url}
  §O{str}
  §B{result} §AWAIT §C{httpClient.GetStringAsync} §A url §/C
  §R result

Emits C#:

C#
public static async Task<string> FetchDataAsync(string url)
{
    var result = await httpClient.GetStringAsync(url);
    return result;
}

Async void function (returns Task):

Calor
§AF{ProcessAsync:pub}
  §O{void}
  §AWAIT §C{Task.Delay} §A 1000 §/C

Automatic Task Wrapping

Declared OutputEmitted Return Type
§O{void}Task
§O{i32}Task<int>
§O{str}Task<string>
§O{Task<i32>}Task<int> (no double-wrap)

Async Methods

Async methods in classes use §AMT instead of §MT.

Syntax

Calor
§AMT{id:name:visibility}
  §I{type:param}
  §O{type}
  // body

Example

Calor
§CL{c001:DataService:pub}
  §AMT{mt001:GetUserAsync:pub}
    §I{i32:id}
    §O{User}
    §B{user} §AWAIT §C{_repository.FindAsync} §A id §/C
    §R user

Modifiers

Async methods support the same modifiers as regular methods:

Calor
§AMT{mt001:ProcessAsync:pub:virt}    // public virtual async
§AMT{mt002:HandleAsync:prot:ovr}     // protected override async
§AMT{mt003:ComputeAsync:pub:stat}    // public static async

Await Expression

Use §AWAIT to await async operations.

Syntax

Calor
§AWAIT expression                    // Simple await
§AWAIT{false} expression             // await with ConfigureAwait(false)
§AWAIT{true} expression              // await with ConfigureAwait(true)

Examples

Simple await:

Calor
§B{data} §AWAIT §C{client.GetAsync} §A url §/C

With ConfigureAwait(false) for library code:

Calor
§B{data} §AWAIT{false} §C{client.GetAsync} §A url §/C

Emits: var data = await client.GetAsync(url).ConfigureAwait(false);

Using Await in Conditions and Expressions

Calor
§IF{if1} §AWAIT §C{IsValidAsync} §A id §/C
  §P "Valid"

§R §AWAIT §C{ComputeAsync} §A x §/C

Lambda Expressions

Lambda expressions create anonymous functions using §LAM/§/LAM blocks.

Syntax

Calor
§LAM{id:param1:type1}              // single parameter
§LAM{id:param1:type1:param2:type2} // multiple parameters
§LAM{id}                           // no parameters

The header encodes parameters as colon-separated name:type pairs after the ID. Parameters without known types use object as the default type.

Single Parameter

Calor
§B{doubler} §LAM{lam1:x:i32} (* x 2) §/LAM{lam1}

Emits: var doubler = (int x) => x * 2;

Multiple Parameters

Parameters are listed as consecutive name:type pairs:

Calor
§B{add} §LAM{lam1:a:i32:b:i32} (+ a b) §/LAM{lam1}

Emits: var add = (int a, int b) => a + b;

Three parameters:

Calor
§B{combine} §LAM{lam1:x:i32:y:str:z:bool} §C{Process} §A x §A y §A z §/C §/LAM{lam1}

No Parameters

Calor
§B{getTime} §LAM{lam1} §C{DateTime.Now} §/C §/LAM{lam1}

As LINQ Arguments

Lambdas are commonly used as arguments inside §C{...} calls:

Calor
§C{numbers.Where} §A §LAM{lam1:n:i32} (!= (% n 3) 0) §/LAM{lam1} §/C
§C{items.Select} §A §LAM{lam2:x:i32} (* x 2) §/LAM{lam2} §/C

Statement Body Lambdas

For multi-statement bodies, include statements between the tags:

Calor
§B{printer} §LAM{lam1:x:i32}
§P x
§P (* x 2)
§/LAM{lam1}

Async Lambdas

Add async after the ID, before parameters:

Calor
§LAM{lam1:async:x:i32}
§B{result} §AWAIT §C{ProcessAsync} §A x §/C
§R result
§/LAM{lam1}

Note: §I{type:name} inline parameter declarations are NOT supported inside §LAM headers. All parameters must be encoded in the header using the name:type pair format.


Delegate Definitions

Delegates define function signatures that can be passed as values.

Syntax

Calor
§DEL{id:name}
  §I{type:param}       // parameters (0 or more)
  §O{type}             // return type (optional for void)
  §E{effects}          // effects (optional)

Examples

Calculator delegate:

Calor
§DEL{d001:Calculator}
  §I{i32:a}
  §I{i32:b}
  §O{i32}

Emits: public delegate int Calculator(int a, int b);

Void delegate:

Calor
§DEL{d001:Logger}
  §I{str:message}

Emits: public delegate void Logger(string message);

Delegate with effects:

Calor
§DEL{d001:FileProcessor}
  §I{str:path}
  §O{bool}
  §E{fs:rw}

Enum Definitions

Enums define a set of named constants.

Syntax

Calor
§EN{id:Name}
  Member1
  Member2 = 1
  Member3 = 2

With underlying type:

Calor
§EN{id:Name:underlyingType}
  ...

Examples

Simple enum:

Calor
§EN{e001:Color}
  Red
  Green
  Blue

Emits:

C#
public enum Color
{
    Red,
    Green,
    Blue,
}

Enum with explicit values:

Calor
§EN{e001:StatusCode}
  Ok = 200
  NotFound = 404
  Error = 500

Enum with underlying type:

Calor
§EN{e001:Flags:u8}
  None = 0
  Read = 1
  Write = 2

Emits:

C#
public enum Flags : byte
{
    None = 0,
    Read = 1,
    Write = 2,
}

Enum Extension Methods

Extension methods can be added to enums using §EEXT (Enum EXTension).

Syntax

Calor
§EEXT{id:EnumName}
  §F{f001:MethodName:pub}
    §I{EnumName:self}
    §O{returnType}
    // body using self

The first parameter with the enum type (or named self) becomes the this parameter.

Example

Calor
§EN{e001:Color}
  Red
  Green
  Blue

§EEXT{ext001:Color}
  §F{f001:ToHex:pub}
    §I{Color:self}
    §O{str}
    §W{sw1} self
    §K Color.Red → §R "#FF0000"
    §K Color.Green → §R "#00FF00"
    §K Color.Blue → §R "#0000FF"

  §F{f002:IsPrimary:pub}
    §I{Color:self}
    §O{bool}
    §R (|| (== self Color.Red) (|| (== self Color.Green) (== self Color.Blue)))

Emits:

C#
public enum Color
{
    Red,
    Green,
    Blue,
}

public static class ColorExtensions
{
    public static string ToHex(this Color self)
    {
        return self switch
        {
            Color.Red => "#FF0000",
            Color.Green => "#00FF00",
            Color.Blue => "#0000FF",
            _ => throw new ArgumentOutOfRangeException(nameof(self))
        };
    }

    public static bool IsPrimary(this Color self)
    {
        return self == Color.Red || self == Color.Green || self == Color.Blue;
    }
}

Event Definitions

Events allow objects to notify subscribers of state changes.

Syntax

Calor
§EVT{id:name:visibility:delegateType}
PartDescription
idUnique identifier
nameEvent name
visibilitypub, pri, prot
delegateTypeDelegate type for handlers

Example

Calor
§CL{c001:Button:pub}
  §EVT{e001:Click:pub:EventHandler}
    §EVT{e002:ValueChanged:pub:EventHandler<ValueChangedEventArgs>}

Emits:

C#
public class Button
{
    public event EventHandler Click;
    public event EventHandler<ValueChangedEventArgs> ValueChanged;
}

Event Subscribe/Unsubscribe

Use §SUB and §UNSUB to add or remove event handlers.

Syntax

Calor
§SUB eventRef handlerRef      // Subscribe
§UNSUB eventRef handlerRef    // Unsubscribe

Examples

Subscribe to event:

Calor
§SUB button.Click OnButtonClick

Emits: button.Click += OnButtonClick;

Unsubscribe from event:

Calor
§UNSUB button.Click OnButtonClick

Emits: button.Click -= OnButtonClick;

Subscribe with lambda:

Calor
§SUB button.Click (sender, e) → §P "Clicked!"

Input Parameters

Input parameters define function arguments.

Syntax

Calor
§I{type:name}

Examples

Calor
§I{i32:x}           // int x
§I{str:name}        // string name
§I{bool:flag}       // bool flag
§I{?i32:maybeVal}   // int? maybeVal (nullable)
§I{[u8}:data}       // byte[] data
§I{[str}:args}      // string[] args

Multiple Parameters

Calor
§F{f001:Add:pub}
  §I{i32:a}
  §I{i32:b}
  §I{i32:c}
  §O{i32}
  §R (+ (+ a b) c)

Output Type

Every function must declare its output type.

Syntax

Calor
§O{type}

Examples

Calor
§O{void}     // returns nothing
§O{i32}      // returns int
§O{str}      // returns string
§O{?i32}     // returns nullable int
§O{i32!str}  // returns Result<int, string>
§O{[u8}}     // returns byte[]
§O{[str}}    // returns string[]

Array Types

Calor uses bracket notation [T] for array types, which aligns with common programming language conventions.

Syntax

Calor
[elementType]         // Single-dimensional array
[[elementType]]       // Jagged array (array of arrays)

Examples

Calor TypeC# Equivalent
[u8]byte[]
[i32]int[]
[str]string[]
[bool]bool[]
[[i32]]int[][]

Usage in Fields and Methods

Calor
§CL{c001:DataProcessor}
  §FLD{[u8}:_buffer:priv}       // private byte[] _buffer
  §FLD{[i32}:_indices:priv}     // private int[] _indices

  §MT{m001:ProcessData:pub}
    §I{[str}:args}              // string[] args parameter
    §O{i32}
    §R args.Length

Block Structure (Indentation)

Calor blocks are delimited by indentation, like Python. A block opens with one of the structural tags (§M, §F, §AF, §MT, §AMT, §CL, §IFACE, §L, §WH, §DO, §IF, §W, §TR, §LAM, §DEL, §EN, §EEXT, §PROP) and ends at the next line that dedents back to (or past) the parent column.

Rules

  1. The compiler default is 2 spaces per nesting level
  2. Tabs and spaces must not be mixed within a block
  3. Chain continuations (§EI, §EL, §K, §WHEN, §CA, §FI) sit at the same column as their parent (§IF, §W, §TR), not indented inside it
  4. Legacy structural §/X closers raise Calor0830

Example

Calor
§M{Example}
  §F{Main:pub}
    §O{void}
    §E{cw}
    §L{i:1:10:1}
      §IF (> i 5)
        §P i
      §EL
        §P "small"

The ends of the §IF, §L, §F, and §M blocks are inferred from dedents; do not write structural closer lines. If you write them explicitly, the lexer drops them silently — but calorfmt will strip them in a future release.

Tag Reference

OpenerPurpose
§M{name}Module
§F{name:vis}Function
§AF{name:vis}Async function
§CL{name}Class
§IFACE{name}Interface
§MT{name:vis}Method
§AMT{name:vis}Async method
§L{var:from:to:step}For loop
§WH condWhile loop
§DODo-while loop (condition on closing line during transition)
§IF condConditional (with §EI / §EL continuations)
§W exprSwitch (with §K cases)
§TRTry (with §CA / §FI)
§LAM{params}Lambda (block body)
§DEL{name}Delegate definition
§EN{name}Enum
§EEXT{enumName}Enum extension methods
§PROP{name:type:vis}Property (may carry an effect row: §PROP{...} §E{cw}, since 0.17)
§EVT{name:vis:type}Event (single line, no block)
§C{target}Call expression (§/C ends the argument list)

Generics

Calor supports generic functions, classes, interfaces, and methods using angle bracket syntax.

Type Parameters

Type parameters are declared using <T> suffix syntax after the tag attributes.

Calor
§F{id:name:vis}<T>         // Generic function with one type parameter
§F{id:name:vis}<T, U>      // Generic function with two type parameters
§CL{id:name}<T>            // Generic class
§IFACE{id:name}<T>         // Generic interface
§MT{id:name:vis}<T>        // Generic method

Constraints

Type parameter constraints are declared using §WHERE clauses.

New syntax (recommended):

Calor
§WHERE T : class                    // T must be a reference type
§WHERE T : struct                   // T must be a value type
§WHERE T : new()                    // T must have parameterless constructor
§WHERE T : IComparable<T>           // T must implement interface
§WHERE T : class, IComparable<T>    // Multiple constraints

Legacy syntax (still supported):

Calor
§WR{T:class}                        // T must be a reference type
§WR{T:IComparable}                  // T must implement interface

Generic Type References

Generic types are written inline using angle bracket syntax.

Calor
§I{List<T>:items}                   // Parameter of type List<T>
§I{Dictionary<str, T>:lookup}       // Nested generic types
§O{IEnumerable<T>}                  // Generic return type
§FLD{List<T>:_items:pri}            // Generic field type

Examples

Generic identity function:

Calor
§F{f001:Identity:pub}<T>
  §I{T:value}
  §O{T}
  §R value

Generic class with constraint:

Calor
§CL{c001:Repository:pub}<T>
  §WHERE T : class
  §FLD{List<T>:_items:pri}

  §MT{m001:Add:pub}
    §I{T:item}
    §O{void}
    §C{_items.Add} §A item §/C

  §MT{m002:GetAll:pub}
    §O{IReadOnlyList<T>}
    §R _items

Generic interface:

Calor
§IFACE{i001:IRepository}<T>
  §WHERE T : class
  §MT{m001:Get}
    §I{i32:id}
    §O{T}

Empty interface (changed in 0.16): a marker interface has no members, so there is nothing indented under it to tell the parser where it stops. From 0.16 an empty §IFACE ends at the next type opener — §CL, §IFACE, §EN, §D, §DEL, or a §CSHARP block — written at exactly the same column as the §IFACE itself:

Calor
§M{m001:Demo}
  §IFACE{i001:IMarker}
  §CL{c001:Thing:pub}
    §MT{mt1:Go:pub} () -> void
      §E{}

Three things this rule does not say:

  • The column has to match. An indented type opener is not a sibling — it looks like you meant to nest one type inside the interface, which Calor does not have — so it is still a Calor0100 error rather than being quietly moved out one level.
  • Only type openers end it. A same-column §F after an empty §IFACE is still Calor0100; the rule is about types sitting next to each other, not about columns in general.
  • This is an interface rule, not a general one. An empty §CL does not behave the same way, and the difference is silent: a §CL written at the same column right after an empty §CL is parsed as nested inside it, with no diagnostic at all. If you write an empty class, give it a body or expect the next one to end up inside it.

C# Attributes

C# attributes are preserved during conversion using inline bracket syntax [@Attribute].

Syntax

Calor
§CL{id:name}[@AttributeName]
§CL{id:name}[@AttributeName(args)]
§MT{id:name:vis}[@Attr1][@Attr2]

Examples

Class with routing attributes (ASP.NET Core):

Calor
§CL{c001:JoinController:ControllerBase}[@Route("api/[controller]")][@ApiController]
  §MT{m001:Post:pub}[@HttpPost]

Property with validation:

Calor
§PROP{p001:Email:str:pub}[@Required][@EmailAddress]
  §GET
    §SET

Attribute Arguments

StyleSyntaxExample
No args[@Name][@ApiController]
Positional[@Name(value)][@Route("api/test")]
Named[@Name(Key="value")][@JsonProperty(PropertyName="id")]
Mixed[@Name(pos, Key=val)][@Range(1, 100, ErrorMessage="Invalid")]

Supported Elements

Attributes can be attached to:

  • Classes: §CL{...}[@attr]
  • Interfaces: §IFACE{...}[@attr]
  • Methods: §MT{...}[@attr]
  • Properties: §PROP{...}[@attr]
  • Fields: §FLD{...}[@attr]
  • Parameters: §I{type:name}[@attr]

Why Indent-Based Blocks?

  1. Familiar to agents - LLMs trained on Python have strong priors for indentation
  2. Tag-light - No §/X to type, no mismatched-closer errors
  3. Refactoring safe - Stable IDs on openers still survive code movement
  4. Lower edit cost - In edit-workload studies, indent form reduced agent token cost by ~16% with no regression in correctness

Next

  • Types - Type system reference