NanoLang has exactly ONE canonical way to write each construct. This eliminates ambiguity and improves LLM code generation.
22.1 Core Principle
**ONE syntax per operation. No alternatives, no sugar, no shortcuts.**
When LLMs see multiple equivalent forms, they guess wrong ~50% of the time. NanoLang solves this by having exactly one way to write everything.
22.2 Operator Notation: Prefix and Infix
NanoLang supports **both prefix and infix** notation for binary operators:
# Both are valid and equivalent:
(+ a b) # Prefix notation
a + b # Infix notation
(* x 2) # Prefix
x * 2 # Infix
(== result 42) # Prefix
result == 42 # Infix
**Infix operators:** +, -, *, /, %, ==, !=, <, <=, >, >=, and, or
**Important:** All infix operators have **equal precedence**, evaluated **left-to-right** (no PEMDAS). Use parentheses to control grouping:
a * (b + c) # Parentheses required for addition-first
a + b * c # Evaluates as (a + b) * c
**Unary operators** (not, -) work without parens: not flag, -x
**Rule:** Function calls always use prefix notation (function_name arg1 arg2 ...)
22.3 Expressions vs Statements
Expressions: Use cond
# ✅ Expressions return values - use cond
let result: int = (cond
((< x 0) -1)
((> x 0) 1)
(else 0)
)
Statements: Use if/else
# ✅ Statements have side effects - use if/else
if (< x 0) {
(println "negative")
} else {
(println "non-negative")
}
**Never mix them up!**
22.4 String Concatenation
# ✅ ALWAYS use + operator
let greeting: string = (+ "Hello, " name)
# ❌ NEVER use str_concat (deprecated)
let bad: string = (str_concat "Hello, " name)
22.5 Array Access
# ✅ ALWAYS use array_get
let value: int = (array_get arr 0)
# ❌ NO subscript syntax (doesn't exist)
# let value: int = arr[0] # This is not valid!
22.6 Type Annotations
# ✅ ALWAYS annotate types explicitly
let x: int = 42
let name: string = "Alice"
let items: array<int> = [1, 2, 3]
# ❌ NEVER omit types
# let x = 42 # Type inference is minimal!
22.7 Function Calls
# ✅ ALWAYS use prefix notation for function calls
(my_function arg1 arg2)
# ❌ NO parentheses-free calls
# my_function arg1 arg2 # Invalid!
**Note:** Infix notation is for **operators only**, not function calls.
22.8 Loops
# ✅ for-in-range
for i in (range 0 10) {
(println i)
}
# ✅ while loops
while (< i 10) {
set i (+ i 1)
}
# ❌ NO foreach, do-while, or other loop forms
22.9 Complete Example
# ✅ Canonical NanoLang
fn calculate_sum(numbers: array<int>) -> int {
let len: int = (array_length numbers)
let mut sum: int = 0
for i in (range 0 len) {
let value: int = (array_get numbers i)
set sum (+ sum value)
}
return sum
}
shadow calculate_sum {
let nums: array<int> = [1, 2, 3, 4, 5]
assert (== (calculate_sum nums) 15)
}
Summary
**Canonical Rules:**
- Operators support both prefix
(+ a b)and infixa + bnotation - All infix operators have equal precedence (left-to-right, no PEMDAS)
- Function calls always use prefix notation:
(function_name arg1 arg2) condfor expressions,if/else(withelse ifchaining) for statements+for string concatenation- Explicit type annotations
array_get, never subscripts
**See also:** docs/CANONICAL_STYLE.md for complete reference
---
**Previous:** Chapter 21: Configuration
**Next:** Chapter 23: Higher-Level Patterns