What AEL compiles today
Every program on this page was checked, compiled, built and run on linux-x86_64 with compiler commit 6f9c44a. The output shown is the output that appeared, and the exit status shown is the status the process returned.
AEL 0.1 cannot emit a string. print accepts
i64 and bool only, so there is no “hello, world” here.
print("hello, world") is refused with
error[E2105], and printing a bound Str<16> is refused
with error[E2100]. Both refusals are
shown below with their exact text.
The type vocabulary
This is the whole of it. There is nothing else in the language today.
| Kind | Types | Notes |
|---|---|---|
| Integers | i8 i16 i32 i64 u8 u16 u32 u64 |
Checked arithmetic and checked shifts at every width. |
| Scalars | bool, unit |
No floating point of any width, in any position. |
| Aggregates | struct, fixed arrays [T; N] |
By-value aggregate parameters and results work. |
| References | &T, &mut T |
Dereference is explicit. Indexing is bounds-checked. |
| Tagged values | declared enum, Option<T>, Result<T, E> |
With match and checked payload access. |
| Error carriers | CastError CapacityError IndexError StringError |
Returned instead of trapping when an operation will not fit. |
| Bounded collections | Vec<T, N>, Str<N> |
Capacity lives in the type. They never allocate. |
Those four generic names are the entire generic vocabulary. User generics are
refused: let held: Vault<i64> = 1; gives
error[E2900] user generics and unauthenticated runtime handle types are
unavailable, and struct Box<T> is refused by the parser.
Operations on the bounded collections are: string literals, vec_empty,
vec_len, str_len, length-checked vector indexing,
vec_push, vec_remove, str_append and
str_byte. Conversions are the eight cast_* functions.
Arithmetic escapes are the wrapping_* and saturating_* families.
Three facts to read the examples by
--tier core is usually required
With no --tier, ael check uses the bootstrap
tier, which accepts only fn declarations and only i64/bool
annotations. Anything using a struct, array, Vec or Str needs
ael check file.ael --tier core or it fails with P003/P002.
ael compile uses the core tier automatically and has no --tier flag.
Dereference is explicit
Indexing through a reference is written (*samples)[i]. Writing
samples[i] where samples is a &[i64; 4]
fails with error[E2100] indexing requires an array or vector.
print takes i64 and bool
Nothing else. str_len returns u32, so the examples compare
it against a constant and print the bool rather than printing the length.
print(true) writes true.
Structs, fixed arrays, references, bounds-checked indexing
// Structs, fixed arrays, references and bounds-checked indexing,
// with checked arithmetic and the explicit wrapping/saturating modes.
struct Reading {
sensor: i32,
value: i64,
}
fn total(samples: &[i64; 4]) -> i64 {
let mut sum: i64 = 0;
let mut i: i64 = 0;
while i < 4 {
sum = sum + (*samples)[i];
i = i + 1;
}
return sum;
}
fn main() -> i64 {
let samples: [i64; 4] = [10, 20, 30, 40];
let sum: i64 = total(&samples);
print(sum);
let reading: Reading = Reading(7, sum);
print(reading.value);
// Explicit modes are the only way to leave the checked default.
let wrapped: i8 = wrapping_add(127, 1);
let saturated: i8 = saturating_add(127, 1);
print(wrapped == -128);
print(saturated == 127);
return 0;
}
ael check checked.ael --tier core
ael compile checked.ael -o checked.aelir --target linux-x86_64 --no-metrics
ael build checked.aelir -o checked --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
./checked
What ./checked printed
100
100
true
true
wrapping_add(127, 1) is -128 and
saturating_add(127, 1) is 127, at i8. Without one
of those two words the same addition is a checked failure — see
checked failures.
Bounded Vec<T, N> and Str<N>
The capacity is part of the type, there is no heap, and an operation that will not fit returns a carrier instead of trapping.
// Bounded collections: Vec<T, N> and Str<N>. The capacity lives in the type,
// there is no heap, and an operation that will not fit returns a carrier
// instead of trapping.
fn main() -> i64 {
let mut queue: Vec<i64, 3> = vec_empty();
print(vec_len(&queue) == 0);
vec_push(&mut queue, 10);
vec_push(&mut queue, 20);
vec_push(&mut queue, 30);
print(vec_len(&queue) == 3);
// The fourth push does not trap: it hands back Err and leaves the
// vector byte-identical.
let overflowed = vec_push(&mut queue, 40);
match overflowed {
Result::Ok(nothing) => { return -1; },
Result::Err(rejected) => { print(rejected == 40); },
}
print(vec_len(&queue) == 3);
let taken = vec_remove(&mut queue, 0);
match taken {
Result::Ok(value) => { print(value); },
Result::Err(error) => { return -2; },
}
// Str<N> is a bounded byte buffer, also with its capacity in the type.
let mut greeting: Str<16> = "hello";
let target: Str<8> = ", world";
let appended = str_append(&mut greeting, &target);
match appended {
// print takes i64 and bool only, and str_len returns u32.
Result::Ok(nothing) => { print(str_len(&greeting) == 12); },
Result::Err(full) => { return -3; },
}
let byte = str_byte(&greeting, 0);
match byte {
Result::Ok(value) => { print(value == 104); },
Result::Err(out_of_range) => { return -4; },
}
// Reading past the stored length is refused, not a trap.
let past_end = str_byte(&greeting, 200);
match past_end {
Result::Ok(value) => { return -5; },
Result::Err(out_of_range) => { print(true); },
}
return 0;
}
ael check bounded.ael --tier core
ael compile bounded.ael -o bounded.aelir --target linux-x86_64 --no-metrics
ael build bounded.aelir -o bounded --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
./bounded
What ./bounded printed
true
true
true
true
10
true
true
true
A string literal is how a Str<N> is created, and that is as far as
strings go. There is no String type — let text: String = "…"
gives error[R0006] unknown type name: String — and no way to print the
contents of a Str<N>. One further intrinsic,
str_from_bytes, type-checks and compiles to IR but is refused by name at
native build time: native machine verification: native profile does not admit the
str_from_bytes intrinsic.
The eight checked conversions
A value the destination type cannot represent produces
Err(CastError::OutOfRange), never a wrap and never a silent truncation. The
range test is taken at the source's signedness.
// The eight cast_* conversions are checked: a value the destination type
// cannot represent produces Err(CastError::OutOfRange), never a wrap
// and never a silent truncation.
fn narrow(value: i64) -> i64 {
let converted: Result<i8, CastError> = cast_i8(value);
match converted {
Result::Ok(small) => { return 1; },
Result::Err(error) => { return 0; },
}
}
fn main() -> i64 {
print(narrow(100) == 1);
print(narrow(300) == 0);
print(narrow(-129) == 0);
print(narrow(-128) == 1);
// The range test is taken at the source's signedness, so u64 at 2^63
// is refused for i64 rather than read as negative.
let big: u64 = 9223372036854775808;
let widened: Result<i64, CastError> = cast_i64(big);
match widened {
Result::Ok(value) => { return -1; },
Result::Err(error) => { print(true); },
}
return 0;
}
ael check casts.ael --tier core
ael compile casts.ael -o casts.aelir --target linux-x86_64 --no-metrics
ael build casts.aelir -o casts --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
./casts
What ./casts printed
true
true
true
true
true
true lines. Exit status 0. Image 6367 bytes.Checked failures
There are seven categories. Each writes AEL panic: <category> to
standard error — file descriptor 2, separate from program output — and exits 101:
arithmetic_overflow, division_by_zero,
shift_out_of_range, index_out_of_range,
invalid_discriminant, invalid_control_flow,
output_io.
Arithmetic overflow
// Checked arithmetic is the default. An overflow is a checked failure:
// the process reports it and stops, it does not wrap.
fn main() -> i64 {
print(1);
let limit: i64 = 9223372036854775807;
let overflowed: i64 = limit + 1;
print(overflowed);
return 0;
}
$ ./overflow
1
AEL panic: arithmetic_overflow
$ echo $?
101
$ ./overflow 2>/dev/null
1
$ ./overflow 2>&1 1>/dev/null
AEL panic: arithmetic_overflow
print never runs. Image 4662 bytes. The last two commands show
the stream separation: program output on stdout, the panic line on stderr.
Index out of range
Every array index is compared against the length before the address is formed, at every integer kind, including a negative signed index.
// Every array index is compared against the length before the address is
// formed, at every integer kind, including a negative signed index.
fn main() -> i64 {
let values: [i64; 3] = [1, 2, 3];
let mut i: i64 = 0;
while i < 4 {
print(values[i]);
i = i + 1;
}
return 0;
}
$ ./bounds
1
2
3
AEL panic: index_out_of_range
$ echo $?
101
A project from ael init
A source/1 project is three files. ael check on a project
directory needs no --tier.
$ ael init demo --profile ael-project-init-filesystem-candidate/1
initialized "/path/to/parent" / "demo"
roots main.ael pack.ael metadata.ael
plan sha256:… # digest covers the plan and its parent path, so it differs per directory
$ ael check demo
Checked source/1 project …
$ ael compile demo -o demo.aelir --target linux-x86_64 --no-metrics
$ ael build demo.aelir -o demo-bin --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
$ ./demo-bin
$ echo $?
0
// main.ael
main main() {
return;
}
// pack.ael
pack { schema = 1; dependencies = []; }
// metadata.ael
metadata {
schema = 1;
name = "demo";
source_profile = "ael-source/1";
}
Give main a return type and the project returns an exit status. Replacing
main.ael with the program below makes ./demo-bin print
42 and exit 42.
main main() -> i64 {
let total: i64 = 6 * 7;
print(total);
return total;
}
The exit status is the low eight bits of the returned i64, so a program that
returns 6765 exits 109.
The failures that are not checked
There are exactly two documented conditions that escape the checked model. They are listed here rather than omitted, because omitting them would make “every failure is a checked failure” look true.
Stack exhaustion
No profile bounds recursion depth or installs a guard page. This program compiles and builds cleanly.
fn down(n: i64) -> i64 {
if n == 0 {
return 0;
}
return down(n - 1) + 1;
}
fn main() -> i64 {
print(down(100000000));
return 0;
}
$ ./stack
Segmentation fault (core dumped)
$ echo $?
139
AEL panic line. Image 4822 bytes.
Recorded as the stack-exhaustion limitation.
SIGPIPE
A print into a closed pipe kills the process before the
output_io category can fire: no panic line and no exit 101. Recorded as the
sigpipe-before-output-io limitation.
What the compiler refuses
Each row was produced by running the compiler. The diagnostic text is the compiler's own.
| You write | The compiler says |
|---|---|
print("hello, world") |
error[E2105] string literal requires an expected Str<N> type |
let s: Str<16> = "hello, world"; print(s); |
error[E2100] intrinsic arguments do not match the core signature registry |
let text: String = "…"; |
error[R0006] unknown type name: String |
let ratio: f64 = 1; |
error[R0006] unknown type name: f64 (and error[P002] at the bootstrap tier). A float literal is refused by the parser. |
let held: Vault<i64> = 1; |
error[E2900] user generics and unauthenticated runtime handle types are unavailable |
agent worker { } |
error[P003] reserved ecosystem declaration has no executable Core source semantics at the core tier; at the bootstrap tier, “bootstrap supports fn declarations; this declaration is unsupported”. |
| A call through a value rather than a name | “unresolved value; function values are unavailable”. There are no function values and no indirect calls. |
str_from_bytes(&bytes) |
Checks and compiles to IR, then ael build fails: native machine verification: native profile does not admit the str_from_bytes intrinsic. |
ael run fibonacci.ael |
“native execution is unavailable for this command or target…”, exit 2. A program is produced by compile then build, and launched by the operating system. |
ael build fib-win.aelir --target windows-x86_64 |
The same refusal sentence, exit 2. No Windows image has ever been produced. |
Not in the language at all
- No heap and no dynamic strings. This is a ruling, not a gap: the support ABI defines no allocation symbol, and a request for
ael.rt.allocorael.rt.freeis refused by its own class. - No floating point of any width, in any position.
- No user generics and no runtime handle types.
- No function values and no indirect calls.
- No threads and no concurrency an AEL program can create. There is no language surface that could name one, and the admitted computation is single-threaded.
- No network of any kind. No first-party crate contains
std::net,TcpStreamorTcpListener. - No file I/O. Output is
print, to standard output. - No optimizer.
--optaccepts onlynone, andnoneis the only level that exists.
What is left is bounded, fixed-capacity, single-threaded computation that can print integers and booleans and return an exit status. AEL 0.1 is not a general-purpose language and this site does not describe it as one.