GitHub
#!/usr/bin/env ape """ Ape is a systems-level programming language that is fast to write, compile, and run. """ # Declarations x, y, z = 5, "Hello, world!", .an_enum_literal s: type[.str] # zero-initialized w: type[.int] = del # uninitialized assert type(x) == type[.int] # Shadowing x = 5.0 assert type(x) == type[.float] # now a float! # Explicit reassignment x := 10 assert type(x) == type[.float] # still a float! # Multiple call syntax, "bare" procedure calls assert type(type) == type == type[.type] # First-class *static* typing id = type[.id] int = type[.int] int32 = int[bits=32] float = type[.float] str = type[.str] list = type[.list] tuple = type[.tuple] assert int.sizeof == __cpu_bits__ // 8 # Pointers a = 5 pointer_to_a = id(a) assert type(pointer_to_a) == id[int] b = pointer_to_a[0] # dereference pointer_to_a[0] := 10 # reassign # Polymorphic procedures def min[T](xs: list[T]) -> T: result = xs[0] # optional bounds checking for xs[1:]: # implicit names, slice syntax result := result if result < it else it return result # Overloadable procedures def min[T](*xs: tuple[T, ...]) -> T: return xs.list().min # Overloadable operators x: list[float, 4, 4] x @= x # calls __imatmul__(id(x), x) # Uniform call syntax assert x.min == min(x) """ Note: There are no methods in Ape. Every procedure defined in your scope can instead be called with dot syntax. The priority for x.y is defined as: 1. field y of x 2. call y[x] 3. call y(x) Use x.y() to choose y(x) over y[x]. """ # Lexical scoping q = 10 with: q = 12 finally: q := 13 # Deferred execution q := 14 assert q == 10 # Casting f = 5.0.(float[bits=32]) q := f.(int) assert q == 5 # Compiler as a library import compiler def main(): pass exe = compiler.compile( entry=main, os_target=.windows, cpu_target=.x64, exe_format=.pe64, ) assert exe.generated_object.len > 1024 # Batteries optional but included from basic import * print("Hello, world!") # Explicit allocators print(42, allocator=page_allocator) # or override for an entire block with heap_allocator as allocator: print("Hello,", "%".format(47)) """ Note: There is no garbage collector in Ape. All allocating procedures in basic are macros with a default `allocator=nonlocal allocator`. We implicitly imported `basic.allocator` which defaults to `basic.temp_allocator`. New users can remain oblivious to what this means outside of learning to call temp_allocator.free_all() every so often and can later graduate to more advanced allocation schemes. """ # Metaprogramming example from basic def format[fmt, T]( fmt: str, *args: T, # T.kind == .tuple allocator=nonlocal allocator, ) -> str[.dynamic] {macro=True}: b: str[.dynamic] b.allocator := allocator yield: i = argi = 0 tmp: str[.dynamic] while i < fmt.len: finally: i += 1 if fmt[i] == ord("%"): if i + 1 > fmt.len or fmt[i + 1] != ord("%"): yield if tmp.count > 0: b += yield tmp.str() yield: b += args[argi].str() argi += 1 continue i += 1 tmp += fmt[i] yield if tmp.count > 0: b += yield tmp.str() assert argi == args.len return b """ Note: `yield` flips back and forth between compile-time and run-time contexts, and tries to compute constants as much as possible. Because T is a tuple, its length must be known at compile time. If argi >= args.len in args[argi], a compile error will be emitted. """