Language Basics

This guide covers the core features of Kōdo that are available today.

Module Structure

Every .ko file contains exactly one module. A module has a name, a meta block, and one or more functions:

module my_program {
    meta {
        purpose: "What this module does",
        version: "0.1.0",
        author: "Your Name"
    }

    fn main() {
        println("Hello!")
    }
}

The meta block is mandatory. It makes every module self-describing — any reader (human or AI) can immediately understand the module’s purpose.

Meta Fields

FieldDescription
purposeWhat this module does (required)
versionSemantic version string
authorWho wrote it

Functions

Functions are declared with fn, followed by a name, parameters, and an optional return type:

fn add(a: Int, b: Int) -> Int {
    return a + b
}
  • Parameters must have explicit type annotations
  • Return type is declared with -> after the parameter list
  • Functions without a return type return nothing
  • The main function is the program’s entry point

Calling Functions

fn double(x: Int) -> Int {
    return x * 2
}

fn main() {
    let result: Int = double(21)
    print_int(result)
}

Recursion

Functions can call themselves:

fn factorial(n: Int) -> Int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n - 1)
}

Types

Kōdo supports the following primitive types:

TypeDescriptionExample
Int64-bit integer42, -7, 0
Float6464-bit floating point3.14, -0.5, 1.0
BoolBoolean valuestrue, false
StringString literals"hello"

The full type system also includes Int8, Int16, Int32, Int64, Float32, and Byte — see the Language Specification for details.

Variables can have explicit type annotations or use local type inference:

// Explicit type annotations
let x: Int = 42
let pi: Float64 = 3.14159
let name: String = "Kōdo"
let active: Bool = true

// Type inference — the compiler infers the type from the initializer
let y = 42           // inferred as Int
let greeting = "hi"  // inferred as String
let flag = true      // inferred as Bool
let tau = 6.28       // inferred as Float64

Note: Function signatures always require explicit type annotations. Type inference is local to let bindings only.

Variables

Immutable Variables

By default, variables are immutable:

let x: Int = 10
// x = 20  — this would be an error

Mutable Variables

Use let mut to create a mutable variable. Mutable variables can be reassigned using =:

let mut counter: Int = 0
counter = counter + 1

Reassignment requires the variable to have been declared with mut. The new value must match the variable’s type. Only simple variable names can be reassigned — field assignment is not yet supported.

Operators

Arithmetic

OperatorDescriptionExample
+Addition (Int, Float64, or String concatenation)a + b, "hi" + name
-Subtractiona - b
*Multiplicationa * b
/Divisiona / b
%Moduloa % b
-Negation (unary)-x

Arithmetic operators work on both Int and Float64 values. The + operator also supports String concatenation — "hello " + "world" produces "hello world".

Comparison

OperatorDescriptionExample
==Equala == b
!=Not equala != b
<Less thana < b
>Greater thana > b
<=Less or equala <= b
>=Greater or equala >= b

Logical

OperatorDescriptionExample
&&Logical ANDa && b
||Logical ORa || b
!Logical NOT!a

Control Flow

if/else

if x > 0 {
    println("positive")
} else {
    println("non-positive")
}

if/else blocks can be nested:

if x > 100 {
    println("large")
} else {
    if x > 0 {
        println("small positive")
    } else {
        println("non-positive")
    }
}

while

Use while to repeat a block while a condition is true:

let mut i: Int = 5
while i > 0 {
    print_int(i)
    i = i - 1
}

The condition must be a Bool expression. The loop body executes repeatedly until the condition becomes false.

Here’s a complete example with a countdown function:

fn countdown(n: Int) {
    let mut i: Int = n
    while i > 0 {
        print_int(i)
        i = i - 1
    }
    println("Liftoff!")
}

for-in

Use for-in to iterate over collections:

let items: List<Int> = list_new()
list_push(items, 10)
list_push(items, 20)
list_push(items, 30)

for item in items {
    print_int(item)
}

for-in works with List<Int>, List<String>, and Map keys. See Iterators for the full iterator protocol and more examples.

return

Use return to exit a function with a value:

fn abs(x: Int) -> Int {
    if x < 0 {
        return -x
    }
    return x
}

Builtin Functions

Kōdo provides builtin functions for output:

FunctionParameterDescription
println(s)StringPrint a string followed by a newline
print(s)StringPrint a string without a newline
print_int(n)IntPrint an integer followed by a newline
print_float(f)Float64Print a float without a newline
println_float(f)Float64Print a float followed by a newline
fn main() {
    println("The answer is:")
    print_int(42)
    println_float(3.14)
}

Complete Example

Here’s a program that combines everything covered in this guide:

module demo {
    meta {
        purpose: "Demonstrate Kōdo language basics",
        version: "0.1.0",
        author: "Kōdo Team"
    }

    fn is_even(n: Int) -> Bool {
        return n % 2 == 0
    }

    fn fizzbuzz_single(n: Int) {
        if n % 15 == 0 {
            println("FizzBuzz")
        } else {
            if n % 3 == 0 {
                println("Fizz")
            } else {
                if n % 5 == 0 {
                    println("Buzz")
                } else {
                    print_int(n)
                }
            }
        }
    }

    fn main() {
        let x: Int = 42
        let mut counter: Int = 1

        if is_even(x) {
            println("42 is even")
        }

        fizzbuzz_single(3)
        fizzbuzz_single(5)
        fizzbuzz_single(15)
        fizzbuzz_single(7)
    }
}

Next Steps