The Atlas77 Programming Language
by Gipson62
This version of the documentation is currently only experimental and thoughts I gathered while developing the language. It may not reflect the current state of the language.
The documentation as a whole will be available offline in the compiler itself in future updates.
Introduction
Atlas77 is an experimental systems-style language with explicit ownership, deterministic cleanup, and a C-oriented toolchain.
This book is split into two tracks:
- Learn Atlas77: practical, step-by-step onboarding
- Language Reference: lower-level semantics and detailed behavior
If you are new, start with Getting Started and keep examples small.
Getting Started
This section gets you from zero to running code.
- Install the toolchain.
- Write a Hello World program.
- Run check and build commands repeatedly while you learn.
Next chapters:
- Installation
- Hello, World!
Installation
Atlas77 currently uses a Rust-based compiler frontend and emits C.
Typical prerequisites:
- Rust toolchain for atlas77 compiler builds
- A C compiler backend (tinycc, gcc, clang, or msvc)
Quick verification commands:
atlas77 --help
atlas77 check src/main.atlas
If check runs, your environment is ready for basic development.
Hello, World!
import "std/io";
fun main() {
println("Hello, Atlas77!");
}
Build with TinyCC for quick iteration:
atlas77 build src/main.atlas -c tinycc -o ./build
Programming A Guessing Game
This chapter is a guided exercise.
Goals:
- Read input from the terminal
- Parse and compare values
- Use loops and conditions
Minimal loop structure:
import "std/io";
fun main() {
let running: bool = true;
while running {
print("Guess: ");
let guess = input();
println(guess.c_str());
// add parse + compare logic here
break;
}
}
Learn Atlas77
This part of the book teaches Atlas77 through small, practical steps.
It is intentionally less technical than the full language manual. If you want implementation details (compiler passes, ownership internals, ABI notes), use the Atlas77 Language Manual chapter.
How to read this section:
- Start with Hello Atlas77.
- Follow chapters in order.
- Copy snippets into small files and run them often.
- Keep code simple until ownership and pointers feel natural.
You can always come back to the reference later.
Hello Atlas77
Atlas77 programs are built around a main function.
import "std/io";
fun main() {
println("Hello, Atlas77!");
}
Build And Run
For fast local iteration, start with TinyCC:
atlas77 build src/main.atlas -c tinycc -o ./build
For stricter checks with common system toolchains:
atlas77 build src/main.atlas -c gcc -r
atlas77 build src/main.atlas -c clang -r
To type-check without producing a final binary:
atlas77 check src/main.atlas
What To Remember
- Keep your first programs tiny.
- Compile frequently.
- Use check early to catch errors before code grows.
Core Syntax
This chapter covers the pieces you use constantly: values, functions, and structs.
Variables And Types
fun main() {
let age: int64 = 42;
let score: float64 = 99.5;
let active: bool = true;
let letter: char = 'A';
let name: *uint8 = "Atlas";
}
Functions
fun add(a: int64, b: int64) -> int64 {
return a + b;
}
fun greet(name: *uint8) -> unit {
println(name);
}
If a function does not return data, use unit.
Structs With Factory Functions
public struct User {
public:
name: *uint8;
level: int32;
fun new(name: *uint8, level: int32) -> User {
return User { .name = name, .level = level };
}
}
fun main() {
let u = User::new("Gip", 1);
println(u.name);
}
Common style in Atlas77 is Type::new(…) instead of constructor syntax.
Ownership Basics
Atlas77 uses ownership and deterministic cleanup.
You do not rely on a garbage collector.
Copy vs Move
Simple values are copied:
let a: int64 = 10;
let b: int64 = a; // copy
Resource-like values are usually moved:
let f = open_file_handle();
let g = move(f);
// f is invalid after move
If you use f after move, the compiler will reject it.
Destructors
Use a destructor when your type owns memory or a handle.
import "std/memory";
public struct Buffer {
private:
raw: *uint8;
public:
~Buffer() {
if this->raw != null {
free(this->raw);
}
}
}
The compiler inserts cleanup at scope boundaries so values are released deterministically.
Practical Rule
When in doubt:
- Decide who owns each resource.
- Move ownership only when needed.
- Add a destructor for owned resources.
Working With The Standard Library
Start with a small subset of std and use it everywhere:
- std/io for input/output
- std/string for owned text
- std/vector for growable arrays
- std/optional and std/expected for safe control flow
Input And Output
import "std/io";
fun main() {
print("Your name: ");
let name = input();
println(name.c_str());
}
String
import "std/string";
fun main() {
let base = String::from_chars("atlas");
let loud = base.to_upper();
println(loud.c_str());
}
Vector
import "std/vector";
fun main() {
let v = Vector<int64>::with_capacity(2);
v.push(1);
v.push(2);
v.push(3);
let last = v.pop();
}
optional And expected<T, E>
Use optional for “might be missing” values.
import "std/optional";
fun find_id(ok: bool) -> optional<int64> {
if ok {
return optional<int64>::of(42);
}
return optional<int64>::empty();
}
Use expected for “value or error” results.
import "std/expected";
fun parse_age(v: int64) -> expected<int64, *uint8> {
if v < 0 {
return expected<int64, *uint8>::unexpected("age cannot be negative");
}
return expected<int64, *uint8>::expect(v);
}
Generics And Function Pointers
You can stay productive in Atlas77 without these features at first. Learn them when your code starts repeating patterns.
Generics
fun pick_first<T>(a: T, _b: T) -> T {
return a;
}
fun main() {
let x = pick_first<int64>(100, 200);
}
Generics let you write one function for many types.
Function Pointers
This pattern is useful for callbacks and reusable behavior.
fun add(a: int32, b: int32) -> int32 {
return a + b;
}
fun apply_twice(fn: fun(int32, int32) -> int32, a: int32, b: int32) -> int32 {
return fn(a, b) + fn(b, a);
}
fun main() {
let add_fn: fun(int32, int32) -> int32 = add;
let r = apply_twice(add_fn, 4, 5);
}
Method pointers are also possible:
struct Foo {
public:
const_add_pointer: fun(*const Foo, int32) -> int32;
fun add_const(*const this, value: int32) -> int32 {
return value + 100;
}
}
fun main() {
let foo = Foo { .const_add_pointer = Foo::add_const };
let out = foo.const_add_pointer(&foo, 11);
}
C Interop And Project Shape
Atlas77 is designed to work with C libraries through extern declarations.
Declaring C Symbols
extern struct FILE {
handle: uint64;
}
extern fun printf<T>(fmt: *uint8, value: T);
fun main() {
printf("%s\n", "Hello from Atlas77");
}
Keep bindings small and test one function at a time.
Real App Shape (Raylib-Style)
The current raylib wrapper in this repository is prototype-level, but the app shape is useful:
fun main() {
InitWindow(800, 450, "demo");
SetTargetFPS(60);
while (!WindowShouldClose()) {
// update
// draw
}
CloseWindow();
}
This is a good default structure for games or interactive tools.
Toolchain Guidance
atlas77 build src/main.atlas -c tinycc -o ./build
atlas77 build src/main.atlas -c gcc -r --c-arg=-lraylib
- Use tinycc for quick iteration.
- Use gcc or clang for release checks and stricter diagnostics.
Language Reference
This section summarizes Atlas77 core syntax and concepts.
- Primitive types
- Functions and structs
- Pointers and ownership rules
- Generics and intrinsics
For a deeper technical document, see Atlas77 Language Manual.
Atlas77 v0.8.0 Language Manual
This manual documents Atlas77 as it exists in v0.8.0. It focuses on behavior that is visible in the compiler pipeline and in the current standard library implementation.
1. Language Fundamentals
1.1 Primitive Types
Atlas77 defines the following primitive scalar/value types in the language reference:
| Type | Meaning | Size |
|---|---|---|
| int8 | Signed integer | 1 byte |
| int16 | Signed integer | 2 bytes |
| int32 | Signed integer | 4 bytes |
| int64 | Signed integer | 8 bytes |
| uint8 | Unsigned integer | 1 byte |
| uint16 | Unsigned integer | 2 bytes |
| uint32 | Unsigned integer | 4 bytes |
| uint64 | Unsigned integer | 8 bytes |
| float32 | IEEE-like floating point | 4 bytes |
| float64 | IEEE-like floating point | 8 bytes |
| bool | true/false | 1 byte |
| char | Single Unicode code point | 4 bytes |
| string | UTF-8 byte string | Variable |
| unit | Empty value type | 0 bytes (semantic) |
Status note: the generated language-reference page also states that some primitive types are planned or not fully implemented yet. Treat compiler support as authoritative over table intent.
fun main() {
let a: int64 = 42;
let b: uint8 = 255;
let c: float64 = 3.14159;
let ok: bool = true;
let ch: char = 'A';
let s: string = "atlas";
}
1.2 Structs
Atlas77 structs are nominal types with fields and methods. In current code style, object creation is done with factory/static methods such as Type::new(…) or with struct literals.
public struct Person {
public:
name: string;
age: int32;
fun new(name: string, age: int32) -> Person {
return Person { .name = name, .age = age };
}
fun birthday(*this) {
this->age = this->age + 1;
}
}
fun main() {
let p = Person::new("Ada", 30);
p.birthday();
}
Member access uses . on values and -> through pointers to values.
1.3 Pointers
Atlas77 uses explicit raw pointers:
- *T for mutable pointer access
- *const T for immutable pointer access
fun read_first(ptr: *const int64) -> int64 {
return *ptr;
}
fun set_first(ptr: *int64, v: int64) {
*ptr = v;
}
NB: References are not yet implemented, but is a planned feature for future releases.
2. The Atlas77 Memory Model
Source of truth: ownership specification in docs/ownership_spec.md.
2.1 Value Semantics: Trivial vs Resource Types
Trivially copyable values can be copied implicitly. Resource values require deterministic drop and cannot be implicitly copied.
let x: int64 = 10;
let y: int64 = x; // copy is valid
let r: Resource = make_resource();
let r2: Resource = r; // invalid: non-trivial implicit copy
2.2 Ownership Transfer with move(x)
move(x) transfers ownership and invalidates x for later use.
let a = build_resource();
let b = move(a);
use(a); // invalid after move
Compiler diagnostics include moved/deleted/consumed and potentially-moved variants at branch joins.
2.3 Deterministic Cleanup and Destructors
The compiler inserts delete operations in HIR, and lowers to destructor calls (and free when needed) in generated C.
public struct FileHandle {
private:
raw: *uint8;
public:
~FileHandle() {
if this->raw != null {
free(this->raw);
}
}
}
Scope-exit cleanup, return-path cleanup, and reassignment pre-delete are handled by ownership/lifecycle passes.
3. Standard Library Reference
3.1 std/memory
Current low-level primitives:
- malloc
(size) - free
(ptr) - memcpy
(dest, src, size) - sizeof
() and alignof () wrappers - move
(from) intrinsic
import "std/memory";
fun main() {
let buf = malloc<uint8>(128);
// ... use buf
free(buf);
}
3.2 Vector
Vector
- push doubles capacity when full (0 -> 1 -> 2 -> 4 …)
- pop and take move values out
- destructor deletes elements in range [0, length)
import "std/vector";
fun main() {
let v = Vector<int64>::with_capacity(2);
v.push(10);
v.push(20);
v.push(30); // growth occurs
let last = v.pop();
let first = v.take(0);
}
Ownership note: element type T should be designed with Atlas77 drop semantics in mind, since vector cleanup calls delete on stored elements.
3.3 String
String in std/string is an owning byte-buffer wrapper over *uint8 with explicit len/capacity.
- UTF-8 bytes, length in bytes
- c_str() ensures trailing NUL and may reallocate
- with_capacity reserves space manually
import "std/string";
fun main() {
let s = String::from_chars("hello");
let t = s.to_upper();
let u = s.concat(&t);
print(u.c_str());
}
Implementation note: string behavior currently reflects pragmatic std internals, not a finalized immutable string design.
3.4 optional
optional
- optional
::of(value) - optional
::empty() - has_value(), is_empty(), value(), value_or(default)
import "std/optional";
fun maybe_even(n: int64) -> optional<int64> {
if (n % 2 == 0) {
return optional<int64>::of(n);
}
return optional<int64>::empty();
}
value() consumes and panics on empty.
3.5 expected<T, E>
expected<T, E> models success or error outcomes.
- expected<T, E>::expect(v)
- expected<T, E>::unexpected(e)
- is_expected(), is_unexpected()
- expected_value(), unexpected_value(), *_or(default)
import "std/expected";
fun parse_nonzero(v: int64) -> expected<int64, string> {
if (v == 0) {
return expected<int64, string>::unexpected("zero is not allowed");
}
return expected<int64, string>::expect(v);
}
3.6 io
std/io provides print, println, input, panic, printf plus FILE/Stdin interop-facing types.
import "std/io";
fun main() {
print("Name: ");
let name = input();
println(name.c_str());
}
3.7 Experimental shared_ptr
std/experimental/smart_ptr.atlas currently includes shared_ptr
- copy increments counter
- release decrements and deletes at zero
- no weak pointers
- no cycle handling
import "std/experimental/smart_ptr";
fun main() {
let p = shared_ptr<int64>::make(10);
let q = p.copy();
// both point to same rc_block
}
3.8 Raylib Wrapper State
Current raylib binding code is an extern-heavy prototype, not a finished package story:
- requires raylib.h and host linker setup
- mixes extern structs/functions with Atlas-side helpers
- demonstrates practical app loop structure
extern fun InitWindow(width: int32, height: int32, title: *const uint8);
extern fun WindowShouldClose() -> bool;
extern fun BeginDrawing();
extern fun EndDrawing();
fun main() {
InitWindow(800, 450, "demo");
while (!WindowShouldClose()) {
BeginDrawing();
EndDrawing();
}
}
Planned direction: expose library gating with a compiler flag before introducing a full build configuration format.
4. C Interoperability
4.1 Extern Functions and Structs
Use extern fun and extern struct to declare C ABI symbols.
extern struct FILE {
handle: uint64;
}
extern fun printf<T>(fmt: *uint8, value: T);
fun main() {
printf("%s\n", "hello from atlas77");
}
4.2 Atlas77 to C Pipeline
The compiler flow in libraries/lib.rs is:
- Parse source to AST.
- Lower AST to HIR.
- Monomorphize generics.
- Type check.
- Run ownership pass and inject delete semantics.
- Lower HIR to LIR.
- Emit C99 source and generated header in build/.
- Invoke selected C compiler backend.
The generated artifacts include:
- build/output.atlas
- build/output.atlas_lir
- build/output.atlas_c.c
- build/__atlas77_header.h
4.3 TCC vs GCC/Clang Workflow
CLI behavior from libraries/main.rs and libraries/lib.rs:
- Build defaults to TinyCC (tinycc or tcc)
- Release and debug are mutually exclusive CLI flags
- GCC/Clang/MSVC/Intel are selectable backends
- Extra linker/compiler flags are passed through with repeated –c-arg
Example commands:
atlas77 build src/main.atlas -c tinycc -o ./build
atlas77 build src/main.atlas -c gcc -r --c-arg=-lm --c-arg=-lraylib
atlas77 build src/main.atlas -c clang -r
atlas77 check src/main.atlas
Practical guidance:
- Use TinyCC for fast local iteration.
- Use GCC or Clang for stricter diagnostics and release optimization validation.
5. Advanced Features
5.1 Generics
Atlas77 supports generic structs and functions with explicit type parameters.
public struct Pair<T> {
public:
a: T;
b: T;
}
fun pick_first<T>(x: T, _y: T) -> T {
return x;
}
fun main() {
let p = Pair<int64> { .a = 1, .b = 2 };
let v = pick_first<int64>(p.a, p.b);
}
Current codebase also demonstrates generic constraints in library helpers (for example copy<T: std::copyable> in std/memory).
5.2 Function Pointers
Atlas77 supports typed function pointers, including static functions, generic function instances, and method pointers.
struct Foo {
public:
fp: fun(int32, int32) -> int32;
add_const_fp: fun(*const Foo, int32) -> int32;
fun add(a: int32, b: int32) -> int32 {
return a + b;
}
fun add_const(*const this, value: int32) -> int32 {
return value + 100;
}
}
fun pick_first<T>(a: T, _b: T) -> T {
return a;
}
fun main() {
let add_fn: fun(int32, int32) -> int32 = Foo::add;
let foo = Foo {
.fp = add_fn,
.add_const_fp = Foo::add_const,
};
let r1 = foo.fp(2, 3);
let r2 = foo.add_const_fp(&foo, 11);
let pick_i64: fun(int64, int64) -> int64 = pick_first<int64>;
let r3 = pick_i64(10, 20);
}
5.3 Intrinsics
Core intrinsics in std/memory:
- size_of
() - align_of
() - move
(value)
User-facing wrappers:
- sizeof
() - alignof
()
import "std/memory";
fun main() {
let s = sizeof<int64>();
let a = alignof<int64>();
let x = String::new();
let y = move(x);
// x is invalid after move
}
6. Real-World Application Structure
The raylib sample demonstrates a typical Atlas77 program skeleton for interactive apps:
- Extern declarations for host APIs.
- Domain structs/enums for runtime state.
- Initialization block.
- Main loop with update + draw phases.
- Deterministic shutdown.
fun main() {
InitWindow(800, 450, "2d camera");
SetTargetFPS(60);
while (!WindowShouldClose()) {
// update
// draw
}
CloseWindow();
}
This structure maps cleanly onto Atlas77 ownership rules because state lifetime is explicit and scoped.
7. Operational Notes and Caveats
- Atlas77 is under active development; not all documented types/features are equally mature.
- Standard library modules are practical and evolving, not frozen ABI contracts.
- Pointer-heavy code must be audited manually; ownership pass does not replace C interop discipline.
- Use check and multiple C backends in CI to catch backend-specific issues early.
Memory Model
Atlas77 uses explicit ownership and deterministic destruction.
Core ideas:
- Trivial values are copyable.
- Resource values are moved.
- move(x) invalidates the source value.
- Cleanup is inserted by compiler passes and lowered to generated C code.
let a = make_resource();
let b = move(a);
// a is invalid after move
Error Handling
Atlas77 standard style favors explicit result types.
Use optional
import "std/optional";
fun find_id(ok: bool) -> optional<int64> {
if ok { return optional<int64>::of(1); }
return optional<int64>::empty();
}
Use expected<T, E> for value-or-error outcomes:
import "std/expected";
fun parse(x: int64) -> expected<int64, string> {
if x < 0 { return expected<int64, string>::unexpected("negative"); }
return expected<int64, string>::expect(x);
}
Reserved Keywords
This page tracks language keywords reserved by the parser and semantic passes.
Common keywords in examples include:
- fun
- struct
- extern
- import
- public
- private
- return
- if
- else
- while
- let
- const
Use descriptive identifiers and avoid reusing keyword names as variables.
Generics
Generics let you write reusable code for multiple types.
public struct Pair<T> {
public:
left: T;
right: T;
}
fun pick_first<T>(a: T, _b: T) -> T {
return a;
}
Monomorphization happens in the compiler pipeline before type checking and lowering to C.
Standard Library
Note:
The standard library is a work in progress. Some modules are complete while others are still in development. This documentation reflects the current state of the implemented modules.
Overview
The Atlas77 standard library provides essential data structures, utilities, and I/O functions. The library is organized into modules that can be imported individually.
Each module is now documented on its own page with complete API reference, usage examples, and best practices.
std/io
Module for terminal I/O and panic/printf interop helpers.
import "std/io";
fun main() {
print("Name: ");
let name = input();
println(name.c_str());
}
Key APIs:
- println
- input
- panic
- printf
std/string
String is an owning UTF-8 byte-buffer abstraction.
import "std/string";
fun main() {
let s = String::from_chars("atlas");
let up = s.to_upper();
println(up.c_str());
}
Useful methods:
- String::new
- String::from_chars
- len
- trim
- to_upper / to_lower
- concat
- c_str
std/vector
Vector
import "std/vector";
fun main() {
let v = Vector<int64>::with_capacity(1);
v.push(10);
v.push(20);
let x = v.pop();
}
Current behavior:
- Tracks length and capacity
- Grows by doubling capacity
- Deletes elements on destructor
std/optional
optional
import "std/optional";
fun maybe_value(ok: bool) -> optional<int64> {
if ok { return optional<int64>::of(42); }
return optional<int64>::empty();
}
Key APIs:
- of
- empty
- has_value
- is_empty
- value
- value_or
std/expected
expected<T, E> represents success (T) or error (E).
import "std/expected";
fun parse_age(age: int64) -> expected<int64, string> {
if age < 0 {
return expected<int64, string>::unexpected("invalid age");
}
return expected<int64, string>::expect(age);
}
Key APIs:
- expect
- unexpected
- is_expected
- is_unexpected
- expected_value / unexpected_value
- expected_value_or / unexpected_value_or
std/mem
Memory helpers and low-level intrinsics.
import "std/memory";
fun main() {
let p = malloc<uint8>(64);
free(p);
}
Core APIs:
- malloc
- free
- memcpy
- sizeof / size_of
- alignof / align_of
- move
Blue Engine
Blue Engine bindings in this repository are experimental.
Current state:
- Binding prototypes exist in libraries/blue_engine
- API and integration details are still evolving
- Expect breaking changes while language/runtime features stabilize
Roadmap
Near-term priorities include:
- Stabilize ownership and lifecycle behavior
- Improve std module completeness
- Strengthen build/toolchain ergonomics
- Expand documentation with examples over specifications
Long-term features are tracked in repository planning docs and issues.