Chapter 1

use "io/fio"
use "prelude"

rec Sphere(center: Point, radius: f64, color: Color, albedo: f32)
rec Plane(center: Point, normal: Vec3, color: Color, albedo: f32)

@entry
fn main argc: i32, argv: str* = {
    if argc != 2 {
        println "Please provide a file to cat"
        exit -1
    }
    var file = fopen argv[1] as str, "r"

    if file as u64 == 0u64 {
        printf "Could not open file %s\n", argv[1]
        exit -1
    }

    var eof = false
    var c: char

    while eof != true {
        c = fgetc file

        if c == EOF then
            eof = true
        else print c
    }

    fclose file
}

exp fn clamp self: Color -> Color =
    Color {
        red: (max (min self.red, 1.0), 0.0),
        green: (max (min self.green, 1.0), 0.0),
        blue: (max (min self.blue, 1.0), 0.0)
    }

fn print color: Color = printf "%d %d %d\n", (color.red * 255.0) as u8, (color.green * 255.0) as u8, (color.blue * 255.0) as u8

fn get_color sphere: Sphere, light: Light, ray: Ray, dist: f64 -> Color = {
    val hit_point = add (to_vec3 ray.origin), mul ray.direction, dist
    val normal = normal sphere, hit_point
    val dir_to_light = mul light.direction, -1f64
    val light_pow = (max (dot normal, dir_to_light) as f32, 0.0) * light.intensity as f32
    val light_refl = sphere.albedo / PI as f32

    val color = mul (mul (mul sphere.color, light.color), light_pow), light_refl

    clamp color
}

Syntax

Primitive Types

Primitive numeric types

Signed

i8
i16
i32
i64

Unsigned

u8
u16
u32
u64

Floating

f32
f64

String and char

str
char

Other

bool
type*     // pointer to `type`
&type     // reference to `type`
[type]    // slice of `type`
[type, n] // array of `type` with `n` length

Literals

There are a few main literals for aatbe.

Numeric

Integer

0
123
987

Hex

0x123
0xcafe
0xbeef

Floating

1.23
123.789

For integer numeric and floating point literals a suffix can be added to specify the value type

255u8        // unsigned 8 bit number
123456789u64 // unsigned 64 bit number
-128i16      // signed 16 bit number

32f32        // 32 bit floating point number
1.768f64     // 64 bit floating point number

Strings and characters

"Hello World!\n" // Simple escape sequences are supported
'a' // character literal `a`

Other

Booleans

true
false

Symbols

Format: Identifier separated by _ underscore with : prefix. Example:

:Symbol
:Foo
:Bar_Baz

Unit value

Equivalent to void/null in other languages, but itself is a value.

()

First Class Citizens

First class citizens are top level file structures, all support type parameters.

Functions

// format
<exp> <extern> fn snake_case param1: type1, param2: type2 (-> ReturnType) = <expr body>

// examples

fn foo () = { }
fn bar baz: i32 -> bool = { }

exp fn export_process param1: Type1, truth: bool -> ProcessedType2 = { }

extern fn fopen stream: FILE* -> i32

Modifiers

Export

To export a function from a file, the exp keyword is used somewhere in the function declaration, before the fn keyword.

External

To bind a function to an externally linked one, you use the extern keyword. External functions must not have a function body.

Imports

For importing files, the syntax is use "path", with either a relative path or absolute path for resolved external dependencies (currently only aatbe's stdlib is supported).

use "relative/path/to/file" // imports ./relative/path/to/file.aat
use "fmt/print"             // imports stdlib/fmt/print.aat
use "math/vec3"
use "prelude"               // imports stdlib/prelude.aat

Example

project/
├── main.aat
├── top.aat
└── module
    ├── foo.aat
    └── bar.aat
// main.aat

use "module/foo" // imports project/module/foo.aat
use "top"        // imports project/top.aat
// foo.aat

use "bar"        // imports project/module/bar.aat
use "../top.aat" // imports project/top.aat

Records

Analogue to C like structures.

rec Name(first: str, last: str)

Usage and initialization

// given the previous definition for Name

fn new :Name, first_val: str, last_val: str -> Name
  = Name {
    first: first_val,
    last: last_val,
  }

fn example () = {
  val name_a = Name {
    first: "John",
    last: "Jim",
  }

  val name_b = new :Name, "Jane", "Doe"
}

Typedefs

Newtype pattern

Newtype is a way to isolate values of a certain type, with a new name. Example definition and usage:

type Age = u8
type Name = str

fn example () = {
  val age = Age 18u8
  val name = Name "Jack"

  *age  // accesses the value of age (u8)
  *name // accesses the value of name (str)
}

Variant types

type Variant
  = CaseA str, bool
  | CaseB i32, str
  | CaseC bool

fn example () = {
  val caseA = CaseA "foo", true
  val caseC = CaseC false

  // Both caseA and caseC have the same parent type of `Variant
  // But they have a different variant type which can be matched on

  caseA.1 // access the str (first) field
  caseA.2 // access the bool (second) field
}

Misc Typedefs

type union = u8 | u16 | i32 // pretty much UB, works in the language, no idea why
type Opaque // used to forward define an opaque type

Operations

Mathematical operations

The common basic math ops are available, including order of operation.

Infix

Math

+ // addition
- // subtraction
* // multiplication
/ // division
% // modulo (remainder)

Bitwise

| // bitwise OR
& // bitwise AND
^ // bitwise XOR

Boolean

Comparison

== // Equality
!= // Inequality
>  // Greater
>= // Greater than or equals
<  // Lower
<= // Lower than or equals

Composition

|| // boolean OR
&& // boolean AND

Prefix

! // boolean negation
- // numeric negation
& // reference / address of
* // dereference

Logic

Branching

For branching, there is support for an if/else statement pair.

if <ret> cond <then> then_expr else else_expr

Examples

if true {
  // do something
} else {
  // do something else
}

if a == b then
  // do something
else
  // do something else

if ret foo.cond {
  ret "foo"
} else {
  ret "bar"
}

if a == b {
  // then
} else if foo {
  // otherwise
} else {
  // fallback
}

A particularity of Åtbe is the if ret syntax. The syntax is used to turn if/else statements into expressions. Similar to the ? (ternary) operator in other languages, or if/else statement pairs in Rust and Scala.

For if ret, the last statement in the body of both the then block and else block needs to have the same type as it will be used as a return value from either of the branches.

Loops

There are currently two types of loops available, while and until loops.

while/until cond expr

Usage

while true {
  // The while loop continues execution while the condition is true
}

until false {
  // The until loop continues execution until the condition is true
}

The two type of loops are basically opposites. For example, if your condition is while a != b it can be rewritten as until a == b.