Language Basics
Nostos syntax is designed to be minimal and readable. It draws inspiration from Ruby, Rust, and Elixir.
Getting Started
Run Nostos programs from the command line:
# Run a program
nostos myprogram.nos
# Start the interactive REPL
nostos
# Show help
nostos --help
Command Line Options
--threads N- Use N worker threads (default: all CPUs)--no-jit- Disable JIT compilation (for debugging)--debug- Show local variables in stack traces--profile- Enable function call profiling--json-errors- Output errors as JSON (for IDE integration)
Hello World
Every Nostos program starts with a main() function:
# hello.nos
main() = {
println("Hello, World!")
}
Literals
# Numbers
42 # Int
3.14 # Float
1_000_000 # Underscores for readability
0xFF # Hex
# Boolean
true
false
# Strings & Chars
"Hello World" # String (double-quoted)
'{"key": "val"}'# String (single-quoted, for embedding double quotes)
'a' # Char (single char in single quotes)
# Unit (Empty value)
()
Comments
Use # for single-line comments.
# This is a comment
x = 10 # Comment at end of line
Bindings: Immutable & Mutable
By default, bindings in Nostos are immutable. Use var to create mutable bindings.
main() = {
# Immutable binding (default)
x = 10
# x = 20 # ERROR: cannot reassign immutable variable
# Mutable binding
var count = 0
count = count + 1 # OK
println(count)
}
You can shadow an existing binding by declaring a new one with the same name.
main() = {
x = 10 # immutable
x = x + 5 # new binding shadows the old one (value is 15)
var x = x + 5 # mutable binding shadows (value is 20)
x = 25 # OK, x is now mutable
println(x)
}
Typed Bindings
Nostos supports optional type annotations using colon syntax: name: Type = value.
Types are inferred when not specified.
main() = {
# With explicit types
x: Int = 42
pi: Float = 3.14159
name: String = "Alice"
# Mutable with type
var counter: Int = 0
counter = counter + 1
# Generic types
numbers: List[Int] = [1, 2, 3]
# Without types (inferred)
y = 100 # inferred as Int
greeting = "Hello" # inferred as String
println(x)
}
Blocks & Expressions
Everything in Nostos is an expression. Blocks are defined with {}.
The last expression in a block is the return value.
main() = {
x = 10
y = 20
# The result of x + y is returned automatically
x + y
}
Control Flow
If-Then-Else
Conditionals use if...then...else syntax. They are expressions that return values.
# Simple conditional expression
larger(a, b) = if a > b then a else b
# Multi-line with blocks
absolute(x) = if x < 0 then {
-x
} else {
x
}
# Chained conditions
grade(score) = if score >= 90 then "A"
else if score >= 80 then "B"
else if score >= 70 then "C"
else "F"
For Loops
Use for i = start to end to iterate over a range.
main() = {
var sum = 0
for i = 1 to 10 {
sum = sum + i
}
sum # Returns 45 (1+2+...+9, excludes 10)
}
While Loops
Use while condition { ... } for condition-based loops.
main() = {
var sum = 0
var i = 1
while i <= 10 {
sum = sum + i
i = i + 1
}
sum # Returns 55
}
Break and Continue
Use break to exit a loop early and continue to skip to the next iteration.
# Find first occurrence
findFirst(arr, target) = {
var result = -1
for i = 0 to arr.length() {
if arr[i] == target then {
result = i
break
}
}
result
}
# Sum only positive numbers
sumPositive(arr) = {
var total = 0
for i = 0 to arr.length() {
if arr[i] < 0 then continue
total = total + arr[i]
}
total
}
Assertions
Use assert and assert_eq to verify conditions during development and testing.
double(n) = n * 2
main() = {
x = 5
# Assert a condition is true
assert(x > 0)
assert(x < 10)
# Assert two values are equal
assert_eq(x * 2, 10)
assert_eq([1, 2, 3], [1, 2, 3])
# Useful in tests
result = double(21)
assert_eq(result, 42)
println("All assertions passed!")
}
Tip: Assertions throw exceptions when they fail, making them ideal for writing tests and validating assumptions in your code.