Language Basics

This page covers Ea's scalar language features. For SIMD vector types and operations, see SIMD.

Scalar types

Ea has fixed-size numeric types. No type inference -- you always write the type explicitly.

TypeDescription
i8, i16, i32, i64Signed integers
u8, u16, u32, u64Unsigned integers
f32, f64Floating point
boolBoolean (true / false)

Variables

All variables must have an explicit type annotation. Variables are immutable by default.

let x: i32 = 5
let y: f32 = 3.14
let flag: bool = true

To make a variable mutable, use mut:

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

Constants

Compile-time constants use const:

const PI: f32 = 3.14159
const BATCH_SIZE: i32 = 256
const EPSILON: f64 = 1e-9

Constants can be used in static_assert for compile-time checks:

const STEP: i32 = 8
static_assert(STEP > 0, "step must be positive")

Arithmetic and comparison

Standard arithmetic operators work on all numeric types:

let a: i32 = 10 + 3    // 13
let b: i32 = 10 - 3    // 7
let c: i32 = 10 * 3    // 30
let d: i32 = 10 / 3    // 3 (integer division)
let e: i32 = 10 % 3    // 1 (remainder)

Comparison operators return bool:

let lt: bool = a < b
let gt: bool = a > b
let le: bool = a <= b
let ge: bool = a >= b
let eq: bool = a == b
let ne: bool = a != b

Logical operators

Ea uses words, not symbols, for logical operations:

let both: bool = a > 0 and b > 0
let either: bool = a > 0 or b > 0
let neither: bool = not (a > 0 or b > 0)

Control flow

if / else if / else

if x > 0 {
    println(1)
} else if x == 0 {
    println(0)
} else {
    println(-1)
}

while loops

let mut i: i32 = 0
while i < n {
    out[i] = data[i] * 2
    i = i + 1
}

for loops

Counted loops with an explicit step:

for i in 0..n step 1 {
    out[i] = data[i] * 2
}

The step is required. The range 0..n is half-open: it includes 0 but excludes n.

foreach loops

A simpler counted loop when the step is always 1:

foreach (i in 0..n) {
    out[i] = data[i] * 2
}

Loop unrolling

Wrap a loop in unroll(N) to unroll it at compile time:

unroll(4) {
    foreach (j in 0..4) {
        out[base + j] = data[base + j] * factor
    }
}

Functions

Functions are declared with func. All parameter and return types are explicit:

func square(x: f32) -> f32 {
    return x * x
}

func add(a: i32, b: i32) -> i32 {
    return a + b
}

Functions without a return type return nothing (void):

func fill(out: *mut i32, n: i32, val: i32) {
    foreach (i in 0..n) {
        out[i] = val
    }
}

Exported functions

To make a function callable from C/Python/Rust, prefix it with export:

export func dot_product(a: *f32, b: *f32, n: i32) -> f32 {
    let mut sum: f32 = 0.0
    foreach (i in 0..n) {
        sum = sum + a[i] * b[i]
    }
    return sum
}

Only export func (and export kernel) produce symbols visible from outside. Non-exported functions are internal helpers.

Pointers

Pointers are how kernels receive data from the host language. There are four pointer variants:

SyntaxMeaning
*TRead-only pointer
*mut TMutable pointer (can write through it)
*restrict TRead-only, no aliasing (enables optimizations)
*restrict mut TMutable, no aliasing

Pointer indexing

Read from a pointer with bracket indexing:

let val: f32 = data[i]    // data is *f32

Write through a mutable pointer:

out[i] = val              // out is *mut f32

Type casts

Explicit casts convert between numeric types:

let x: i32 = 42
let f: f32 = to_f32(x)       // 42.0
let d: f64 = to_f64(x)       // 42.0
let back: i32 = to_i32(f)    // 42
let wide: i64 = to_i64(x)    // 42

There are no implicit conversions. Mixing types without a cast is a compile error.

println

println is the only output primitive. It exists for debugging:

println(42)
println(3.14)
println(true)
println("hello")

It accepts integers, floats, bools, and string literals. It does not support format strings.

What does not exist

Ea is deliberately minimal. The following features do not exist and are not planned:

  • No generics or templates
  • No traits or interfaces
  • No modules or imports
  • No heap allocation
  • No strings (except literal arguments to println)
  • No semicolons (statements are newline-separated)
  • No closures or lambdas
  • No enums or pattern matching
  • No exceptions or error handling

One file, one compilation unit. Compose at the C level.