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
Welcome to The Atlas77 Programming Language, an experimental book about the Atlas77 programming language. Experimental because Iβm trying to gather and lay down my thoughts about the language as I develop it to normalize the language in itself.
Atlas77 is a statically typed, compiled programming language running on a VM that I do in my free time. I hope to be able to use it for future projects. Donβt expect it to be production-ready anytime soon.
I would describe it as a modern take of C++. And for that it takes inspiration from many languages like:
- Rust/C++/C# for the syntax
- Java/CPython for the runtime
Goals
- Having a
core::graphicsmodule that links to Vulkan/OpenGL/DirectX. - Having a
core::sdlmodule that links to SDL. - Bootstrapping the compiler.
- Having a
core::ffimodule that allows interfacing with C libraries. - Having a
core::ffimodule that allows interfacing with Rust libraries. - Making a game with it.
- Linking to blue_engine (see blue_engine documentation).
- It should work π
Getting Started with Atlas 77
Welcome to Atlas 77! This guide will help you install the language and write your first program.
Your First Program
Here is a simple βHello, Atlas!β program:
import "std/io";
fun main() {
println("Hello, Atlas!");
}
Save this code to a .atlas file, then run it with:
atlas_77 run <FILE_PATH>
For example:
atlas_77 run hello.atlas
Creating a Project
To create a new Atlas 77 project with standard structure:
atlas_77 init my_project
cd my_project
atlas_77 run
This creates a basic project template with all necessary files.
Next Steps
- Read Hello, World! for a detailed walkthrough
- Explore Language Reference for complete syntax documentation
- Try Guessing Game for a practical example
- Check Memory Model to understand ownership and cleanup
- Browse Standard Library for available modules and functions
Comments
Comments document code and are ignored by the compiler.
Single-line Comments
Single-line comments start with // and continue to the end of the line:
// This is a comment
let x: int64 = 42; // Comments can appear at the end of lines too
Note: Multi-line comments are not yet implemented.
In the future Iβll add support for documentation comments, though the syntax is not yet decided.
4. Variables
Variables in Atlas77 are either mutable or immutable. The design follows in some sense TypeScript/JavaScript, with the
const & let keywords. Variables can be declared using the let keyword, which creates a mutable variable, or the
const keyword, which creates an immutable variable.
import "std/io";
fun main() -> int64 {
let x: int64 = 5;
x = 10;
print(x); // Output: 10
const y: int64 = 5;
y = 10; // Error: Cannot assign to a constant variable
}
5. Data Types
Atlas77 has several built-in data types, including integers, floating-point numbers, booleans, strings, and arrays. The following table lists the built-in data types in Atlas77:
| Data Type | Description | State |
|---|---|---|
int8 | 8-bit signed integer | π€ |
int16 | 16-bit signed integer | π€ |
int32 | 32-bit signed integer | π€ |
int64 | 64-bit signed integer | β |
isize | Platform-dependent signed integer | π€ |
uint8 | 8-bit unsigned integer | π€ |
uint16 | 16-bit unsigned integer | π€ |
uint32 | 32-bit unsigned integer | π€ |
uint64 | 64-bit unsigned integer | β |
usize | Platform-dependent unsigned integer | π€ |
float32 | 32-bit floating-point number | π€ |
float64 | 64-bit floating-point number | β |
bool | Boolean value (true or false) | β |
char | Unicode character | β |
string | String | β |
array | Array (syntax: [YourType]) | π |
6. Functions
Functions in Atlas77 are defined using the func keyword, followed by the function name, parameters, return type, and
body. The return type of a function is specified after the -> symbol. For example:
import "std/io";
fun add(x: int64, y: int64) -> int64{
return x + y;
}
fun main() -> int64 {
let result: int64 = add(5, 10);
print(result); // Output: 15
}
7. Control Structures
Atlas77 supports several control structures, including if statements, match expression, while loops, and for
loops. The syntax for these control structures is similar to other programming languages. For example:
| Control Structure | Description | State |
|---|---|---|
if statement | Conditional statement | β |
match expression | Pattern matching expression | π€ |
while loop | Loop with a condition | β |
for loop | Loop over a range or collection | π€ |
import "std/io";
fun main() -> int64 {
let x = 5;
if x > 0 {
print("x is positive");
} else if x < 0 {
print("x is negative");
} else {
print("x is zero");
}
let i = 0;
while i < 5 {
print(i);
i += 1;
}
}
8. The standard library
Atlas77 comes with a relatively small standard library, which includes functions & types for input/output, file
handling, string & list manipulation, time & math functions. The standard library is imported using the import
keyword, followed by the library name. For example:
import "std/io";
fun main() {
println("Hello, World!");
}
Check out the current state of the standard library.
9. Arrays
Arrays in Atlas77 are used to store multiple values of the same type. They are defined using square brackets []. For example:
import "std/io";
fun main() -> int64 {
let numbers: [int64] = [1, 2, 3, 4, 5];
let i = 0;
while i < 5 {
print(numbers[i]);
i += 1;
}
}
If you want, you can also allocate an empty array with a specific size:
import "std/io";
fun main() -> int64 {
let size: int64 = 5;
// Allocates an array of 5 int64s initialized to 0
let numbers: [int64] = new [int64; size];
let i = 0;
while i < size {
numbers[i] = i * 2; // Assign values
print(numbers[i]);
i += 1;
}
}
10. Enums
Enums in Atlas77 are used to define a type that can have a set of named values. They are defined using the enum keyword. For example:
public enum Color {
Red = 1;
Yellow;
Green = 3;
Purple;
Blue = 5;
}
11. Class & Structs
WIP
Current state of std/fs would be a good enough example of how classes/structs work in Atlas77:
private extern read_dir(path: string) -> [string];
private extern read_file(path: string) -> string;
private extern write_file(path: string, content: string);
private extern remove_file(path: string);
private extern file_exists(path: string) -> bool;
private extern close_file(path: string);
//NB: This struct works for now, but because of the lack of move/copy semantics in Atlas,
// it may lead to unexpected behavior.
public struct File {
private:
content: string;
public:
path: string;
public:
/// Creates a new File object with the given path
/// Note: The file is not opened until the open() method is called
File(path: string) {
this.content = "";
this.path = path;
}
~File() {
//Following the RAII pattern, we close the file when it goes out of scope
this.close();
}
fun read(this) -> string {
let content = read_file(this.path);
this.content = content;
return this.content;
}
fun open(this) {
this.content = read_file(this.path);
return;
}
fun close(this) {
close_file(this.path);
return;
}
fun write(this, content: string) {
write_file(this.path, content);
return;
}
fun remove(this) {
remove_file(this.path);
return;
}
fun exists(this) -> bool {
return file_exists(this.path);
}
fun read_dir(this, path: string) -> [string] {
return read_dir(path);
}
fun read_file(this, path: string) -> string {
return read_file(path);
}
}
12. Generics
As of now you can define generic for external functions & structs. For example:
extern identity<T>(value: T) -> T;
struct Box<T> {
value: T;
Box(value: T) {
self.value = value;
}
fun get_value(this) -> T {
return this.value;
}
}
13. Concepts
The name is still to be decided
Installation
Prerequisites
- Rust Compiler
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
NB: This is only for Unix-based systems (or WSL for Windows). For Windows, you can use Scoop
scoop install main/rust
Or directly from their website: Rust
Installation
Once Rust is installed, you can install Atlas77 using Cargo.
- Install it from Cargo
cargo install atlas_77 - Use it as a CLI
atlas_77 --help - Enjoy!
Or you can clone the repository and build it yourself.
- Clone the repository
git clone https://github.com/atlas77-lang/atlas77.git && cd atlas77 - Build it
cargo build --release - Use it as a CLI
./target/release/atlas_77 --help
Hello, World!
Programming a Guessing Game in Atlas77
Setting up
First create a new Atlas77 project:
atlas77 init guessing_game
cd guessing_game
Then open the src/main.atlas file in your favorite text editor.
Writing the Game Code
Language Reference
This document provides a comprehensive reference for Atlas 77 syntax, semantics, and language features.
Syntax Overview
Comments
Atlas 77 supports single-line comments only:
// This is a single-line comment
let x: int64 = 42; // Comments can appear at end of line
Note
Multi-line comments (
/* ... */) are not yet implemented.
Keywords
Reserved keywords in Atlas 77 include: let, const, fun, struct, import, return, if, else, while, for, new, delete, public, private, this, and others.
Refer to Reserved Keywords for the complete list.
Variables and Constants
Variables are declared with let and constants with const. Type annotations are mandatory:
let x: int64 = 42;
let name: string = "Atlas";
const PI: float64 = 3.14159;
// Type inference is limited; explicit types are required
let result = 10; // Types can be inferred for initialized variables.
Types
Atlas 77 is statically typed and strongly typed. Here are the fundamental types:
Primitive Types
| Type | Description | Size |
|---|---|---|
int8 | Signed 8-bit integer | 1 byte |
int16 | Signed 16-bit integer | 2 bytes |
int32 | Signed 32-bit integer | 4 bytes |
int64 | Signed 64-bit integer | 8 bytes |
uint8 | Unsigned 8-bit integer | 1 byte |
uint16 | Unsigned 16-bit integer | 2 bytes |
uint32 | Unsigned 32-bit integer | 4 bytes |
uint64 | Unsigned 64-bit integer | 8 bytes |
float32 | 32-bit floating point | 4 bytes |
float64 | 64-bit floating point | 8 bytes |
bool | Boolean (true / false) | 1 byte |
char | Single Unicode character | 4 bytes |
string | UTF-8 string (length in bytes) | Variable |
unit | Empty type (like void) | β |
Strings
Strings are UTF-8 encoded and their length is measured in bytes, not characters:
let greeting: string = "Hello, Atlas!";
Note
String type is currently temporary and may change in future versions.
Arrays
Arrays have fixed length known at compile-time:
let numbers: [int64] = [1, 2, 3, 4, 5];
let items: [string] = ["apple", "banana", "cherry"];
Generics
Atlas 77 supports parametric types (generics) with explicit type parameters (no type inference):
struct Box<T> {
public:
value: T;
Box(val: T) {
this.value = val;
}
}
fun get_value<T>(box: Box<T>) -> T {
return box.value;
}
let int_box: Box<int64> = new Box<int64>(42);
let value: int64 = get_value::<int64>(int_box);
Type parameters must be explicitly specified at call sites. For more details, see Generics.
Functions
Functions are declared with fun keyword and require explicit return types:
fun add(a: int64, b: int64) -> int64 {
return a + b;
}
fun greet(name: string) -> unit {
println("Hello, " + name);
}
// Functions that return nothing use return type 'unit'
fun perform_action() -> unit {
// No return statement needed for unit
}
Return Type Specification
Return types are mandatory for all functions. Use unit for functions that donβt return a value.
Operators and Precedence
Atlas 77 uses operator precedence similar to C/C++.
Operator Precedence Table
Listed from highest to lowest precedence:
| Precedence | Operator | Associativity | Description |
|---|---|---|---|
| 1 | () [] . | Left-to-right | Function call, array index, member access |
| 2 | ! - + ~ | Right-to-left | Logical NOT, unary minus, unary plus, bitwise NOT |
| 3 | * / % | Left-to-right | Multiplication, division, modulo |
| 4 | + - | Left-to-right | Addition, subtraction |
| 5 | << >> | Left-to-right | Bitwise shift left, shift right |
| 6 | < <= > >= | Left-to-right | Relational operators |
| 7 | == != | Left-to-right | Equality operators |
| 8 | & | Left-to-right | Bitwise AND |
| 9 | ^ | Left-to-right | Bitwise XOR |
| 10 | | | Left-to-right | Bitwise OR |
| 11 | && | Left-to-right | Logical AND |
| 12 | || | Left-to-right | Logical OR |
| 13 | = += -= *= /= etc. | Right-to-left | Assignment operators |
Note
Operator overloading is planned for a future version. Some operators have not been implemented yet.
Structs
Structs are product types that group related data:
struct Person {
private:
age: int32;
public:
name: string;
email: string;
Person(name: string, email: string, age: int32) {
this.name = name;
this.email = email;
this.age = age;
}
fun display(this: Person) -> unit {
println(this.name);
}
}
Visibility Rules
By default, all struct fields and methods are private. To make fields or methods public, precede them with a public: block:
struct Config {
private:
internal_state: int64;
public:
api_key: string;
timeout: int32;
}
- Only
api_keyandtimeoutare accessible outside the struct internal_stateis private and only accessible within struct methods
Methods
Methods are functions associated with a struct. The receiver (this) is passed explicitly:
fun display(this) -> unit {
println(this.name);
}
References
References allow you to work with values without taking ownership. References are not nullable.
Warning
Reference design is still a work in progress. There is no guarantee that the current design will remain stable.
Reference Syntax
let x: int64 = 42;
let mutable_ref: &int64 = &x; // Mutable reference
let immutable_ref: &const int64 = &const x; // Immutable reference
Reference Behavior
- References are trivially copyable and are copied implicitly
- References are not rebindable (may change in future versions)
- References cannot be null; all references point to valid values
- Taking a reference:
&my_var(mutable),&const my_var(immutable)
Copy and Move Semantics
Copy Semantics (Implicit)
Primitive types and references are implicitly copyable:
let a: int64 = 10;
let b: int64 = a; // 'a' is copied; both 'a' and 'b' exist
let ref_a: &int64 = &a;
let ref_b: &int64 = ref_a; // Reference is copied
Move Semantics (Default for Custom Types)
For custom structs, values are moved by default unless the struct implements a Copy constructor:
struct Resource {
public:
data: string;
}
let r1: Resource = Resource("data");
let r2: Resource = r1; // 'r1' is moved to 'r2'; 'r1' no longer accessible
// Using 'r1' here would be a compile error
Copy Constructor (Opt-in)
To make a custom type copyable, implement a Copy constructor:
struct CopyableData {
public:
value: int64;
CopyableData(val: int64) {
this.value = val;
}
// Copy constructor (opt-in copyability)
fun Copy(this: CopyableData) -> CopyableData {
return CopyableData(this.value);
}
}
let d1: CopyableData = CopyableData(100);
let d2: CopyableData = d1; // Now 'd1' is copied, both exist
[!Warning] The copyability of all standard library types is uncertain. Treat standard library types as potentially non-copyable until verified.
Memory Management
Atlas 77 uses manual memory management with automatic cleanup at scope boundaries.
Allocation and Deallocation
// Allocate memory with 'new'
let ptr: &MyStruct = &new MyStruct(...);
// Deallocate explicitly with 'delete'
delete ptr;
Automatic Deallocation (RAII)
The compiler automatically inserts delete instructions at the end of each scope for variables that havenβt been moved:
fun example() -> unit {
let resource: MyStruct = MyStruct();
// ... use resource ...
} // Compiler automatically calls delete on 'resource' here
Destructors
When a value is deleted, its destructor is called before memory is freed. Destructors clean up resources:
struct File {
path: string;
// Destructor: called by delete
fun ~File(this: File) -> unit {
// Close file, cleanup resources
}
}
Note
The current runtime is marked as deprecated. Precise semantics of destructors and scope-based cleanup may evolve.
Error Handling
Atlas 77 provides Option<T> and Result<T, E> types for error handling. There is no implicit error propagation (no try/? operator); handle errors explicitly.
Option Type
struct Option<T> {
public:
has_value: bool;
value: T; // Only valid if has_value is true
}
fun find_user(id: int64) -> Option<User> {
if id > 0 {
return Option(true, User(id));
} else {
return Option(false, User(0));
}
}
// Using Option
let user_opt: Option<User> = find_user(1);
if user_opt.has_value {
println("Found user");
} else {
println("User not found");
}
Result Type
struct Result<T, E> {
public:
is_ok: bool;
ok_value: T; // Only valid if is_ok is true
err_value: E; // Only valid if is_ok is false
}
fun parse_int(s: string) -> Result<int64, string> {
// Try to parse
if success {
return Result(true, parsed_value, "");
} else {
return Result(false, 0, "Failed to parse");
}
}
// Using Result
let result: Result<int64, string> = parse_int("42");
if result.is_ok {
let number: int64 = result.ok_value;
println("Parsed: " + number);
} else {
println("Error: " + result.err_value);
}
Unwrapping
let result: Result<int64, string> = parse_int("42");
let number: int64 = result.ok_value; // Unwrap: use value directly (risky!)
Note
There is no automatic error propagation syntax (
try/?operator) by design to maintain explicit control and avoid βhidden magicβ in error handling.
Modules and Imports
Current Import System
Use the import statement to include modules:
import "std/io";
import "std/fs";
import "std/string";
fun main() -> unit {
println("Hello, Atlas!");
}
Imports use file paths (not yet package-based). The compiler locates module files relative to the standard library.
Warning
Multiple imports of the same module may result in duplication even with guard mechanisms. This is a known limitation of the current system.
Future: Package-Based Imports
Package-based imports and selective imports are planned for after the compiler bootstrap phase. Expected syntax:
// Planned (not yet available):
// import mypackage::io;
// from mypackage import io, fs;
Control Flow
If-Else
if condition {
// ...
} else if other_condition {
// ...
} else {
// ...
}
While Loops
while x < 10 {
x = x + 1;
}
For Loops (Basic)
Basic for loops are currently supported:
for i in 0..10 { // Range iteration is planned
println(i);
}
Note
Range-based for-loops (
for x in range) are planned for a future version.
Planned Features
The following features are actively developed and planned for future releases:
- Operator Overloading β Define custom behavior for operators like
+,-,*, etc. - For-loops over Ranges β Iterate over ranges with syntax like
for i in 0..10 - Pattern Matching β Destructure and match values
- First-class Functions and Closures β Functions as values, anonymous functions, closures
- Async/Await β Asynchronous programming support
- Traits (or βConceptsβ) β Define shared behavior across types
- Union Types β Discriminated unions for variant data
- Package System β Dependency management and package organization
- Package Manager β Tool for downloading and managing packages
- Copy & Move Semantics Refinement β Better documentation and ergonomics
- Dead Code Elimination β Optimization pass to remove unused code
- Cranelift Backend β Native code generation (in addition to VM execution)
- Compiler Error Recovery β Better error messages and recovery from multiple errors
Known Limitations and Footguns
- No multiline comments β Only single-line comments with
// - No pattern matching β Cannot destructure values into patterns
- No break/continue β Loop control statements not yet implemented
- No variadics β Variable argument functions not supported
- No named parameters β All parameters are positional
- No inheritance β Structs use composition instead of inheritance
- No type inference β All type annotations must be explicit
- String type is temporary β May change in future versions
- stdlib copyability uncertain β Assume standard library types are non-copyable
For detailed examples and tutorials, see:
Memory Model
Atlas 77 employs manual memory management with automatic scope-based cleanup. This document explains how memory allocation, deallocation, and ownership work.
Allocation with new
Memory is allocated using the new keyword:
struct Person {
public:
name: string;
age: int32;
Person(name: string, age: int32) {
this.name = name;
this.age = age;
}
}
let person: &Person = &new Person("Alice", 30);
Deallocation with delete
Memory is freed using the delete keyword:
delete person; // Free memory and call destructor
The delete operation:
- Calls the destructor (if one exists)
- Frees the allocated memory
- Invalidates the reference
Automatic Scope-Based Cleanup (RAII)
The compiler automatically inserts delete instructions at the end of each scope for variables that havenβt been moved yet:
fun process_file() -> unit {
let file: File = File("data.txt");
// ... use file ...
} // Compiler automatically calls: delete file;
This pattern is called RAII (Resource Acquisition Is Initialization):
- Resources are acquired during object construction (
new) - Resources are released automatically at scope exit via destructors
Example: File Resource Management
struct File {
path: string;
is_open: bool;
// Constructor acquires resource
File(path: string) {
this.path = path;
this.is_open = true;
// Open file
}
// Destructor releases resource
fun ~File(this: File) -> unit {
if this.is_open {
// Close file
this.is_open = false;
}
}
}
fun read_and_process() -> unit {
let file: File = File("input.txt");
let content: string = file.read();
// ...
} // File destructor called here; file is automatically closed
Ownership and Move Semantics
Atlas 77 uses move semantics by default for custom types. When you assign a value, ownership transfers to the new variable:
struct Box<T> {
public:
value: T;
}
let box1: Box<int64> = Box(42);
let box2: Box<int64> = box1; // Ownership moves from box1 to box2
// box1 is no longer accessible; moved values cannot be used
// println(box1.value); // ERROR: box1 has been moved
Once a value is moved, the original variable becomes inaccessible. This prevents use-after-free bugs:
fun take_ownership(b: Box<int64>) -> unit {
println(b.value);
// b is deleted at end of scope
}
let box: Box<int64> = Box(100);
take_ownership(box); // Ownership transfers to function
// println(box.value); // ERROR: box has been moved
Copy Semantics
Implicit Copy (Primitive Types and References)
Primitive types and references are implicitly copyable:
let x: int64 = 42;
let y: int64 = x; // 'x' is copied; both x and y exist
let ref_x: &int64 = &x;
let ref_y: &int64 = ref_x; // Reference is copied
Opt-In Copy (Custom Types)
To make a custom type copyable, implement a Copy constructor:
struct MyData {
public:
value: int64;
MyData(val: int64) {
this.value = val;
}
// Copy constructor
fun Copy(this: MyData) -> MyData {
return MyData(this.value);
}
}
let data1: MyData = MyData(100);
let data2: MyData = data1; // Now data1 is copied; both data1 and data2 exist
Without the Copy constructor, assignment moves instead of copying.
References
References allow borrowing values without transferring ownership. References in Atlas 77 are:
- Not nullable β always point to valid values
- Trivially copyable β copying is implicit and cheap
- Not rebindable β may change in future versions
Reference Syntax
let value: int64 = 42;
// Mutable reference
let mutable_ref: &int64 = &value;
// Immutable reference
let immutable_ref: &const int64 = &const value;
Using References
fun modify_value(ref: &int64) -> unit {
*ref = 100; // Dereference and modify
}
let x: int64 = 42;
modify_value(&x);
println(x); // 100
Warning
Reference design is still evolving and heavily inspired by Rust. Current behavior and semantics may change.
Destructors
Destructors are special methods that clean up resources when an object is deleted:
struct Resource {
public:
ptr: int64; // Some resource pointer
Resource() {
// Acquire resource
this.ptr = allocate();
}
// Destructor: called by delete
fun ~Resource(this: Resource) -> unit {
if this.ptr != 0 {
deallocate(this.ptr);
this.ptr = 0;
}
}
}
Destructors are called:
- When
deleteis explicitly called - When scope exits (automatic cleanup)
- In the correct order for nested scopes
Destructor Execution Order
For scoped values, destructors are called in reverse order of construction (LIFO):
fun example() -> unit {
let a: Resource = Resource(); // Constructed first
let b: Resource = Resource(); // Constructed second
// ...
} // Destructors called: ~b, then ~a (reverse order)
Scope-Based Cleanup Rules
- Single scope: Variables deleted at end of scope
- Early returns: Variables deleted before returning
- Moved values: Not deleted (ownership transferred)
- Conditional scopes: Variables deleted when block exits
fun conditional_cleanup(flag: bool) -> unit {
let resource1: Resource = Resource();
if flag {
let resource2: Resource = Resource();
// resource2 deleted here
}
// resource1 deleted here
}
Lifetime and Validity
References must remain valid for their entire lifetime:
fun create_ref() -> &int64 {
let x: int64 = 42;
return &x; // ERROR: x will be deleted at end of scope
}
This is a common pitfall. Values cannot outlive their owners:
fun valid_reference(x: &int64) -> &int64 {
return x; // OK: reference comes from parameter, guaranteed to be valid
}
Current Limitations
- Runtime is deprecated β Precise semantics may evolve
- Destructor semantics uncertain β Copy/move interaction with destructors being refined
- stdlib copyability uncertain β Assume standard library types are non-copyable unless documented
Future Improvements
- Smart Pointers (
rc_ptr<T>) β Reference-counted pointers for shared ownership - Guaranteed Copy Constructor Optimization β Automatic elision of unnecessary copies
- Move Semantics Refinement β Clearer rules for implicit copying vs. moving
See Language Reference for details on copy/move semantics.
Error Handling in Atlas 77
Atlas 77 provides explicit error handling through Option<T> and Result<T, E> types. There is no implicit error propagation; errors must be handled explicitly to avoid βhidden magic.β
Philosophy
Error handling in Atlas 77 is explicit and intentional:
- No automatic exception throwing or catching
- No implicit
try/?operator for error propagation - Errors must be checked and handled at the point they occur
- This gives you maximum control over error recovery
Option Type
The Option<T> type represents a value that may or may not exist:
struct Option<T> {
public:
has_value: bool;
value: T; // Only valid when has_value is true
}
Use Option<T> for fallible operations that donβt need error details:
fun find_user(id: int64) -> Option<User> {
if id > 0 {
// Found user
let user: User = User(id);
return Option(true, user);
} else {
// Not found
return Option(false, User(0)); // Default user
}
}
Checking Option Values
let user_opt: Option<User> = find_user(42);
// Check if value exists
if user_opt.is_some() {
let user: User = user_opt.unwrap();
println("Found user: " + user.name);
} else {
println("User not found");
}
Result Type
The Result<T, E> type represents either a success with a value, or a failure with an error:
enum ResultTag {
ERR = 0;
OK = 256;
}
struct Result<T, E> {
private:
tag: ResultTag;
ok_value: T;
err_value: E;
}
Use Result<T, E> when operations can fail and you need to communicate why:
fun parse_int(s: string) -> Result<int64, string> {
// Try to parse string to integer
if is_valid_number(s) {
let number: int64 = convert_to_int(s);
return Result::<int64, string>::ok(number);
} else {
return Result::<int64, string>::err("Invalid number format");
}
}
Checking Result Values
let result: Result<int64, string> = parse_int("42");
// Explicit check with is_ok
if result.is_ok() {
let number: int64 = result.unwrap();
println("Parsed: " + number);
} else {
let error: string = result.unwrap_err();
println("Error: " + error);
}
Unwrapping Results
If youβre confident a Result is Ok, you can unwrap directly (at your own risk):
let result: Result<int64, string> = parse_int("42");
let number: int64 = result.unwrap(); // Unwrap without check (risky!)
// If is_ok is false, you'll get a default/undefined value
// Use only when you're certain the operation succeeded
Warning: Unwrapping without checking can lead to using garbage values if the operation failed. Always check
is_okfirst unless you have a strong reason not to.
Panic
For unrecoverable errors, use panic() to abort the program:
import "std/io";
fun critical_operation() {
if invalid_state {
panic("Critical error: invalid state detected!");
}
}
panic() will:
- Print the error message
- Terminate the program immediately
- Bypass normal cleanup (use sparingly)
Pattern Examples
Option Handling
struct User {
public:
id: int64;
name: string;
}
fun get_user_name(id: int64) -> Option<string> {
let user: Option<User> = find_user(id);
if user.is_some() {
return Option::<string>::ok(user.unwrap().name);
} else {
return Option::<string>::none();
}
}
// Usage
let name_opt: Option<string> = get_user_name(1);
if name_opt.is_some() {
print("User: ");
println(name_opt.unwrap().value);
} else {
println("User not found");
}
Result Chaining
When multiple fallible operations depend on each other:
fun read_and_parse() -> Result<int64, string> {
// First operation
let content: Result<string, string> = read_file("numbers.txt");
if !content.is_ok() {
return Result::<int64, string>::err(content.unwrap_err());
}
// Second operation (depends on first)
let number: Result<int64, string> = parse_int(content.ok_value);
if !number.is_ok() {
return Result::<int64, string>::err(number.unwrap_err());
}
// Success
return Result::<int64, string>::ok(number.unwrap());
}
Nested Results with Meaningful Errors
struct ParseError {
public:
line: int64;
column: int64;
message: string;
}
fun parse_config(path: string) -> Result<Config, ParseError> {
// Try to read file
let content: Result<string, string> = read_file(path);
if !content.is_ok() {
let error: ParseError = new ParseError(0, 0, content.unwrap_err());
return Result::<Config, ParseError>::err(error);
}
// Try to parse content
let config: Result<Config, ParseError> = parse_config_text(content.unwrap());
return config;
}
Best Practices
- Check explicitly β Always use
is_ok()/is_err()checks before accessing values - Provide context β Include helpful error information in
Result<T, E> - Fail fast β Return errors early rather than propagating invalid states
- Use Option for simple cases β When you only need to know if something exists
- Use Result for complex cases β When you need to communicate failure reasons
- Avoid panics in libraries β Let caller decide how to handle errors
- Document error cases β Explain what errors an operation can produce
Future Improvements
In the future, Atlas 77 may introduce:
- Discriminated unions for more flexible error types
- Error traits for better composability
However, the core design will remain explicit and visible, avoiding hidden control flow.
See also:
- Language Reference β Error handling in context
- Memory Model β Resource safety
- Standard Library β Available error types
Reserved Keywords
List of all of the reserved keywords in the language:
Branching and control flow
if/elsewhile/forbreak/continuereturn
Declarations
public/private(access modifiers for declarations)import(for importing modules/packages)package(for defining packages/modules)fun(for defining functions)let/const(for defining variables)struct(for defining structures)enum(for defining enumerations)class(for defining classes)
Not used yet, but reserved in case of future use.
concept(for defining concepts which are like interfaces)
Not used yet, but reserved because itβs a feature planned for the future
extern(for defining external functions/variables)
Primitives and types
Primitives types are considered to be keywords in Atlas 77, though this might change in the future.
string(for string type)char(for character type)int8/int16/int32/int64(for signed integer types)uint8/uint16/uint32/uint64(for unsigned integer types)float32/float64(for floating-point types)bool(for boolean type)unit(for unit type)true/false(for boolean literals)This/this(for referring to the current instance in classes/structs)
Other keywords
new/delete(for memory management)as(for type casting)comptime(for compile-time evaluation)
Not used yet, but reserved in case of future use.
operator(for operator overloading)
Not used yet, but reserved in case of future use.
Standard Library
Note
All the standard library is a work in progress. Most of the modules arenβt implement or finished yet. The documentation is here to give an idea of what the standard library will look like.
| Module | Description |
|---|---|
std/io | Input/output functions |
std/fs | File handling functions |
std/string | String manipulation functions |
std/vector | Vector manipulation functions |
std/time | Time functions |
std/math | Math functions |
std/option | Option type and functions |
std/result | Result type and functions |
std/box | Box type and functions |
std/iter | Iterator type and functions |
std/map | Map type and functions |
You can see what is implemented with the checkboxes below.
std/io
-
print<T>(val: T) -> unit: Print a value to the standard output. -
println<T>(val: T) -> unit: Print a value to the standard output followed by a newline. -
input() -> str: Read a line from the standard input. -
panic(msg: string) -> !: Abort the program with an error message.
std/fs
Structs
-
File: A file type that can be used to read and write files.
NB:
Fileis not stable yet. BEWARE.Lack of copy/move semantics makes it tricky to handle files safely.
struct File {
private:
content: string;
public:
path: string;
File(path: string) {
this.content = "";
this.path = path;
}
}
Methods
-
read(this: File) -> string: Read the entire content of the file. -
open(path: string) -> File: Open a file for reading and writing.
Does the same shit as βread()β but it makes more sense to open once then use
read()later if the file got updated
-
close(this: File) -> unit: Close the file.
NB: This is called automatically in the destructor, BUT because of the current state of the memory, you might wanna do it manually just to be sure.
-
exists(this: File) -> bool: Check if the file exists. -
read_dir(this: File) -> [string]: Read the contents of a directory. -
read_file(this: File) -> string: Read the entire content of the file and returns it to you. It does not setFile.contentto the content of the file.
Functions
-
read_dir(path: string) -> [string]: Read the contents of a directory. -
read_file(path: string) -> string: Read the entire content of a file. -
write_file(path: string, content: string) -> unit: Write content to a file. -
remove_file(path: string) -> unit: Remove a file. -
file_exists(path: string) -> bool: Check if a file exists. -
close_file(path: string) -> unit: Close a file.
std/string
Structs
-
String: A string type that can be used to manipulate strings.
struct String {
s: string;
len: uint64;
String(s: string) {
self.s = s;
self.len = str_len(s);
}
}
Methods
-
String::from_chars(chars: [char]) -> String: Create a newStringstruct from a list of characters. -
String::str_len(s: string) -> uint64: Get the length of a string primitive. -
len(this: String) -> uint64: Get the length of the String. -
is_empty(this: String) -> bool: Check if the String is empty. -
concat(this: String, other: String) -> String: Concatenate two Strings. -
push(this: String, c: char) -> unit: Add a character to the end of the String. -
push_str(this: String, s: String) -> unit: Add a String to the end of the String. -
find(this: String, sub_string: String) -> Option<uint64>: Find the index of a substring in the String.
Not stable yet. BEWARE.
-
get(this: String, index: uint64) -> char: Get a character from the String by index. -
set(this: String, index: uint64, c: char) -> unit: Set a character in the String by index. -
to_str(this: String) -> string: Convert the String struct to a string primitive. -
to_chars(this: String) -> [char]: Convert the String struct to a list of characters. -
to_upper(this: String) -> String: Convert the String to uppercase. -
to_lower(this: String) -> String: Convert the String to lowercase -
trim(this: String) -> String: Trim whitespace from both ends of the String. -
split(this: String, sep: string) -> [String]: Split the String by a separator. -
into_iter(this: String) -> Iter<char>: Create an iterator (fromstd/iter) over the characters of the String.
Functions
-
str_len(s: string) -> uint64: Get the length of a string primitive. -
trim(s: string) -> string: Trim whitespace from both ends of a string. -
to_upper(s: string) -> string: Convert a string to uppercase. -
to_lower(s: string) -> string: Convert a string to lowercase. -
split(s: string, sep: string) -> [string]: Split a string by a separator. -
str_cmp(s1: string, s2: string) -> uint64: Compare two strings. -
to_chars(s: string) -> [char]: Convert a string to a list of characters. -
from_chars(s: [char]) -> string: Convert a list of characters to a string.
std/vector
Structs
-
Vector<T>: A vector is a dynamic array that can grow or shrink in size. It has a bunch of methods to manipulate the data.
struct Vector<T> {
private:
data: [T];
public:
length: uint64;
capacity: uint64;
Vector(data: [T]) {
this.length = len(data);
this.capacity = this.length;
this.data = data;
}
}
Methods
-
len(this: Vector<T>) -> uint64: Get the length of the vector. -
is_empty(this: Vector<T>) -> bool: Check if the vector is empty. -
push(this: Vector<T>, val: T) -> unit: Add an element to the end of the vector. -
pop(this: Vector<T>) -> T: Remove and return the last element of the vector. -
get(this: Vector<T>, index: uint64) -> T: Get an element from the vector by index. -
set(this: Vector<T>, index: uint64, val: T) -> unit: Set an element in the vector by index. -
into_iter(this: Vector<T>) -> Iter<T>: Create an iterator (fromstd/iter) over the elements of the vector. -
Vector::<T>::with_capacity(capacity: uint64) -> Vector<T>: Create a new vector with a specified capacity.
Functions
-
len<T>(lst: [T]) -> uint64: Get the length of a list. -
slice<T>(lst: [T], start: uint64, end: uint64) -> [T]: Get a slice of a list from start to end.
std/time
Structs
-
Time: A time type that can be used to represent time.
struct Time {
public:
sec: int64;
nsec: int64;
Time(sec: int64, nsec: int64) {
this.sec = sec;
this.nsec = nsec;
}
}
Methods
-
Time::now() -> Time: Get the current time. -
format(this: Time, fmt: str) -> str: Format the time as a string. -
to_iso_string(this: Time) -> str: Convert the time to an ISO 8601 string.
Yeah well, I donβt know why this function exists, but who knows, maybe it will be useful one day.
-
sleep(this: Time) -> unit: Sleep for the specified time.
NB: This is a blocking sleep. Might not work. BEWARE.
-
elapsed(this: Time, since: Time) -> uint64: Calculate the elapsed time between two time values in milliseconds.
Functions
-
now() -> Time: Get the current time. -
sleep(t: Time) -> unit: Sleep for the specified time.
NB: This is a blocking sleep. Might not work. BEWARE.
-
format_time(t: Time, fmt: str) -> str: Format the time as a string.
std/math
Functions
-
abs(x: int64) -> int64: Compute the absolute value of a integer number. -
abs_f(x: float64) -> float64: Compute the absolute value of a floating-point number. -
round(x: float64) -> int64: Round a floating-point number to the nearest integer. -
random(min: int64, max: int64) -> int64: Generate a random integer number in the range[min, max]. -
pow(x: int64, y: int64) -> int64: Computexraised to the power ofy. -
pow_f(x: float64, y: float64) -> float64: Computexraised to the power ofy. -
min(x: int64, y: int64) -> int64: Compute the minimum of two integer numbers. -
min_f(x: float64, y: float64) -> float64: Compute the minimum of two floating-point numbers. -
max(x: int64, y: int64) -> int64: Compute the maximum of two integer numbers. -
max_f(x: float64, y: float64) -> float64: Compute the maximum of two floating-point numbers.
std/option
Structs
-
Option<T>: A type that represents an optional value, which can either beSOMEorNONE.
private enum OptionTag {
NONE = 0;
SOME = 256;
}
struct Option<T> {
private:
tag: OptionTag;
data: T;
Option(data: T, tag: OptionTag) {
this.tag = tag;
this.data = data;
}
}
Methods
-
is_some(this: Option<T>) -> bool: Check if the option isSOME. -
is_none(this: Option<T>) -> bool: Check if the option isNONE. -
unwrap(this: Option<T>) -> T: Get the value inside the option if it isSOME, otherwise panic. -
unwrap_or(this: Option<T>, default: T) -> T: Get the value inside the option if it isSOME, otherwise return the default value. -
map<U>(this: Option<T>, f: (T) -> U) -> Option<U>: Apply a function to the value inside the option if it isSOME, otherwise returnNONE.
Donβt exist yet.
std/result
Structs
-
Result<T, E>: A type that represents either a successOKcarrying the typeTor an errorERRcarrying the typeE.
private enum ResultTag {
OK = 0;
ERR = 256;
}
struct Result<T, E> {
private:
data: T;
err: E;
tag: ResultTag;
Result(data: T, err: E, tag: ResultTag) {
this.data = data;
this.err = err;
this.tag = tag;
}
}
Methods
-
Result::<T, E>::ok(value: T) -> Result<T, E>: Create a newOKresult. -
Result::<T, E>::err(error: E) -> Result<T, E>: Create a newERRresult. -
is_ok(this: Result<T, E>) -> bool: Check if the result isOK. -
is_err(this: Result<T, E>) -> bool: Check if the result isERR. -
unwrap(this: Result<T, E>) -> T: Get the value inside the result if it isOK, otherwise panic. -
unwrap_err(this: Result<T, E>) -> E: Get the error inside the result if it isERR, otherwise panic. -
unwrap_or(this: Result<T, E>, default: T) -> T: Get the value inside the result if it isOK, otherwise return the default value. -
unwrap_err_or(this: Result<T, E>, default: E) -> E: Get the error inside the result if it isERR, otherwise return the default error. -
map<U>(this: Result<T, E>, f: (T) -> U) -> Result<U, E>: Apply a function to the value inside the result if it isOK, otherwise return theERR.
Donβt exist yet.
-
map_err<F>(this: Result<T, E>, f: (E) -> F) -> Result<T, F>: Apply a function to the error inside the result if it isERR, otherwise return theOK.
Donβt exist yet.
std/box
Structs
-
Box<T>: A type that represents a heap-allocated value of typeT.
struct Box<T> {
private:
data: T;
public:
Box(data: T) {
this.data = data;
}
}
Methods
-
get(this: Box<T>) -> T: Get the value inside the box. -
set(this: Box<T>, value: T) -> unit: Set the value inside the box.
std/iter
Structs
-
Iter<T>: An iterator type that can be used to iterate over a collection of typeT. (as of now it only iters over aVector<T>.)
struct Iter<T> {
private:
data: Vector<T>;
index: uint64;
public:
Iter(data: Vector<T>) {
this.data = data;
this.index = 0_uint64;
}
}
Methods
-
next(this: Iter<T>) -> Option<T>: Get the next element in the iterator. -
peek(this: Iter<T>) -> Option<&const T>: Peek at the next element in the iterator without advancing it. -
has_next(this: Iter<T>) -> bool: Check if the iterator has more elements. -
Iter::<T>::from_array(data: [T]) -> Iter<T>: Create a new iterator from an array. -
Iter::<T>::from_string(data: string) -> Iter<char>: Create a new iterator from a primitive string.
std/map
Structs
-
Map<K, V>: A type that represents a key-value store.
struct Map<K, V> {
private:
keys: Vector<K>;
values: Vector<V>;
public:
Map() {
this.keys = new Vector<K>(new [K; 0]);
this.values = new Vector<V>(new [V; 0]);
}
}
Methods
-
insert(this: Map<K, V>, key: K, value: V) -> unit: Insert a key-value pair into the map. -
get(this: Map<K, V>, key: K) -> Option<V>: Get a value from the map by key. -
remove(this: Map<K, V>, key: K) -> unit: Remove a key-value pair from the map. -
contains(this: Map<K, V>, key: K) -> bool: Check if a key exists in the map. -
size(this: Map<K, V>) -> uint64: Get the number of key-value pairs in the map.
Experimental Modules (Not Yet Included)
The following modules are under active development and are not included in the current standard library:
std/rc_ptr
Status: Experimental, work in progress
Reference-counted smart pointers for shared ownership:
// Planned usage (not yet available):
// let ptr: rc_ptr<Data> = rc_ptr::new(Data());
// let cloned: rc_ptr<Data> = ptr.clone(); // Reference count increases
std/cast
Status: Experimental, work in progress
Type casting and conversion utilities:
// Planned usage (not yet available):
// let num: int64 = 42;
// let as_float: float64 = cast<float64>(num);
Notes
- All modules are works in progress and subject to change.
- Most modules are not yet fully implemented or finalized.
- For experimental modules listed above, expect significant API changes.
- Standard library types may not be copyable; assume move semantics unless documented otherwise.
Blue Engine Library
Blue Engine is the experimental FFI testbed connecting Atlas77 to Rust. It serves as one of the core libraries shipped with Atlas77 and can be imported directly:
import blue_engine::triangle; import blue_engine::Engine; import blue_engine::ObjectSettings; import blue_engine::call_update_function; import std::result; import std::time; fun main() { let engine = Engine::init(); triangle("my_triangle", ObjectSettings::init("default"), &engine.renderer, &engine.objects).expect("Failed to create triangle"); let start = Time::now(); call_update_function(&engine, "update", start); } fun update(delta: Time, engine: &Engine) { return; }
The objective is to replicate the public API and general usage patterns of the original Blue Engine crate while adapting it to Atlas77βs execution model, type system, and VM. This library is under active development. Interfaces may change, features may remain incomplete, and stability is not guaranteed.
Current scope: a safe Atlas77 wrapper layer built entirely on extern_ptr and extern_fn, exposing Rust engine components without unsafe surface area in Atlas77.
Atlas 77 Language Roadmap
This document outlines the planned features and improvements for Atlas 77. Features are organized by category and development status.
Current Version
Atlas 77 is currently in early development (v0.6). The compiler and runtime are functional but not production-ready. Expect breaking changes and API modifications.
Language Features
Implemented β
- Static typing β All variables require explicit type annotations
- Structs β Product types with fields and methods
- Functions β First-class declaration with explicit return types
- Generics β Parametric types with explicit type parameters
- Control flow β
if/else,whileloops - Memory management β
new/deletewith RAII cleanup - References β Borrowing without ownership transfer
- Copy/Move semantics β Copy implicit for primitives, move for custom types
- Error types β
Option<T>andResult<T, E>for error handling - Comments β Single-line comments with
//
Planned π―
High Priority
-
Operator Overloading
- Allow custom implementations of
+,-,*,/,==,<, etc. - Enable ergonomic syntax for user-defined types
- Status: Design phase
- Allow custom implementations of
-
For-loops over Ranges
- Syntax:
for i in 0..10 { ... } - Support inclusive ranges:
0..=10 - Status: Design phase
- Syntax:
-
Pattern Matching
- Match expressions
- Status: Design phase
-
Copy & Move Semantics Refinement
- Better ergonomics for ownership patterns
- Improved compiler diagnostics
- Status: In progress
-
Dead Code Elimination
- Optimization pass to remove unused variables, functions, and code
- Status: Planned
-
Compiler Improvements
- Error Recovery β Report multiple errors instead of stopping at first
- Better diagnostics β More helpful error messages with suggestions
- Optimization passes β Constant folding, inlining, etc.
- Status: Ongoing
-
Code Generation Backends
- Cranelift Backend β Native code generation (in addition to VM)
- Status: Cranelift in progress
Medium Priority
-
First-class Functions and Closures
- Functions as values:
fun_ptr: fun(int64) -> int64 - Anonymous functions:
fun (x: int64) -> int64 { x * 2 } - Closures that capture variables
- Status: Design phase
- Functions as values:
-
Concepts
- Define shared behavior and interfaces
- Generic constraints on type parameters
- Default implementations
- Status: Design phase
-
Union Types
- Discriminated unions for variant data
- Similar to Rust enums with pattern matching
- Status: Design phase
-
Smart Pointers
rc_ptr<T>β Reference-counted pointers for shared ownership- Status: Experimental (std/rc_ptr, WIP)
-
Type Casting
- Explicit casting utilities in
std/cast - Support for numeric and structural conversions
- Status: Experimental (std/cast, WIP)
- Explicit casting utilities in
Lower Priority
- Async/Await
- Non-blocking I/O support
asyncfunctions andawaitexpressions- Status: Design phase
-
Package System
- Organize code into packages
- Namespace support for avoiding name collisions
- Status: Design phase
-
Package Manager
- Dependency resolution and management
- Binary distribution via crates.io
- Status: Design phase
Standard Library Roadmap
Core Modules (v1.0)
- β
std/ioβ Input/output (print, input, panic) note: println WIP - β
std/fsβ File system operations - β
std/stringβ String manipulation - β
std/vectorβ Dynamic arrays - β
std/optionβ Optional values - β
std/resultβ Error handling with results - β
std/boxβ Heap-allocated values - β
std/iterβ Iterators and iteration - β
std/mapβ Key-value collections - β
std/timeβ Time and duration - β
std/mathβ Mathematical functions
Experimental Modules (v0.6)
- π§
std/rc_ptrβ Reference-counted smart pointers (WIP) - π§
std/castβ Type conversion utilities (WIP)
Future Modules (v2.0+)
- π
std/syncβ Threads and synchronization - π
std/netβ Network I/O and TCP/UDP sockets - π
std/jsonβ JSON serialization/deserialization - π
std/regexβ Regular expressions - π
std/hashβ Cryptographic and hash functions - π
core/graphicsβ Graphics rendering (Vulkan/OpenGL/DirectX) - π
core/sdlβ SDL 2.0 bindings - π
core/ffiβ C FFI (foreign function interface) - π
core/ffi_rustβ Rust FFI
Runtime & VM
Current Status
- VM-based interpreter (deprecated, to be replaced)
- Basic execution model for testing and learning
- Not optimized for performance
Planned Improvements
- Cranelift Backend β Compile to native code for performance
- Optimizations β Constant folding, dead code elimination, inlining
- Error Recovery β Report multiple compilation errors
- Better Diagnostics β Improving the context and suggestions in error messages
Tooling
atlas_77 CLI
Implemented β
atlas_77 init <project>β Create new projectatlas_77 build <file>β Compile projectatlas_77 run <file>β Execute file or projectatlas_77 helpβ Show help
Planned π―
atlas_77 testβ Run testsatlas_77 docβ Generate documentation
Build Artifacts
- Default location:
./builds/<project_name>/ - Binary format: VM bytecode (temporary)
- Native code (future)
Compiler Features
Optimizations
- π Dead code elimination
- π Constant folding
- π Function inlining
- π Loop unrolling
- π Register allocation (with native backend)
Milestone Timeline
-
v0.3.x:
- Initial language design
- Basic parser implementation
- Basic AST visitor runtime
-
v0.4.x:
- Prototype language design
- Basic parser and AST
-
v0.5.x (completed):
- Proof of concept compiler
- Basic syntax and semantics
-
vO.6.x (current):
- Core language features
- Basic standard library
- Move & copy semantics
[!Note] Nothing is set in stone; timelines may shift based on what I want to work on and community feedback.
- v0.7.x:
- Cranelift backend MVP
- Future versions:
- Operator overloading
- Pattern matching
- First-class functions & closures
- Concepts
Note
Timeline is aspirational and subject to change based on development priorities and community feedback.
Contributing
The language is actively developed. Feature requests, bug reports, and discussions are welcome. Development happens in the compiler repository.