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/0xforms 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, so1 + 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, while1 / 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) versusvariablemutable_andmutating_method_(trailing underscore)_protectedand__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, so2 .+ 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};thenadd ()→4 - Anonymous argument:
.square {_ * _};thensquare 3→9 - Argument frames insert values into the closure’s scope:
.mag {(x * x) + (y * y)};thenmag (.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]
