TSM-5: Homoiconic C (HC) Syntax Cheat Sheet

Introduction

Homoiconic C (“HC”) is a minimalist and highly expressive alternative to traditional programming languages. It eschews traditional grammar, keywords, and reserved words, focusing instead on a single type of object called a Frame. HC’s syntax is a thin veneer over its robust semantics, which are centered around ubiquitous scope, consistent evaluation, and homoiconicity (symmetry between code and data). This cheat sheet provides an overview of HC’s key syntactical elements.

Updated October 2026 for hclang 0.16.0. Every example below was run against the npm CLI (npx hclang@latest -e '…'), and the output is shown after →. The main changes since 2024: map is now & and reduce is |; comparisons use the dotted form (5.> 1); 0b/0o/0x forms are blobs, not integers; and a trailing ; suppresses output. The full reference is in the hclang repo.

1. Frames

Everything in HC is a Frame, which combines aspects of dictionaries, arrays, and functions. There are three types of Aggregate Frames:

  • FrameLazy: { closure } – Functions
  • FrameArray: [ tuple ] – Lists
  • FrameExpr: ( group ) – Precedence

2. Separators

  • Statement Separator: ; (dictionary-like, non-enumerable). A statement does not print, so 1 + 1; shows nothing.
  • Expression Separator: , (array-like, enumerable): 2 * 3, 7 - 4 → (6, 3)
  • Newline: Acts as a comma for separating expressions.

3. Primitives

3.1 Strings

  • Smart quotes, which nest without escapes: “a “b” c” → “a “b” c”
  • ASCII quotes are canonicalized to smart quotes: "Strings" → “Strings”
  • Juxtaposition joins strings, which serves as interpolation: “a” 1 “b” → “a1b”
  • Resource identifiers are inert names for things outside the program: 'jsr:@swanfactory/hclang'
  • Comments: #inline# or # to end of line: 1 #note# + 2 → 3

3.2 Numeric

  • Integer (exact, arbitrary precision): 123
  • Decimal (exact): 123.456
  • Sequence (inert; structural = only): 1.408.055.1212
  • Typed number (exact magnitude with an opaque unit): 9.8.m, 9.8.kg.m.s-2; 9.80.m = 9.8.m → <>
  • Rationals are results, not literals: 1 / 3 → 1/3, while 1 / 2.0 → 0.5
  • Modulo: 7 %% 3 → 1

3.3 Times

  • Instant: %2026-08-21T00:00:00Z%
  • Date (start of that UTC day): %2026-08-21%
  • Duration: %PT1H30M%
  • Arithmetic: %2026-08-21% + %PT1H% → %2026-08-21T01:00:00Z%; %PT1H% * 2 → %PT2H%
  • The current time is a grant, not a literal: 'clock:now'

3.4 BLOBs (Binary Large Objects)

  • Direct bytes: \5\Bytes
  • Binary, octal, hex: 0b101, 0o755, 0xDEADBEEF (these are blobs, not integers)
  • Base64: 0sBASE64 (documented, but it fails in 0.16.0)

4. Identifiers

Apart from aggregates and primitives, everything else is an identifier:

  • .Names – Set a property: .p 42;
  • Values – Read a value: p → 42
  • @Controls – Control flow, such as reassignment inside a mutating method: @v 9
  • $ – The current file or module namespace
  • $$ – The explicit host namespace

Identifier spelling carries effect and access:

  • CONST (uppercase) versus variable
  • mutable_ and mutating_method_ (trailing underscore)
  • _protected and __private (leading underscores)

5. Expressions

A sequence of identifiers and primitives forms an expression.

5.1 Properties

  • Dictionary: [.x 1; .y 2;]
  • Composite: [.name “w”; 85, 110]
  • Internal reference: .n [.min 3; .max 9; min, max] → [.min 3; .max 9; 3, 9]
  • External reference: n .min → 3

5.2 Operators

  • Math: + - * / (2 + 2 → 4); operators are properties, so 2 .+ 2 → 4
  • Equality: = is comparison, never assignment (1 = 1 → <>); == and === compare data and metadata
  • Ordering uses the dotted form: 5.> 1 → <>, 1.< 5 → <>
  • Nil (false): ()
  • All (true): <>
  • Membership: 1 ~ <> → <>

5.3 Closures and Arguments

  • Lazy expression: .add {2 + 2}; then add () → 4
  • Anonymous argument: .square {_ * _}; then square 3 → 9
  • Argument frames insert values into the closure’s scope: .mag {(x * x) + (y * y)}; then mag (.x 1; .y 2;) → 5

5.4 Inheritance and Mutation

Declare a parent with .^, and read inherited properties by plain name:

.base [.x 1;];
.kid [.^ base; .y 2;];
kid.x
→ 1

A mutating method has a trailing _ and reassigns with @:

.o_ [.v 1; .set_ {1 ? {@v 9}}];
o_.set_ ();
o_.v
→ 9

6. Predefined Operators

6.1 Conditionals

? evaluates its argument when the receiver is not nil, and : evaluates its argument when the receiver is nil:

5.> 1 ? {100}         → 100
1.> 5 : {10}          → 10
1.> 5 ? {100} : {10}  → 10

The chained form works as an else. When the condition is true, ? returns 100, and 100 : {10} then returns nil, so use ? on its own for the true branch.

6.2 Iterators

  • Map (&) applies a function to each element:

    [1, 2, 3] & { _ + 1 }  → [2, 3, 4]
    
  • Reduce (|) threads a seed through the elements by juxtaposition. Each answer becomes the receiver for the next element:

    [1, 2, 3] | “”  → “123”
    [1, 2, 3] | []  → [1, 2, 3]
    [1, 2, 4] | 1   → 8
    
  • Members (&&, ||) iterate over [key, value] pairs:

    [10, 20] && { _ }          → [[.0, 10], [.1, 20]]
    (.a 1; .b 2;) && { _ .0 }  → [.a, .b]
    

Leave a Reply

Blog at WordPress.com.

Up ↑