The Aegis Programming Language
Aegis is a dynamic, high-performance scripting language designed for modern systems automation, game scripting, and rapid prototyping.
Built in Rust, it features a lightning-fast Bytecode Virtual Machine (VM), a gradual typing system, and a rich standard library "batteries included."
⚡ Why Aegis?
🚀 High Performance
Powered by a custom Stack-Based Virtual Machine, Aegis v0.2.0 is exponentially faster than its predecessors. It handles heavy recursion and complex algorithms with ease (benchmarked at ~250ms for Fib30, rivaling optimized dynamic runtimes).
🛡️ Robust & Safe
Aegis combines the flexibility of dynamic typing with the safety of Gradual Typing. You can prototype fast using var x = 10, then secure your critical code with var x: int = 10.
Plus, the robust try/catch mechanism ensures your scripts handle errors gracefully.
📦 Modular & Modern
- Functional: First-class support for Lambdas, Closures,
map, andfilter. - Object-Oriented: Clean Class syntax with methods and state.
- Organized: Native support for Namespaces and Imports.
🔋 Batteries Included
Aegis comes with a comprehensive Standard Library out of the box:
- HTTP Client for web interactions.
- JSON parsing and serialization.
- File System manipulation.
- Regex support.
- SQLite integration (via plugins).
A Taste of Aegis
Here is a glimpse of what Aegis code looks like:
import "stdlib/http.aeg"
import "stdlib/json.aeg"
// A Class representing a User
class User(name, id) {
info() {
return "User: " + this.name + " (ID: " + this.id + ")"
}
}
// Fetching data from an API
try {
print "Fetching data..."
var response = Http.get("https://jsonplaceholder.typicode.com/users/1")
var data = Json.parse(response)
// Creating an object
var user = new User(data.get("name"), data.get("id"))
print "✅ Success!"
print user.info()
} catch (e) {
print "❌ Error: " + e
}
Getting Started
Welcome to the Aegis journey. This section will guide you through the initial steps of setting up your development environment and running your first scripts.
We will cover:
- Installation: How to get the Aegis binary on your machine.
- Hello World: Writing and executing your first script.
- The REPL: Using the interactive shell for quick testing.
- Package Manager: Managing dependencies for larger projects.
Installation
Currently, Aegis is distributed as source code. To install it, you need to compile the project locally using Rust.
Prerequisites
Before proceeding, ensure you have the following installed on your machine:
Git: To clone the repository. Download GitRust&Cargo: To build the language. The easiest way to install Rust is via rustup.- Linux / macOS:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Windows: Download
rustup-init.exefrom rust-lang.org.
- Linux / macOS:
Step-by-Step Guide
1. Clone the Repository
Open your terminal and clone the Aegis source code:
git clone https://github.com/AegisProgrammingLanguage/AegisProgrammingLanguage.git
cd aegis
2. Build and Install
Use cargo to compile the project in release mode and install the binary globally on your system.
cargo install --path .
This process might take a minute or two as it downloads dependencies (like tokio, reqwest, etc.) and optimizes the build.
3. Verify Installation
Once the compilation is complete, cargo places the aegis binary in your generic Cargo bin folder (usually ~/.cargo/bin). This folder should be in your system PATH automatically if you installed Rust via rustup.
To verify that Aegis is correctly installed, run:
aegis --version
You should see an output similar to:
aegis 0.2.0
Troubleshooting
"Command not found: aegis"
If the installation finished successfully but the command is not found:
- Ensure
~/.cargo/bin(Linux/Mac) or%USERPROFILE%\.cargo\bin(Windows) is in your system PATH. - Restart your terminal session to refresh the environment variables.
Build Errors on Linux
Since Aegis depends on openssl (via the HTTP module) and sometimes system libraries, you might need to install development packages on Linux (Ubuntu/Debian example):
sudo apt update
sudo apt install build-essential pkg-config libssl-dev
Updating Aegis
To update to the latest version of Aegis, pull the latest changes from Git and force a re-installation:
git pull origin master
cargo install --path . --force
Hello World
Now that you have Aegis installed, it's time to write your first program. This is the traditional "Hello World" test to ensure everything is working correctly.
1. Create a Script
Create a new file named hello.aeg in your favorite text editor or IDE.
Add the following line to the file:
print "Hello, World!"
2. Run the Script
Open your terminal, navigate to the folder where you saved the file, and run the following command:
aegis run hello.aeg
3. Output
You should see the following output in your terminal:
Hello, World!
If you see this message, congratulations! You have successfully compiled and executed your first Aegis program.
Going Further: Interaction
Let's try something slightly more complex. Aegis has a built-in input instruction to interact with the user.
Update your hello.aeg file with the following code:
print "--- Aegis Greeter ---"
// Ask for the user's name
input name "What is your name? "
// String concatenation
print "Welcome to Aegis, " + name + "!"
Run it again:
aegis run hello.aeg
Example interaction:
--- Aegis Greeter ---
What is your name? Ethan
Welcome to Aegis, Ethan!
What just happened?
print: Displays text to the standard output (console).input: Pauses execution, waits for the user to type text and press Enter, and stores the result in the variable name.+: Joins (concatenates) the strings together.
The REPL
Status Note (v0.2.0)
The Interactive REPL is now functional with the new v0.2.0 Bytecode Virtual Machine.
What is a REPL?
REPL stands for Read-Eval-Print Loop. It is an interactive shell that allows you to type Aegis code and see the results immediately, without creating a file.
It is typically used for:
- Testing small snippets of logic.
- Doing quick calculations.
- Exploring the available Standard Library functions.
Starting a Session
To enter the interactive mode, run the repl command:
aegis repl
Or simply run the executable without arguments:
aegis
You will see the prompt >> waiting for your input.
Usage Example
Concept of a typical session once the migration is complete:
Aegis v0.2.0 - Interactive Mode
Type 'exit' or 'quit' to leave.
>> var a = 10
>> var b = 5
>> print a * b
50
>> func greet(name) { return "Hello " + name }
>> print greet("User")
Hello User
Exiting
To exit the REPL and return to your system terminal, type:
exit
Or quit.
Package Manager (apm)
Modern development relies heavily on reusing existing code and libraries. Aegis comes with a built-in Package Manager (often referred to as APM) to manage your project's dependencies, ensuring you can easily install, update, and publish modules.
The Project Manifest: aegis.toml
Every Aegis project starts with a manifest file named aegis.toml at the root of your directory. This file describes your project and lists the external packages it needs.
Here is an example of a typical aegis.toml file:
[package]
name = "my_rpg_game"
version = "0.1.0"
authors = ["You <you@example.com>"]
[dependencies]
glfw = "1.0.0"
sqlite = "0.2.0"
http = "*"
-
[package]: Metadata about your project. -
[dependencies]: A list of packages to install from the Aegis Registry.
Managing Dependencies
Adding a Package
To add a new library to your project, use the add command. This will download the package and automatically add it to your aegis.toml file.
aegis add glfw
You can also specify a version:
aegis add sqlite 1.0.4
Installation Folder
When you add a dependency, Aegis downloads the files into a packages/ directory at the root of your project.
Project Structure:
my_project/
├── aegis.toml <-- Manifest
├── src/
│ └── main.aeg <-- Your code
└── packages/ <-- Managed by APM
├── glfw/
└── sqlite/
Note: You should generally add packages/ to your .gitignore file, similar to node_modules in JavaScript or target in Rust.
Using Installed Packages
Once a package is installed, you can use it in your scripts using the import statement. Since packages are stored locally in the packages folder, the path is straightforward.
// Importing the GLFW library installed via APM
import "packages/glfw/glfw.aeg"
// Importing SQLite
import "packages/sqlite/sqlite.aeg"
func main() {
// Use the namespace defined in the package
var db = Sqlite.open("game.db")
print "Database opened!"
}
main()
Publishing a Package
If you have created a library and want to share it with the world, APM makes it easy.
1. Authentication
First, you need to authenticate with the Aegis Registry using your token.
aegis login <your-api-token>
2. Publishing
Ensure your aegis.toml is correctly configured with a unique name and version, then run:
aegis publish
This will upload your code (excluding ignored files) to the registry, making it available for everyone to aegis add.
Aegis Cheat Sheet
A quick reference guide for the Aegis syntax (v0.2.0).
Variables & Types
var name = "Aegis" // Dynamic
var count: int = 42 // Typed (Gradual)
var pi: float = 3.14
var is_live = true
var nothing = null
Collections
// List
var list = [1, 2, 3]
list.push(4)
var item = list.at(0)
// Dict
var user = { id: 1, name: "Admin" }
var id = user.get("id")
Control Flow
if (x > 10) { ... } else { ... }
while (running) { ... }
for (i, 0, 10, 1) { ... } // Start (inc), End (exc), Step
switch (val) {
case 1: print "One"
default: print "Other"
}
Functions
// Named
func add(a, b) { return a + b }
// Lambda
var mult = func(a, b) { return a * b }
// Decorator
@logger
func action() { ... }
Classes
class Hero(name, hp) {
heal(amount) {
this.hp = this.hp + amount
}
}
var h = new Hero("Link", 100)
h.heal(20)
Modules
// File: lib.aeg
namespace Lib {
var version = "1.0"
}
// File: main.aeg
import "lib.aeg"
print Lib.version
Error Handling
try {
throw "Oops"
} catch (e) {
print e
}
Language Basics
Aegis is designed to be familiar to developers who know C, Java, JavaScript, or Python. It uses a clean, brace-based syntax with optional semicolons.
In this section, we will explore the fundamental building blocks of the language:
- Variables: How to store state.
- Types: Dynamic vs Gradual typing.
- Control Flow: Making decisions and looping (
if,while,for,switch). - Comments: Documenting your code.
Variables & Data Types
Aegis allows you to store data using mutable variables or immutable constants.
It is a dynamically typed language by default. This means you do not need to specify the type of a variable when you declare it, and a variable can hold values of different types throughout its life (unless you use Gradual Typing).
Mutable Variable
Use the var keyword when the value needs to change during the execution of the program.
var username = "Admin"
var score = 100
var is_active = true
Reassignment
You can change the value of a variable at any time:
var data = 42
print data // 42
data = "Changed to string"
print data // Changed to string
Immutable Constants (const)
Use const for values that must remain the same throughout the entire scope (e.g., configuration, math constants). This provides safety and clarifies intent.
const PI = 3.14159
const MAX_RETRIES = 5
// Using the value works fine
var area = PI * 10 * 10
Safety Check
Constants are protected at Compile Time. If you try to reassign a constant, Aegis will raise an error and refuse to run the script.
const URL = "https://google.com"
// This causes a panic/crash at compile time:
URL = "https://bing.com"
Tip: It is considered good practice to name constants using UPPER_SNAKE_CASE (e.g., MAX_SPEED), although Aegis does not enforce it.
Primitive Types
Aegis supports the following primitive data types:
| Type | Description | Example |
|---|---|---|
| Null | Represents the absence of value. | null |
| Boolean | Logical true or false. | true, false |
| Integer | 64-bit signed integer. | 42, -10, 0 |
| Float | 64-bit floating point number. | 3.14, -0.01 |
| String | UTF-8 text sequence. | "Hello World" |
Note: Lists and Dictionaries are complex types and are covered in the Data Structures section.
String Interpolation
You can inject variables directly into strings using the ${} syntax. This converts the value to a string automatically.
var name = "Aegis"
var version = 0.2
print "Welcome to ${name} version ${version}!"
// Output: Welcome to Aegis version 0.2!
Compound Assignment
Aegis provides shorthand operators to modify the value of a variable based on its current value. This avoids repeating the variable name.
| Operator | Syntax | Equivalent To |
|---|---|---|
+= | x += y | x = x + y |
-= | x -= y | x = x - y |
*= | x *= y | x = x * y |
/= | x /= y | x = x / y |
Examples
var score = 10
score += 5 // score is now 15
score *= 2 // score is now 30
Increment & Decrement
To quickly add or subtract 1 from a number, you can use the increment (++) and decrement (--) operators.
var i = 0
i++ // i is now 1
i++ // i is now 2
i-- // i is now 1
These operators also work on object properties:
player.hp -= 10
player.level++
Gradual Typing
While dynamic typing is great for prototyping, large applications often require more safety. Aegis offers Gradual Typing, allowing you to enforce types on specific variables or function arguments.
Syntax
To enforce a type, add a colon : followed by the type name after the variable name.
// This variable MUST be an integer
var count: int = 0
// This works fine
count = 10
// This throws a Runtime Error:
// count = "ten"
Supported Type Keywords
| Keyword | Matches |
|---|---|
int | Integers |
float | Floating point numbers |
string | Strings |
bool | Booleans |
list | Lists (Arrays) |
dict | Dictionaries (Maps) |
func | Functions |
Typing in Functions
Gradual typing is particularly powerful in function signatures to ensure arguments and return values are correct.
// Arguments must be floats, return value must be a float
func divide(a: float, b: float) -> float {
return a / b
}
var res = divide(10.0, 2.0) // Works
// var err = divide(10, 2) // Throws Error: Expected 'float', got '10' (int)
Note: Type checks happen at Runtime. If a type mismatch occurs, the Virtual Machine throws an exception that can be caught with try/catch.
Built-in Functions
Aegis comes with a set of utility functions that are available globally in any scope. You do not need to import anything to use them.
Type Conversion
String
Syntax: to_str(value)
Converts any value into its string representation.
var x = 10
print "Value: " + to_str(x)
Integer
Syntax: to_int(value)
Converts a value to an integer.
- Floats are truncated (not rounded).
- Strings are parsed.
print to_int("42") // 42
print to_int(3.99) // 3
Float
Syntax: to_float(value)
Converts a value to a floating-point number.
print to_float("3.14") // 3.14
print to_float(10) // 10.0
Type Inspection
Type of
Syntax: typeof(value)
Returns a string representing the type of the value. Possible return values:
int,float,string,bool,nulllist,dict,range,enumfunction,class
For class instances, it returns the name of the Class (e.g., "User").
print typeof(10) // "int"
print typeof([1, 2]) // "list"
class User {}
var u = new User()
print typeof(u) // "User"
Is Instance
Syntax: is_instance(object, class)
Checks if an object is an instance of a specific class or inherits from it. This is the preferred way to check types for objects.
class Animal {}
class Dog extends Animal {}
var d = new Dog()
print is_instance(d, Dog) // true
print is_instance(d, Animal) // true
Utilities
Len
Syntax: len(iterable)
Returns the length of a container. Supports:
- String: Number of characters (UTF-8 aware).
- List: Number of elements.
- Dict: Number of key-value pairs.
- Range: Number of steps.
print len("Hello") // 5
print len([1, 2, 3]) // 3
Fmt
Syntax: fmt(value, format_string)
Formats a number (usually a float) into a string with specific precision.
- Format: ".Nf" where N is the number of decimal places.
var pi = 3.14159265
print fmt(pi, ".2f") // "3.14"
print fmt(pi, ".4f") // "3.1416"
Text Encoding & Unicode
To handle low-level string manipulation, Aegis provides standard functions to convert between characters and their integer representations.
Char
Syntax: chr(code)
Converts a Unicode integer code point into a string containing the corresponding character.
- Arguments:
int(The Unicode code point). - Returns:
string.
print chr(65) // "A"
print chr(97) // "a"
print chr(8364) // "€"
Ord
Syntax: ord(string)
Returns the integer Unicode code point of the first character in the given string.
- Arguments:
string. - Returns:
int.
print ord("A") // 65
print ord("€") // 8364
print ord("🚀") // 128640
Enums
Enums (Enumerations) allows you to define a set of named constants. They are useful for representing states, options, or error codes without using "magic numbers".
Defining an Enum
Use the enum keyword followed by a name and a list of variants.
enum Status {
Idle,
Running,
Error
}
Under the hood, Aegis assigns an auto-incrementing integer to each variant, starting at 0.
Status.Idleis0Status.Runningis1Status.Erroris2
Usage
You access enum members using the dot notation.
var current_state = Status.Running
if (current_state == Status.Error) {
print "Something went wrong!"
} else {
print "All systems go."
}
Immutability & Safety
Unlike Dictionaries, Enums are read-only. You cannot add, remove, or modify variants at runtime. This ensures that your constants remain constant throughout the program's execution.
try {
// This will throw a Runtime Error
Status.Idle = 100
} catch (e) {
print "Error: " + e
}
Control Flow
Aegis provides standard control structures to direct the flow of your program.
If / Else
Standard conditional logic. The condition must evaluate to a boolean or a truthy value.
var hp = 50
if (hp > 75) {
print "Healthy"
} else if (hp > 25) {
print "Injured"
} else {
print "Critical condition!"
}
Loops
Aegis provides two types of loops: while for indefinite iteration and foreach for iterating over sequences.
While Loop
Repeats a block of code as long as the condition is true.
var i = 3
while (i > 0) {
print "Countdown: " + i
i = i - 1
}
print "Liftoff!"
The Foreach Loop
The foreach loop is the primary tool for iteration in Aegis. It works with Ranges, Lists, and Strings.
Syntax
foreach (variable in iterable) {
// code block
}
Iterating over Lists
var users = ["Alice", "Bob", "Charlie"]
foreach (user in users) {
print "Hello, " + user + "!"
}
Iterating over Ranges
To repeat an action a specific number of times, use a Range with ...
foreach (i in 1..10) {
print "Iteration " + i
}
This is generally preferred over the C-style for loop (for (i, 0, 10, 1)) because it is cleaner and easier to read.
Iterating over Strings
You can also iterate over a string character by character.
foreach (char in "Aegis") {
print char
}
Nesting
Foreach loops can be nested. The loop variable is local to its specific block, preventing conflicts.
var matrix = [ [1, 2], [3, 4] ]
foreach (row in matrix) {
foreach (cell in row) {
print cell
}
}
Loop Control: Break and Continue
You can finely control the execution of loops using break and continue.
Break
Stops the loop immediately and resumes execution after the loop block.
// Stop searching when we find the target
var target = 5
foreach (i in 0..10) {
if (i == target) {
print "Found it!"
break
}
}
Continue
Skips the rest of the current iteration and jumps directly to the next one (checking the condition in while, or incrementing in for).
// Print only odd numbers
foreach (i in 0..10) {
// If even, skip printing
if (i % 2 == 0) {
continue
}
print i
}
// Output: 1, 3, 5, 7, 9
Switch
The switch statement simplifies long if/else chains. Aegis switches perform an implicit break (no fall-through).
var status = 200
switch (status) {
case 200:
print "OK"
case 404:
print "Not Found"
case 500:
print "Server Error"
default:
print "Unknown status"
}
Ternary Operator
For simple conditions where you want to assign a value based on a check, the standard if/else can be verbose. Aegis provides the Ternary Operator ? : for this purpose.
Syntax
condition ? value_if_true : value_if_false
Example
Instead of:
var status = null
if (age >= 18) {
status = "Adult"
} else {
status = "Minor"
}
You can write:
var status = (age >= 18) ? "Adult" : "Minor"
Nesting
Ternary operators can be nested, although this can reduce readability.
var category = (score > 90) ? "A" : ((score > 50) ? "B" : "C")
Null Coalescing Operator (??)
The null coalescing operator ?? is a logical operator that returns its right-hand side operand when its left-hand side operand is null, and returns its left-hand side operand otherwise.
It is cleaner and safer than using || because it only checks for null, not false or 0.
Syntax
left_expr ?? default_value
Examples
Basic Usage:
var user_input = null
var username = user_input ?? "Guest"
print username // "Guest"
Difference with ||:
var count = 0
// || considers 0 as false
print count || 100 // 100 (Unwanted behavior?)
// ?? only cares about null
print count ?? 100 // 0 (Correct!)
Chaining:
var config = null
var env = null
var port = config ?? env ?? 8080
print port // 8080
Comments
Comments are lines of code ignored by the compiler. They are useful for documenting your code.
Single Line Comments
Use // for single line comments. Everything after the slashes is ignored.
var x = 10 // This is a variable
// This entire line is a comment
Block Comments
Use /* to start and */ to end a block comment. This can span multiple lines.
/*
This is a multi-line comment.
It is useful for describing complex logic
or temporarily disabling a block of code.
*/
var y = 20
Style Guide & Best Practices
To keep Aegis code clean and readable, we recommend following these conventions.
Naming Conventions
-
Variables & Functions: Use
snake_case.var my_variable = 10 func calculate_total() { ... } -
Classes: Use
PascalCase.class GameManager { ... } -
Constants: Use
UPPER_SNAKE_CASEinside Namespaces.namespace Config { var MAX_RETRIES = 5 }
File Structure
- Use
.aegextension for all scripts. - Place reusable code in a
lib/ormodules/folder. - Wrap library code in a
namespaceto avoid polluting the global scope.
Error Handling
Prefer try/catch over checking for null when performing I/O operations (File, Network).
// ✅ Good
try {
var data = File.read("config.json")
} catch (e) {
print "Config not found, using defaults."
}
// ❌ Avoid
var data = File.read("config.json") // If fails, script crashes!
Data Structures
Beyond simple numbers and booleans, real-world applications require organizing data into complex structures. Aegis provides three robust built-in structures:
- Lists: Ordered, dynamic arrays of values.
- Dictionaries: Key-value mappings (HashMaps).
- Strings: Immutable sequences of characters with powerful manipulation methods.
These structures are reference types and are managed automatically by the Aegis memory manager.
Lists
Lists are ordered collections of values. In Aegis, lists are dynamic (they can grow or shrink) and can hold values of mixed types.
Creation
Use square brackets [] to create a list.
// Empty list
var empty = []
// Mixed types
var numbers = [1, 2, 3, 4]
var user = ["Admin", 42, true]
Accessing Elements
Lists are 0-indexed. Use the .at(index) method to retrieve an element.
var fruits = ["Apple", "Banana", "Cherry"]
print fruits.at(0) // Apple
print fruits.at(2) // Cherry
// Accessing an out-of-bounds index returns null
print fruits.at(99) // null
Modifying Lists
You can add and remove elements dynamically.
| Method | Description | Example |
|---|---|---|
.push(value) | Adds an element to the end of the list. | list.push(5) |
.pop() | Removes and returns the last element. | var last = list.pop() |
.len() | Returns the number of elements. | list.len() |
.reverse() | Reverses elements in-place and returns the list itself. | list.reverse() |
.contains(val) | Returns true if the value exists in the list. | if (list.contains("admin")) { ... } |
.index_of(val) | Returns the index of the first occurrence of val, or -1 if not found. | var idx = list.index_of("banana") |
.join(sep) | Joins all elements into a single string using a separator. | list.join(", ") |
.is_empty() | Returns true if the list is empty. | if (list.is_empty()) { ... } |
.first() | Returns the first element of the list. | var first = list.first() |
.last() | Returns the last element of the list. | var last = list.last() |
.clear() | Removes all items from the list. | list.clear() |
.slice(start, end) | Returns a new sub-list from start to end (exclusive). | var sub = items.slice(0, 10) |
.sort(fn?) | Sorts the list in-place. Accepts an optional comparison function func(a,b). | list.sort(func(a,b) { return a - b }) |
.find(fn) | Returns the first element where the callback returns true, or null. | var u = users.find(func(u) { return u.id == 1 }) |
.map(fn) | Creates a new list with the results of calling a function on every element. | var squares = nums.map(func(n) { return n * n }) |
.filter(fn) | Creates a new list with all elements that pass the test implemented by the function. | var adults = users.filter(func(u) { return u.age >= 18 }) |
.reduce(fn, init) | Reduces the list to a single value using an accumulator. | var sum = nums.reduce(func(acc, n) { return acc + n }, 0) |
.for_each(fn) | Executes a provided function once for each array element. | list.for_each(func(item) { print item }) |
Example
var stack = []
stack.push("First")
stack.push("Second")
print stack.len() // 2
print stack.pop() // "Second"
print stack.len() // 1
Note: Lists in Aegis are passed by reference. If you pass a list to a function and modify it there, the original list is affected.
Dictionaries
Dictionaries (or Maps) are collections of key-value pairs. They are useful for storing structured data or representing objects.
Creation
Use curly braces {}. Keys must be strings (or identifiers which are converted to strings).
var config = {
host: "localhost",
port: 8080,
debug: true
}
Operations
| Method | Description | Example |
|---|---|---|
.get(key) | Returns the value associated with the key. | dict.get("host") |
.insert(key, val) | Adds or updates a key-value pair. | dict.insert("ssl", true) |
.keys() | Returns a List of all keys in the dictionary. | dict.keys() |
.len() | Returns the number of entries. | dict.len() |
.is_empty() | Returns true if the dict is empty, otherwise returns false. | if (dict.is_empty()) { ... } |
.remove(key) | Removes a key and returns its value. | dict.remove("ssl") |
.values() | Returns a List of all values in the dictionary. | dict.values() // [true] (true is the value of key "ssl") |
Example
var user = {}
// Adding data
user.insert("name", "Arthur")
user.insert("level", 5)
// Retrieving data
print "User: " + user.get("name")
// Listing keys
var fields = user.keys()
print fields // ["name", "level"]
Note: Accessing a non-existent key with .get() returns null.
Strings
Strings in Aegis are immutable sequences of UTF-8 characters. While they act like primitive types, they possess several built-in methods for manipulation.
Basic Operations
You can concatenate strings using the + operator.
var first = "Hello"
var second = "World"
print first + " " + second // "Hello World"
Methods
| Method | Description | Example |
|---|---|---|
.len() | Returns the length of the string. | "Hi".len() (2) |
.at(index) | Returns the character at the specified index (or null). | "Abc".at(1) ("b") |
.index_of(sub) | Returns the index of the first occurrence (or -1). | "Hello".index_of("e") (1) |
.slice(start, end) | Returns a substring from start to end (exclusive). | "Hello".slice(1, 4) ("ell") |
.trim() | Removes whitespace from both ends. | " a ".trim() ("a") |
.upper() | Converts the entire string to uppercase. | "aegis".upper() ("AEGIS") |
.lower() | Converts the entire string to lowercase. | "AEGIS".lower() ("aegis") |
.contains(sub) | Returns true if the string contains the substring. | "Hello".contains("el") |
.starts_with(sub) | Returns true if the string starts with the substring. | "file.txt".starts_with("file") |
.ends_with(sub) | Returns true if the string ends with the substring. | "image.png".ends_with(".png") |
.replace(old, new) | Replaces all occurrences of a substring. | "a-b-c".replace("-", ".") |
.split(delim) | Splits the string into a List of substrings. | "a,b".split(",") |
.is_empty() | Returns true if the string length is 0. | "".is_empty() |
.pad_start(len, char) | Pads the start with char (default " ") until length is reached. | "1".pad_start(3, "0") ("001") |
.pad_end(len, char) | Pads the end with char (default " ") until length is reached. | "Hi".pad_end(5, "!") ("Hi!!!") |
Examples
Cleaning Input
var input = " user@example.com "
var clean = input.trim()
print clean // "user@example.com"
Replacement
var text = "Hello World"
print text.replace("World", "Aegis") // "Hello Aegis"
Parsing CSV-like data
var csv = "apple,banana,orange"
var items = csv.split(",")
print items.len() // 3
print items.at(0) // "apple"
Multi-line Strings (Template Literals)
You can create strings that span multiple lines using backticks (`). This is useful for SQL queries, HTML, or formatted text.
var menu = `
Welcome to the Game
1. Start
2. Options
3. Exit
`
print menu
Backticks also support interpolation ${variable} just like double quotes.
Functions & Functional Programming
Functions are the heart of Aegis logic. They are first-class citizens, meaning they can be assigned to variables, passed as arguments, and returned from other functions.
Aegis v0.2 heavily emphasizes Functional Programming paradigms alongside imperative code.
This chapter covers:
- Declarations: Standard named functions.
- Lambdas: Anonymous functions and closures.
- Decorators: Modifying function behavior dynamically.
- Functional Tools: Processing data with
map,filter, andfor_each.
Function Declaration
Functions are reusable blocks of code. In Aegis, functions are "first-class citizens," meaning they can be stored in variables, passed as arguments, and returned from other functions.
Syntax
Use the func keyword followed by a name, parameters in parentheses, and a block of code.
func greet(name) {
print "Hello, " + name + "!"
}
// Calling the function
greet("Alice")
Return Values
Use the return keyword to send a value back to the caller. If no return statement is provided, the function returns null.
func add(a, b) {
return a + b
}
var result = add(5, 10)
print result // 15
Recursion
Functions can call themselves. Thanks to the stack-based VM, Aegis handles recursion efficiently.
func fib(n) {
if (n < 2) { return n }
return fib(n - 1) + fib(n - 2)
}
Lambdas & Closures
Aegis supports anonymous functions, often called Lambdas. These are functions without a name, typically used for short operations or callbacks.
Creating a Lambda
The syntax is identical to a standard function, but without the name.
var say_hello = func() {
print "Hello from Lambda!"
}
// Execute the variable
say_hello()
Closures (Capturing Environment)
Lambdas in Aegis are Closures. This means they can "capture" and remember variables from the scope in which they were defined, even after that scope has finished executing.
func make_counter() {
var count = 0
// This lambda captures 'count'
return func() {
count = count + 1
return count
}
}
var counter = make_counter()
print counter() // 1
print counter() // 2
print counter() // 3
Note: Aegis v0.2 captures variables by value (snapshot) or by reference depending on implementation specifics. In the current version, complex logic inside closures is fully supported.
Decorators
Decorators provide a clean syntax to modify or enhance the behavior of a function without changing its code. They are widely used for logging, access control, or performance measuring.
Syntax
A decorator is simply a function that takes a function as an argument and returns a new function. You apply it using the @ symbol.
// 1. Define the decorator
func logger(target_func) {
return func(arg) {
print "[LOG] Calling function with: " + arg
var result = target_func(arg)
print "[LOG] Result: " + result
return result
}
}
// 2. Apply it
@logger
func square(x) {
return x * x
}
// 3. Use the decorated function
square(5)
Output:
[LOG] Calling function with: 5
[LOG] Result: 25
How it works internally
The @ syntax is syntactic sugar. The code above is equivalent to:
func square(x) { return x * x }
square = logger(square)
Functional Programming
Aegis provides built-in methods on Lists to process data using a functional style. This allows for cleaner, more expressive code compared to traditional loops.
Map
Syntax: map(callback)
Creates a new list by applying a function to every element in the original list.
var numbers = [1, 2, 3, 4]
var doubled = numbers.map(func(n) {
return n * 2
})
print doubled // [2, 4, 6, 8]
Filter
Syntax: filter(callback)
Creates a new list containing only elements for which the callback returns true.
var numbers = [10, 5, 20, 3]
var big = numbers.filter(func(n) {
return n > 8
})
print big // [10, 20]
For each
Syntax: for_each(callback)
Executes a function for every element in the list. Useful for side effects (like printing or saving).
var names = ["Alice", "Bob"]
names.for_each(func(name) {
print "User: " + name
})
Chaining
Since map and filter return new Lists, you can chain them together.
// Take 1..5, multiply by 10, keep those > 20
var res = [1, 2, 3, 4, 5]
.map(func(n) { return n * 10 })
.filter(func(n) { return n > 20 })
print res // [30, 40, 50]
Object-Oriented Programming
Aegis supports Object-Oriented Programming (OOP) to help structure larger codebases and model complex data.
The system is Class-based, supporting:
- Classes: Blueprints for objects.
- Instances: Objects created from classes.
- Methods: Functions bound to specific objects using
this. - Composition: Building complex objects from simpler ones.
Classes & Instances
Object-Oriented Programming in Aegis allows you to structure your code by bundling data and behavior into reusable blueprints called Classes.
Defining a Class
Use the class keyword followed by the class name and a block { ... }.
Aegis now supports Declared Fields and Visibility Modifiers. You define your data structure at the top of the class.
class User {
// 1. Field Declaration
public name
private email
protected role = "Guest" // Default value
// 2. Initializer (Constructor)
init(name, email) {
this.name = name
this.email = email
print "User created: " + this.name
}
// 3. Methods
public get_info() {
return this.name + " (" + this.role + ")"
}
}
Visibility & Encapsulation
Aegis enforces strict encapsulation. There are three visibility modifiers available for both fields and methods:
| Keyword | Scope | Description |
|---|---|---|
| public | Global | Accessible from anywhere. This is the default if no modifier is used. |
| protected | Hierarchy | Accessible only within the class and its subclasses. |
| private | Internal | Accessible only within the class itself. Strictly internal. |
Example
class BankAccount {
private balance = 0
public currency = "EUR"
init(start_amount) {
if (start_amount > 0) {
this.balance = start_amount
}
}
public deposit(amount) {
this.balance += amount
this.log_transaction()
}
// Private method: Internal use only
private log_transaction() {
print "Transaction logged."
}
}
var acc = new BankAccount(100)
// ✅ Public access allowed
print acc.currency
acc.deposit(50)
// ❌ Error: Access denied ('balance' is private)
// print acc.balance
// ❌ Error: Access denied ('log_transaction' is private)
// acc.log_transaction()
Creating Instances
To create an object, use the new keyword. This allocates memory, initializes fields with their default values, and then calls the init method.
var admin = new User("Alice", "alice@aegis.lang")
Type Checking
To verify if an object is an instance of a specific class, use the global function is_instance().
if (is_instance(admin, User)) {
print "This is a user."
}
Static Members
You can define fields and methods that belong to the class itself, rather than to instances of the class. These are called static members.
Use the static keyword to declare them. Static members can also have visibility modifiers (public, private).
Usage
Static members are accessed using the class name: ClassName.member.
class MathUtils {
// A class constant
public static PI = 3.14159
// A static method
static circle_area(radius) {
// Inside a static method, 'this' refers to the Class itself
return this.PI * radius * radius
}
}
print MathUtils.PI // 3.14159
print MathUtils.circle_area(10) // 314.159
Static vs Instance
- Instance Members: Created for each new object (e.g.,
new User("Bob")). - Static Members: Created once when the class is defined. They are shared.
This is particularly useful for configuration, counters, or Factory patterns.
class Database {
private static connection_count = 0
init() {
Database.connection_count += 1
}
static get_active_connections() {
return this.connection_count
}
}
var db1 = new Database()
var db2 = new Database()
print Database.get_active_connections() // 2
Properties
Properties allow you to expose class members like fields (variables), but define custom logic for reading and writing them using Getters and Setters.
Defining a Property
Use the prop keyword. A property can have a get block, a set block, or both.
class Circle {
private _radius = 0
// Public interface usually uses PascalCase or snake_case depending on style
prop radius {
get {
return this._radius
}
set(value) {
if (value < 0) {
print "Radius cannot be negative"
} else {
this._radius = value
}
}
}
}
var c = new Circle()
c.radius = 10 // Calls the setter
print c.radius // Calls the getter (10)
Computed Properties (Read-Only)
If you define only a get block, the property becomes Read-Only. This is perfect for calculated values.
class User {
init(first, last) {
this.first = first
this.last = last
}
prop full_name {
get { return this.first + " " + this.last }
}
}
var u = new User("John", "Doe")
print u.full_name // "John Doe"
// u.full_name = "Jane" // Error: Property 'full_name' is write-only/read-only mismatch logic
Static Properties
Properties can also be static. They apply to the class itself.
class Settings {
private static _theme = "Dark"
static prop Theme {
get { return Settings._theme }
set(v) {
print "Changing theme to " + v
Settings._theme = v
}
}
}
Settings.Theme = "Light"
Encapsulation
Properties respect visibility modifiers (public, private, protected).
private prop x: Only accessible within the class.public prop y: Accessible from anywhere.
Methods & this
Methods are functions defined inside a class. They define what an object can do.
Defining Methods
Methods can have visibility modifiers (public, private, protected). If omitted, they are public by default.
class Rectangle {
// Public fields
public width = 0
public height = 0
init(width, height) {
this.width = width
this.height = height
}
// Public method
func area() {
return this.width * this.height
}
// Private method (internal helper)
private check_validity() {
if (this.width < 0) throw "Invalid width"
}
}
The this Keyword
Inside a method, the special variable this refers to the current instance. It allows you to access or modify the object's fields and call other methods.
var rect = new Rectangle(10, 20)
print "Area: " + rect.area() // 200
Inheritance
Inheritance allows a class (the Child) to derive behavior and properties from another class (the Parent).
In Aegis, inheritance is achieved using the extends keyword.
Basic Inheritance
Child classes inherit public and protected members from the parent. private members remain inaccessible to the child.
// The Parent Class
class Animal {
protected name // 'protected' allows children to access this field
init(name) {
this.name = name
}
public speak() {
print this.name + " makes a noise."
}
}
// The Child Class
class Dog extends Animal {
public fetch() {
// Can access 'this.name' because it is protected in Animal
print this.name + " runs after the ball!"
}
}
var d = new Dog("Rex")
d.fetch() // "Rex runs after the ball!"
d.speak() // "Rex makes a noise." (Inherited)
Method Overriding
A child class can provide its own implementation of a method.
class Cat extends Animal {
// Overriding the 'speak' method
speak() {
print "Meow!"
}
}
var c = new Cat("Luna")
c.speak() // "Meow!"
Accessing Parent Methods
Use super to call a method from the parent class. This is essential in the init method to ensure the parent is correctly initialized.
class Hero extends Entity {
init(name, hp) {
// 1. Call the parent constructor first
super.init(name)
// 2. Add child-specific logic
this.hp = hp
}
speak() {
return "Hero says: " + super.speak()
}
}
Polymorphism & Type Checking
is_instance checks the entire inheritance chain.
var d = new Dog("Buddy")
print is_instance(d, Dog) // true
print is_instance(d, Animal) // true (Dog is an Animal)
print is_instance(d, String) // false
Final Classes & Methods
You can restrict inheritance and overriding using the final keyword. This is useful for security, creating immutable utilities, or enforcing a specific design.
Final Classes
A final class cannot be extended. This effectively seals the class hierarchy at that point.
final class Virus {
init() {}
}
// ❌ Error: Class 'Corona' cannot inherit from 'Virus' because it is marked 'final'.
// class Corona extends Virus {}
Final Methods
A final method cannot be overridden by subclasses. This ensures that the behavior defined in the parent class remains consistent across all children.
class God {
final func create_universe() {
print "Big Bang!"
}
}
class Human extends God {
// ❌ Error: Cannot override final method 'create_universe' of class 'God'.
/* func create_universe() {
print "I made this."
}
*/
}
var h = new Human()
h.create_universe() // "Big Bang!"
Composition Pattern
Aegis promotes flexible design patterns. While inheritance creates rigid hierarchies ("is-a" relationship), Composition allows you to build complex objects by combining simpler ones ("has-a" relationship).
Example: Game Entity
Instead of a deep inheritance tree, you can compose an Player using a Vector and a Stats object.
// Component 1: Position
class Vector {
public x = 0
public y = 0
init(x, y) {
this.x = x
this.y = y
}
func str() { return "(" + this.x + ", " + this.y + ")" }
}
// Component 2: Stats
class Stats {
public hp = 100
public mana = 50
init(hp, mana) {
this.hp = hp
this.mana = mana
}
}
// Main Entity using Composition
class Player {
public name
public pos
public stats
init(name, x, y) {
this.name = name
// We initialize components inside the constructor
this.pos = new Vector(x, y)
this.stats = new Stats(100, 50)
}
func info() {
print this.name + " is at " + this.pos.str()
}
}
Usage:
var p = new Player("Hero", 10, 10)
p.info() // Hero is at (10, 10)
Modularity & System
A script is rarely a single file. To build scalable applications, code must be organized into logical units and spread across multiple files.
Aegis provides a robust module system:
- Namespaces: Grouping related logic to avoid naming conflicts.
- Imports: Loading code from other files with caching and scope isolation.
- Error Handling: Managing runtime exceptions gracefully with
try,catch, andthrow.
Namespaces
As your code grows, you might run into naming conflicts (e.g., two functions named init). Namespaces allow you to group related variables and functions under a unique name.
Defining a Namespace
Use the namespace keyword followed by a name and a block of code.
namespace Math {
var PI = 3.14159
func square(x) {
return x * x
}
func circle_area(r) {
// Accessing siblings: explicit access is recommended
return Math.PI * Math.square(r)
}
}
Accessing Members
You can access the contents of a namespace using the dot . notation, just like an object or a dictionary.
print Math.PI // 3.14159
var area = Math.circle_area(10)
print area // 314.159
Namespaces are Objects
Under the hood in Aegis v0.2, a Namespace is compiled as a Dictionary containing the local variables defined in its scope.
This means Namespaces are First-Class Citizens:
- They can be assigned to variables.
- They can be passed as arguments.
- They can be returned from a script (see Imports).
var M = Math
print M.square(5) // 25
Import System
To organize code across multiple files, Aegis provides a powerful import statement.
Syntax
var module = import "path/to/module.aeg"
The path is a string relative to the current working directory.
How Imports Work
When you import a file:
- Execution: The VM loads, compiles, and executes the file immediately.
- Scope Sharing: The imported file shares the global scope (native functions, etc.).
- Return Value: The
importexpression returns the last value evaluated in the script (ornullif nothing is returned). - Caching: Aegis caches the returned value. If you import the same file twice, it is not re-executed; the cached value is returned immediately.
Pattern 1: Global Inclusion (Legacy)
In this pattern, the imported file defines a Namespace or variables directly in the global scope.
File: lib/utils.aeg
namespace Utils {
func hello() { return "Hello Global!" }
}
File: main.aeg
import "lib/utils.aeg" // Returns null, but 'Utils' is now defined globally
print Utils.hello()
Pattern 2: The Module Pattern (Recommended)
To avoid naming conflicts (e.g., two libraries defining a Common namespace), it is best practice to return the namespace at the end of the file and assign it to a variable.
File: lib/math_v1.aeg
namespace Math {
func add(a, b) { return a + b }
}
// Export the namespace
return Math
File: lib/math_v2.aeg
namespace Math {
func add(a, b) { return a + b + 0.5 }
}
return Math
File: main.aeg
// We can now load two modules that have the same internal name
var M1 = import "lib/math_v1.aeg"
var M2 = import "lib/math_v2.aeg"
print M1.add(10, 10) // 20
print M2.add(10, 10) // 20.5
This pattern ensures your code remains modular and safe from global scope pollution.
Error Handling
Scripts can fail (missing files, network errors, bad math). Aegis provides a robust try/catch mechanism to handle these situations gracefully instead of crashing the Virtual Machine.
Try / Catch
Wrap risky code in a try block. If an error occurs, execution immediately jumps to the catch block.
try {
print "1. Doing something risky..."
var result = 10 / 0 // Division by zero error
print "2. This will not run."
} catch (error) {
print "3. Error caught: " + error
}
print "4. Program continues."
Output:
1. Doing something risky...
3. Error caught: Division by zero
4. Program continues.
Throwing Errors
You can raise your own errors using the throw keyword. You can throw strings or any other value.
func validate_age(age) {
if (age < 0) {
throw "Age cannot be negative!"
}
return true
}
try {
validate_age(-5)
} catch (e) {
print "Validation failed: " + e
}
Note: Aegis native modules (like File or Http) throw exceptions when operations fail. You should wrap I/O operations in try/catch blocks.
Standard Library
Aegis follows a "batteries-included" philosophy. The Standard Library provides a comprehensive set of modules to interact with the operating system, the network, and data formats.
These modules are built directly into the Aegis binary or shipped as core extensions.
Available Modules
| Module | Import Path | Description |
|---|---|---|
| System | stdlib/system.aeg | Args, Environment vars, CLI tools. |
| File | stdlib/fs.aeg | Read/Write files and Path manipulation. |
| Http | stdlib/http.aeg | Web client (GET, POST). |
| Json | stdlib/json.aeg | Parsing and stringifying JSON. |
| Math | stdlib/math.aeg | Advanced math and trigonometry. |
| Test | stdlib/test.aeg | Unit testing framework. |
System & Process
This module provides tools to interact with the operating system, environment variables, time, and external processes.
System
Import: import "stdlib/system.aeg"
| Function | Description |
|---|---|
System.args() | Returns a List of command-line arguments passed to the script. |
System.env(key) | Returns the value of an environment variable (or null). |
System.clear() | Clears the console screen. |
System.fail(msg) | Exits the program immediately with an error message. |
System.exit(code) | Exist the program immediately with an exit code. |
System.write(str) | writes the string passed as a parameter without moving to the next line. |
Time
Import: import "stdlib/time.aeg"
| Function | Description |
|---|---|
Time.sleep(ms) | Pauses execution for the specified milliseconds. |
Time.now() | Returns the current system timestamp (integer). |
Date
Import: import "stdlib/date.aeg"
| Function | Description |
|---|---|
Date.now() | Returns the current date as an ISO 8601 string. |
Date.format(fmt) | Returns the current date formatted (e.g., "%Y-%m-%d"). |
Process
Import: import "stdlib/process.aeg"
Allows you to run shell commands.
var output = Process.exec("git", ["status"])
print "Exit Code: " + output.get("code")
print "Stdout: " + output.get("stdout")
| Function | Description |
|---|---|
Process.exec(cmd, args) | Runs a command and returns a Dict {code, stdout, stderr}. |
Process.run(cmd) | Helper that prints stdout and returns true if successful. |
File System (IO)
The File System module allows reading from and writing to the disk.
File Operations
Import: import "stdlib/file.aeg"
| Function | Description |
|---|---|
File.read(path) | Reads the entire file content as a string. Throws if failed. |
File.write(path, content) | Writes string content to a file (overwrites). |
File.exists(path) | Returns true if the file or directory exists. |
Example
var config_path = "settings.ini"
if (File.exists(config_path)) {
var data = File.read(config_path)
print "Loaded: " + data
} else {
File.write(config_path, "defaults")
}
Path Manipulation
Import: import "stdlib/path.aeg"
It is highly recommended to use Path functions instead of concatenating strings manually, to ensure cross-platform compatibility (Windows vs Linux).
| Function | Description |
|---|---|
Path.join(a, b) | Joins two path segments (e.g., dir/file.txt). |
Path.extension(path) | Returns the file extension (e.g., txt). |
Path.exists(path) | Alias for File.exists. |
Network (HTTP)
Aegis includes a lightweight HTTP client for interacting with web APIs.
Import: import "stdlib/http.aeg"
Methods
Http GET
Syntax: Http.get(url)
Performs a GET request.
- Returns: The response body as a String.
- Throws: An error if the connection fails or status is not 2xx.
Http Post
Syntax: Http.post(url, body)
Performs a POST request.
- body: String (payload).
Example: Fetching an API
import "stdlib/http.aeg"
try {
var response = Http.get("https://api.github.com/zen")
print "GitHub Zen: " + response
} catch (e) {
print "Network Error: " + e
}
Data Handling
Aegis provides robust tools for data serialization and pattern matching.
JSON
Import: import "stdlib/json.aeg"
| Function | Description |
|---|---|
Json.parse(str) | Parses a JSON string into Aegis Lists/Dicts. |
Json.stringify(val) | Converts an Aegis value into a JSON string. |
Regex
Import: import "stdlib/regex.aeg"
var re = Regex.new("^[0-9]+$")
var is_digit = Regex.test(re, "12345") // true
| Function | Description |
|---|---|
| Regex.new(pattern) | Compiles a regex pattern. Returns an ID. |
| Regex.test(id, str) | Returns true if the string matches. |
| Regex.replace(id, str, repl) | Replaces matches with the replacement string. |
Crypto & Encoding
Import: import "stdlib/crypto.aeg"
| Function | Description |
|---|---|
| Base64.encode(str) | Encodes a string to Base64. |
| Base64.decode(str) | Decodes a Base64 string. |
| Hash.sha256(str) | Computes the SHA-256 hash (hex string). |
Math & Random
The Math module provides a collection of mathematical functions, constants, and a geometry class implemented directly in Aegis.
Import: import "stdlib/math.aeg"
Constants
The module exposes common mathematical constants.
| Constant | Value | Description |
|---|---|---|
Math.PI | 3.14159... | Ratio of a circle's circumference to its diameter. |
Math.TAU | 6.28318... | Equal to 2 * PI. |
Math.E | 2.71828... | Euler's number. |
Basic Utilities
Helper functions for everyday logic.
| Function | Description |
|---|---|
Math.abs(n) | Returns the absolute (positive) value of n. |
Math.max(a, b) | Returns the larger of two numbers. |
Math.min(a, b) | Returns the smaller of two numbers. |
Math.is_even(n) | Returns true if n is even. |
Math.is_odd(n) | Returns true if n is odd. |
Arithmetic & Algebra
Advanced calculation functions.
Power & Roots
Math.pow(base, exp): Calculatesbaseraised to the power ofexp.Math.sqrt(n): Calculates the square root ofnusing the Newton-Raphson approximation method.- Note: Returns
-1ifnis negative.
- Note: Returns
Number Theory
Math.gcd(a, b): Calculates the Greatest Common Divisor using the Euclidean algorithm.Math.lcm(a, b): Calculates the Least Common Multiple.
print Math.pow(2, 3) // 8
print Math.sqrt(16) // 4
print Math.gcd(12, 18) // 6
Trigonometry
Functions to handle angles and waves. These implementations use Taylor Series approximations.
Math.sin(x): Sine ofx(in radians).Math.cos(x): Cosine ofx(in radians).Math.tan(x): Tangent ofx(in radians).
Conversions
Math.to_radians(deg): Converts degrees to radians.Math.to_degrees(rad): Converts radians to degrees.
var angle = Math.to_radians(90)
print Math.sin(angle) // ~1.0
Random Module
The Random module provides utilities for generating random numbers and selecting items from collections.
Import: import "stdlib/random.aeg"
Number Generation
| Function | Description |
|---|---|
Random.int(min, max) | Returns a random integer where min is inclusive and max is exclusive. |
Random.float() | Returns a random floating-point number between 0.0 and 1.0. |
Collections
| Function | Description |
|---|---|
Random.choice(list) | Returns a random element from the provided list. Returns null if the list is empty. |
Example
import "stdlib/random.aeg"
// 1. Roll a die (1 to 6)
var dice = Random.int(1, 7)
print "Dice roll: " + dice
// 2. Random probability
if (Random.float() < 0.5) {
print "Heads!"
} else {
print "Tails!"
}
// 3. Pick a winner
var players = ["Alice", "Bob", "Charlie"]
var winner = Random.choice(players)
print "The winner is: " + winner
Socket (TCP Networking)
The Socket module provides low-level access to TCP networking. Unlike the Http module which is designed for high-level requests, Socket allows you to create your own servers or implement custom protocols.
Import: import "stdlib/socket.aeg"
Server Functions
To accept incoming connections, you must first create a listener.
| Function | Description |
|---|---|
Socket.listen(host, port) | Binds a TCP listener to the address. Returns a Server ID. |
Socket.accept(server_id) | Blocks execution until a client connects. Returns a Client ID. |
Client Functions
To connect to a remote server.
| Function | Description |
|---|---|
Socket.connect(host, port) | Establishes a connection to a remote address. Returns a Client ID. |
Data Transmission
These functions work with Client IDs (returned by accept or connect).
| Function | Description |
|---|---|
Socket.read(id, size) | Reads up to size bytes from the stream. Returns a String. |
Socket.write(id, data) | Writes the string data to the stream. |
Socket.close(id) | Closes the connection (or the listener). |
Example 1: Simple Echo Server
A basic server that listens on port 9000, reads a message, prints it, and closes.
import "stdlib/socket.aeg"
var server = Socket.listen("127.0.0.1", 9000)
print "Listening on 9000..."
while (true) {
// 1. Wait for connection
var client = Socket.accept(server)
print "Client connected!"
// 2. Read data (max 128 bytes)
var msg = Socket.read(client, 128)
print "Received: " + msg
// 3. Send response and close
Socket.write(client, "Message received.")
Socket.close(client)
}
Example 2: Building a Web Server
Since Aegis handles strings efficiently, you can implement a basic HTTP server in just a few lines of code.
import "stdlib/socket.aeg"
import "stdlib/time.aeg"
var HOST = "127.0.0.1"
var PORT = 8080
var server = Socket.listen(HOST, PORT)
print "HTTP Server running on http://" + HOST + ":" + PORT
while (true) {
var client = Socket.accept(server)
// Read the HTTP Request
var request = Socket.read(client, 1024)
// Simple parsing to get the path (e.g., "GET /home HTTP/1.1")
var parts = request.split(" ")
if (parts.len() > 1) {
var method = parts.at(0)
var path = parts.at(1)
print method + " " + path
// Build HTTP Response Body
var body = "<h1>Hello from Aegis!</h1>"
body += "<p>You requested: " + path + "</p>"
// Build Headers
var response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type: text/html; charset=utf-8\r\n"
response += "Content-Length: " + body.len() + "\r\n"
response += "Connection: close\r\n"
response += "\r\n" // End of headers
// Combine
response += body
Socket.write(client, response)
}
// Close the connection
Socket.close(client)
}
Testing Framework
Aegis includes a lightweight unit testing framework to help you write reliable code.
Import: import "stdlib/test.aeg"
Writing Tests
Use Test.run to define a test case. It creates a protected scope where errors are caught.
Test.run("Calculations", func() {
var sum = 10 + 20
Assert.eq(sum, 30, "Sum should be 30")
})
Assertions
The Assert namespace provides methods to validate conditions. If an assertion fails, the test stops and is marked as failed.
| Method | Description |
|---|---|
Assert.eq(a, b, msg) | Fails if a != b. Prints msg on failure. |
Assert.is_true(cond, msg) | Fails if the condition is false. |
Note: We use is_true instead of true to avoid conflict with the boolean keyword.
Example Output
TEST: Calculations...
✅ PASS
TEST: Bad Math...
❌ FAIL: Assertion Failed: 10 != 5 (Math is broken)
Advanced Topics
This section is for developers who want to understand how Aegis works "under the hood" or who wish to extend the language capabilities using Rust.
We will dive into:
- Architecture: The difference between the v0.1 Tree-Walk Interpreter and the current v0.2 Bytecode Virtual Machine.
- Native Extensions: How to write high-performance plugins in Rust (
.dll/.so) and load them into your Aegis scripts.
Aegis Architecture (VM vs Tree-Walk)
Aegis v0.2 marks a paradigm shift from a Tree-Walk Interpreter to a Bytecode Virtual Machine. This architecture is designed for speed, cache locality, and portability.
[Image of Aegis compiler pipeline source to bytecode]
The Pipeline
When you run a script (aegis run script.aeg), the following steps occur:
- Lexing (Scanner): The source code string is broken down into a stream of Tokens (Keywords, Identifiers, Literals).
- Parsing: The tokens are analyzed to build an Abstract Syntax Tree (AST). This represents the logical structure of the code.
- Compilation: The AST is traversed once to generate a flat Chunk of Bytecode. Control flow (if/while) is converted into Jump instructions.
- Execution (VM): The Virtual Machine executes the Bytecode linearly using a central Stack.
Stack-Based Virtual Machine
Aegis uses a Stack-Based architecture (similar to Java JVM or Python). There are no registers. Instructions push values onto the stack or pop them off to perform operations.
Example: 10 + 20
| Instruction | Stack State | Description |
|---|---|---|
LOAD_CONST 10 | [10] | Pushes 10 onto the stack. |
LOAD_CONST 20 | [10, 20] | Pushes 20 onto the stack. |
ADD | [30] | Pops 20 and 10, adds them, pushes 30. |
Memory Model
- Values: Aegis uses a compact
Valueenum (~24 bytes). Heavy objects (Functions, Classes, Lists) are stored on the Heap using Reference Counting (Rc<RefCell>), allowing for cheap copies and automatic memory management. - Call Frames: When a function is called, a new Frame is pushed. It tracks the function's instruction pointer and the offset for its local variables on the global stack.
Performance
The transition to v0.2 resulted in a massive performance boost (approx. 12x faster on heavy recursion).
- CPU Cache Friendly: Instructions are stored in a contiguous
Vec<u8>, reducing cache misses compared to traversing a pointer-heavy tree. - Fast-Path Optimization: Common operations (like Integer addition) are optimized to occur in-place on the stack without memory allocation.
Writing Native Extensions
Aegis is designed to be extensible. You can write high-performance modules in Rust and load them dynamically into your Aegis scripts.
Prerequisites
You need Rust installed. Create a new library project:
cargo new --lib my_plugin
Update your Cargo.toml to produce a dynamic library:
[package]
name = "my_aegis_package"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[[bin]]
name = "package"
path = "scripts/build_package.rs"
[dependencies]
# You must link against the core aegis library definition
aegis_core = { path = "../path/to/aegis_core" }
The Build Script
Create the file scripts/build_package.rs and paste the following code into it. You will need this to compile your package.
use std::env; use std::fs; use std::path::Path; use std::process::Command; fn main() { // 1. Récupération DYNAMIQUE du nom du projet let crate_name = env!("CARGO_PKG_NAME"); println!("📦 Packaging du projet : [{}]", crate_name); // 2. Détection de l'OS pour l'extension let (lib_prefix, lib_ext) = if cfg!(target_os = "windows") { ("", "dll") } else if cfg!(target_os = "macos") { ("lib", "dylib") } else { ("lib", "so") // Linux }; let lib_filename = format!("{}{}.{}", lib_prefix, crate_name, lib_ext); // 3. Lancer la compilation println!("⚙️ Compilation en cours (Release)..."); let status = Command::new("cargo") .args(&["build", "--release", "--lib"]) .status() .expect("Impossible de lancer cargo"); if !status.success() { eprintln!("❌ Erreur lors de la compilation Rust."); std::process::exit(1); } // 4. Préparation des dossiers (CHANGEMENTS ICI) let root_dir = env::current_dir().unwrap(); let dist_root = root_dir.join("dist"); // On crée le chemin : dist/<nom_du_paquet>/ let package_out_dir = dist_root.join(crate_name); println!("📂 Dossier de sortie : {:?}", package_out_dir); // Nettoyage de la version précédente de CE paquet uniquement if package_out_dir.exists() { fs::remove_dir_all(&package_out_dir).unwrap(); } // Création de l'arborescence complète fs::create_dir_all(&package_out_dir).unwrap(); // 5. Copie du binaire (.dll / .so) let target_dir = root_dir.join("target/release"); let src_lib_path = target_dir.join(&lib_filename); // Destination dans le sous-dossier let dest_lib_path = package_out_dir.join(&lib_filename); println!("📄 Copie du binaire : {}", lib_filename); if src_lib_path.exists() { fs::copy(&src_lib_path, &dest_lib_path) .unwrap_or_else(|e| panic!("Erreur copie DLL : {}", e)); } else { eprintln!("❌ Fichier introuvable : {:?}", src_lib_path); eprintln!(" Vérifiez le 'name' dans Cargo.toml"); std::process::exit(1); } // 6. Copie des scripts Aegis (contenu de /packages) let packages_dir = root_dir.join("packages"); if packages_dir.exists() { println!("📂 Copie des scripts Aegis..."); // On copie VERS le sous-dossier spécifique copy_dir_recursive(&packages_dir, &package_out_dir).expect("Erreur copie packages"); } else { println!("⚠️ Aucun dossier 'packages/' trouvé (seul le binaire sera distribué)."); } // 7. Génération du manifeste aegis.toml pour le paquet println!("📝 Génération du manifeste de paquet..."); // Noms théoriques des fichiers pour les autres OS let lib_name_linux = format!("lib{}.so", crate_name); let lib_name_windows = format!("{}.dll", crate_name); let lib_name_macos = format!("lib{}.dylib", crate_name); let toml_content = format!(r#"[package] name = "{}" version = "0.1.0" [targets] linux = "{}" windows = "{}" macos = "{}" "#, crate_name, lib_name_linux, lib_name_windows, lib_name_macos); let manifest_path = package_out_dir.join("aegis.toml"); fs::write(&manifest_path, toml_content).expect("Impossible de créer le manifeste"); println!("\n✅ SUCCÈS ! Votre package est prêt dans : dist/{}/", crate_name); } // Fonction utilitaire inchangée fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> { if !dst.exists() { fs::create_dir_all(dst)?; } for entry in fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; let src_path = entry.path(); let dst_path = dst.join(entry.file_name()); if ty.is_dir() { copy_dir_recursive(&src_path, &dst_path)?; } else { fs::copy(&src_path, &dst_path)?; } } Ok(()) }
The Entry Point
Aegis looks for a specific symbol _aegis_register to load your functions.
src/lib.rs:
#![allow(unused)] fn main() { use aegis_core::{Value, NativeFn}; use std::collections::HashMap; // This function is called by the VM when loading the plugin #[no_mangle] pub extern "C" fn _aegis_register(map: &mut HashMap<String, NativeFn>) { // Map an Aegis function name to a Rust function map.insert("my_hello".to_string(), hello_world); map.insert("my_add".to_string(), add_numbers); } // 1. A simple function fn hello_world(_args: Vec<Value>) -> Result<Value, String> { println!("Hello from Rust!"); Ok(Value::Null) } // 2. Handling arguments fn add_numbers(args: Vec<Value>) -> Result<Value, String> { if args.len() != 2 { return Err("Expected 2 arguments".into()); } // Use helper methods to safely cast values let a = args[0].as_int()?; let b = args[1].as_int()?; Ok(Value::Integer(a + b)) } }
Aegis Library File
Create a packages/my_plugin.aeg file to make it easy to use:
// Wrap in a Namespace
namespace MyPlugin {
func hello() {
return my_hello()
}
func add(a, b) {
return my_add(a, b)
}
}
Compiling
Build your plugin in release mode:
cargo run --bin package
This will generate a dist/<package_name>/ folder which contains the ready-to-use Aegis package:
- Shared Library Files (
.dllfor Windows,.sofor Linux and.dylibfor MacOS) - The Aegis files (
.aeg) created in thepackages/folder - An
aegis.tomlmanifest file automatically generated by the build script
Usage in Aegis
import "packages/my_plugin.aeg"
MyPlugin.hello() // Prints: Hello from Rust!
var sum = MyPlugin.add(10, 20)
print sum // 30
Aegis Cookbook
Welcome to the Aegis Cookbook. This section contains a collection of practical recipes and code snippets for common programming tasks.
Unlike the API Reference (StdLib), which explains what a function does, the Cookbook explains how to combine functions to achieve a specific goal.
Recipes
- File Processing
- Reading line-by-line
- Parsing CSV files
- Saving/Loading JSON configurations
- Networking
- Fetching API data
- Posting JSON data
- Handling HTTP errors
File Processing Recipes
This page covers common patterns for working with the file system.
Prerequisites:
import "stdlib/file.aeg"
import "stdlib/path.aeg" // Optional, for path manipulation
Reading a File Line-by-Line
Since File.read loads the entire content into memory, you can use split to iterate over lines.
var content = File.read("logs/server.log")
var lines = content.split("\n")
lines.for_each(func(line) {
// Skip empty lines
if (line.trim().len() > 0) {
print "Processing: " + line
}
})
Parsing a CSV String
Aegis handles string manipulation efficiently. Here is a helper function to turn CSV text into a List of Lists.
func parse_csv(data) {
var rows = data.split("\n")
// Use map to transform ["a,b", "c,d"] into [["a","b"], ["c","d"]]
return rows.map(func(row) {
return row.split(",")
})
}
// Example usage
var csv_data = File.read("data.csv")
// Assume content is: name,age\nAlice,30\nBob,25
var table = parse_csv(csv_data)
// Accessing Bob's age (Row 2, Column 1)
// Note: Row 0 is the header
print "Age of Bob: " + table.at(2).at(1)
Saving and Loading Configuration (JSON)
Combining the File module with the Json module is perfect for managing application settings.
import "stdlib/json.aeg"
var config_path = "settings.json"
// 1. Create default config
var config = {
theme: "dark",
volume: 80,
username: "Guest"
}
// 2. Save to disk
File.write(config_path, Json.stringify(config))
print "Configuration saved."
// 3. Load back
if (File.exists(config_path)) {
var loaded_raw = File.read(config_path)
var loaded_config = Json.parse(loaded_raw)
print "Welcome back, " + loaded_config.get("username")
}
Networking Recipes
Aegis is excellent for writing "glue code" that connects different web services. These recipes use the Http module.
Prerequisites:
import "stdlib/http.aeg"
import "stdlib/json.aeg"
Fetching JSON Data (GET)
This is the most common task: getting data from a REST API.
var url = "https://jsonplaceholder.typicode.com/todos/1"
try {
print "Fetching..."
var response = Http.get(url)
// Convert string response to Dict
var todo = Json.parse(response)
print "Task ID: " + todo.get("id")
print "Title: " + todo.get("title")
print "Done: " + todo.get("completed")
} catch (e) {
print "Request failed: " + e
}
Sending Data (POST)
To send data to a server, you typically need to stringify your payload first.
var url = "https://jsonplaceholder.typicode.com/posts"
// 1. Prepare data
var payload = {
title: "Aegis Language",
body: "Aegis is a cool new language.",
userId: 1
}
// 2. Send request
try {
var json_payload = Json.stringify(payload)
var response = Http.post(url, json_payload)
print "Server responded: " + response
} catch (e) {
print "Post failed: " + e
}
Simple Health Check Monitor
A script to check if a website is up.
func check_status(site) {
try {
var res = Http.get(site)
// If Http.get returns without throwing, the site is reachable
print "[OK] " + site
return true
} catch (e) {
print "[DOWN] " + site + " (" + e + ")"
return false
}
}
var sites = [
"https://google.com",
"https://github.com",
"https://invalid-url-example.com"
]
print "--- Starting Health Check ---"
sites.for_each(func(url) {
check_status(url)
})
System Automation Recipes
Aegis is great for replacing complex Bash scripts or Python glue code.
Prerequisites:
import "stdlib/system.aeg"
import "stdlib/file.aeg"
import "stdlib/date.aeg"
import "stdlib/path.aeg"
Daily Backup Script
This script copies a file and appends the current date to its name.
var source = "database.db"
var timestamp = Date.format("%Y-%m-%d") // e.g., 2023-10-25
var dest = "backups/database_" + timestamp + ".db"
if (File.exists(source)) {
print "backing up " + source + " to " + dest + "..."
// Read source
var content = File.read(source)
// Ensure directory exists (basic check)
// Ideally, use Process.run("mkdir -p backups")
// Write backup
File.write(dest, content)
print "✅ Backup complete."
} else {
print "❌ Source file not found!"
}
Environment Checker
Check if required environment variables are set before starting an app.
var required_vars = ["API_KEY", "DB_HOST", "PORT"]
var missing = 0
required_vars.for_each(func(key) {
var val = System.env(key)
if (val == null) {
print "❌ Missing ENV: " + key
missing = missing + 1
} else {
print "✅ " + key + " is set."
}
})
if (missing > 0) {
System.fail("Environment is not configured correctly.")
}
print "Starting Application..."