Language Reference

Complete syntax and semantics reference for TinyLang. Every language feature is documented here with examples.

Data Types

TinyLang has six fundamental data types:

TypeDescriptionExamples
number64-bit floating point numbers42, 3.14, -7, 1e6
stringUnicode text (double or single quotes)"hello", 'world'
booleanLogical true or falsetrue, false
nullAbsence of valuenull
arrayOrdered collection of values[1, 2, 3]
objectKey-value pairs{name: "Alice", age: 30}

Numbers

let integer = 42
let float = 3.14159
let negative = -7
let scientific = 1e6     // 1000000

Strings

let double = "Hello, World!"
let single = 'Also valid'
let concat = "Hello" + ", " + "World"  // String concatenation with +
let length = len("hello")  // 5

Arrays

let empty = []
let nums = [1, 2, 3, 4, 5]
let mixed = [1, "two", true, null]
let nested = [[1, 2], [3, 4]]

// Access by index (zero-based)
print(nums[0])  // 1
print(nums[4])  // 5

Objects

let person = {
  name: "Alice",
  age: 30,
  city: "NYC"
}

// Property access
print(person.name)    // "Alice"
print(person["age"])  // 30

// Modify properties
person.age = 31

Variables

let - Mutable Variables

let x = 10
x = 20  // OK - can reassign

let name = "Alice"
name = "Bob"  // OK

const - Immutable Variables

const PI = 3.14159
// PI = 3.0  // Error! Cannot reassign a constant

const MAX_SIZE = 100
const APP_NAME = "TinyLang"

Compound Assignment

let x = 10
x += 5   // x = 15
x -= 3   // x = 12
x *= 2   // x = 24
x /= 4   // x = 6

Operators

Arithmetic

OperatorDescriptionExample
+Addition / String concatenation3 + 4 = 7
-Subtraction10 - 3 = 7
*Multiplication4 * 5 = 20
/Division15 / 4 = 3.75
%Modulo (remainder)17 % 5 = 2
**Exponentiation2 ** 10 = 1024
- (unary)Negation-x

Comparison

OperatorDescriptionExample
==Equal to5 == 5 = true
!=Not equal to5 != 3 = true
<Less than3 < 5 = true
<=Less than or equal5 <= 5 = true
>Greater than7 > 3 = true
>=Greater than or equal5 >= 5 = true

Logical

OperatorDescriptionExample
andLogical ANDtrue and false = false
orLogical ORtrue or false = true
notLogical NOTnot true = false

Conditional (Ternary)

cond ? whenTrue : whenFalse is an expression, so it can appear anywhere a value can. Only the arm that is selected is evaluated.

let n = 5
let label = n > 0 ? "positive" : "not positive"

print(items.length() == 0 ? "empty" : "has items")

let config = {retries: isProd ? 5 : 1}

It binds looser than every operator except assignment, so a comparison forms the condition without parentheses, and it is right-associative, so a chain reads top to bottom:

let grade = score > 90 ? "A" : score > 80 ? "B" : "C"
// groups as: score > 90 ? "A" : (score > 80 ? "B" : "C")

Control Flow

if / else

let age = 18

if age >= 18 {
  print("Adult")
} else if age >= 13 {
  print("Teenager")
} else {
  print("Child")
}

while Loop

let i = 0
while i < 5 {
  print(i)
  i += 1
}
// Output: 0 1 2 3 4

for..in Loop

Iterate over arrays, strings, or ranges:

// Iterate over an array
let fruits = ["apple", "banana", "cherry"]
for fruit in fruits {
  print(fruit)
}

// Iterate over a range (0 to 4)
for i in 0..5 {
  print(i)
}

// Iterate over characters of a string
for ch in "HELLO" {
  print(ch)
}

Ranges

The .. operator creates a range (exclusive upper bound):

// Range from 0 to 9
for i in 0..10 {
  print(i)
}

// Ranges work with variables
let n = 5
for i in 0..n {
  print(i)
}

break and continue

// break - exit loop early
for i in 0..100 {
  if i > 5 { break }
  print(i)
}

// continue - skip to next iteration
for i in 0..10 {
  if i % 2 == 0 { continue }
  print(i)  // Only odd numbers
}

match / when

Pattern matching for multi-way branching:

fn describe(code) {
  match code {
    when 200 => return "OK"
    when 404 => return "Not Found"
    when 500 => return "Server Error"
    else => return "Unknown"
  }
}

print(describe(200))  // "OK"
print(describe(404))  // "Not Found"

Match with blocks:

match op {
  when "+" => return a + b
  when "-" => return a - b
  when "/" => {
    if b == 0 {
      print("Error: division by zero")
      return null
    }
    return a / b
  }
  else => return null
}

Functions

Named Functions

fn add(a, b) {
  return a + b
}

print(add(3, 4))  // 7

Default Parameters

fn greet(name, greeting = "Hello") {
  return greeting + ", " + name + "!"
}

print(greet("Alice"))         // "Hello, Alice!"
print(greet("Bob", "Hi"))  // "Hi, Bob!"

Arrow Functions

let double = (x) => x * 2
let square = (x) => x * x
let isEven = (n) => n % 2 == 0

print(double(5))   // 10
print(square(4))   // 16
print(isEven(6))   // true

Closures

Functions capture variables from their enclosing scope:

fn makeCounter(start = 0) {
  let count = start
  return fn() {
    count += 1
    return count
  }
}

let counter = makeCounter()
print(counter())  // 1
print(counter())  // 2
print(counter())  // 3

Higher-Order Functions

fn apply(f, value) {
  return f(value)
}

let double = (x) => x * 2
print(apply(double, 5))  // 10

// Arrays support map, filter, reduce
let nums = [1, 2, 3, 4, 5]
let doubled = nums.map((x) => x * 2)        // [2, 4, 6, 8, 10]
let evens = nums.filter((x) => x % 2 == 0) // [2, 4]
let sum = nums.reduce((a, x) => a + x, 0)  // 15

Classes

Basic Classes

class Animal {
  let name = ""
  let sound = ""

  fn init(name, sound) {
    this.name = name
    this.sound = sound
  }

  fn speak() {
    print(this.name + " says " + this.sound)
  }
}

let dog = new Animal("Dog", "Woof")
dog.speak()  // "Dog says Woof"

Inheritance

class Dog extends Animal {
  let tricks = []

  fn init(name) {
    this.name = name
    this.sound = "Woof"
    this.tricks = []
  }

  fn learn(trick) {
    this.tricks.push(trick)
  }
}

let buddy = new Dog("Buddy")
buddy.speak()       // "Buddy says Woof" (inherited)
buddy.learn("sit")  // Dog-specific method

Class Features

  • Properties - Declared with let inside the class body
  • Constructor - The init() method is called on new
  • Methods - Declared with fn inside the class body
  • this - Refers to the current instance
  • Inheritance - Use extends to inherit from a parent class
  • Instantiation - Use new ClassName(args)

Modules

Importing

// Import specific items from a module
import {greet, add} from "./lib"

greet("World")
print(add(2, 3))
Note

Module paths are relative to the importing file. The .tiny extension is optional.

Test Blocks

TinyLang has built-in testing support:

fn add(a, b) {
  return a + b
}

test "addition works" {
  expectToBe(add(2, 3), 5)
  expectToBe(add(-1, 1), 0)
}

test "string concatenation" {
  let result = "hello" + " world"
  expectToBe(result, "hello world")
}

Run tests with:

tinylang test main.test.tiny

Comments

// Single-line comment

// Multi-line comments use multiple single-line comments
// There is no block comment syntax in TinyLang

Array Methods

Arrays support method-style calls for common operations:

let nums = [1, 2, 3, 4, 5]

// Transform
nums.map((x) => x * 2)         // [2, 4, 6, 8, 10]
nums.filter((x) => x > 2)       // [3, 4, 5]
nums.reduce((a, x) => a + x, 0) // 15

// Search
nums.find((x) => x > 3)         // 4
nums.includes(3)                // true

// Iterate
nums.forEach((x, i) => {
  print(i, x)
})

// Chaining
[1, 2, 3, 4, 5]
  .filter((x) => x % 2 == 0)
  .map((x) => x * x)
// [4, 16]

Scoping Rules

TinyLang uses lexical (block) scoping:

let x = 1

if true {
  let x = 2  // New variable in inner scope
  print(x)   // 2
}

print(x)  // 1 (outer scope unchanged)

String Interpolation (F-Strings)

Prefix a string with f to embed expressions inside curly braces. The expressions are evaluated and converted to strings automatically.

let name = "World"
let x = 7
print(f"Hello, {name}!")          // "Hello, World!"
print(f"{x} * {x} = {x * x}")    // "7 * 7 = 49"
print(f"Items: {[1, 2, 3]}")     // "Items: [1, 2, 3]"

// Works with any expression
let arr = [10, 20, 30]
print(f"Length: {len(arr)}, First: {arr[0]}")

Error Handling (Try/Catch/Throw)

TinyLang provides structured error handling with try, catch, and throw.

Throwing Errors

fn divide(a, b) {
  if b == 0 {
    throw "Division by zero!"
  }
  return a / b
}

Catching Errors

try {
  let result = divide(10, 0)
  print(result)
} catch err {
  print(f"Error: {err.message}")  // "Error: Division by zero!"
}

Error Propagation

Errors propagate up through function calls until caught by a try/catch block. Uncaught errors terminate the program with an error message.

fn validateAge(age) {
  if age < 0 { throw "Age cannot be negative" }
  return age
}

fn createUser(name, age) {
  let valid = validateAge(age)  // may throw
  return {name: name, age: valid}
}

try {
  let user = createUser("Bob", -5)
} catch err {
  print(err.message)  // "Age cannot be negative"
}

Destructuring

Destructuring lets you unpack values from arrays and objects into distinct variables.

Array Destructuring

let [a, b, c] = [1, 2, 3]
print(a)  // 1
print(b)  // 2
print(c)  // 3

// From function returns
fn getPoint() { return [3, 4] }
let [x, y] = getPoint()

Object Destructuring

let config = {host: "localhost", port: 8080}
let {host, port} = config
print(host)  // "localhost"
print(port)  // 8080

Spread Operator

The spread operator (...) expands an array or string into individual elements within another array.

Array Spread

let a = [1, 2, 3]
let b = [4, 5, 6]
let combined = [...a, ...b]      // [1, 2, 3, 4, 5, 6]
let withExtra = [0, ...a, 99]  // [0, 1, 2, 3, 99]

String Spread

let chars = [..."hello"]  // ["h", "e", "l", "l", "o"]