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."

Aegis Version Build Status


⚡ 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, and filter.
  • 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:

  1. Installation: How to get the Aegis binary on your machine.
  2. Hello World: Writing and executing your first script.
  3. The REPL: Using the interactive shell for quick testing.
  4. 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 Git
  • Rust & 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.exe from rust-lang.org.

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:

TypeDescriptionExample
NullRepresents the absence of value.null
BooleanLogical true or false.true, false
Integer64-bit signed integer.42, -10, 0
Float64-bit floating point number.3.14, -0.01
StringUTF-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.

OperatorSyntaxEquivalent To
+=x += yx = x + y
-=x -= yx = x - y
*=x *= yx = x * y
/=x /= yx = 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

KeywordMatches
intIntegers
floatFloating point numbers
stringStrings
boolBooleans
listLists (Arrays)
dictDictionaries (Maps)
funcFunctions

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, null
  • list, dict, range, enum
  • function, 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.Idle is 0
  • Status.Running is 1
  • Status.Error is 2

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_CASE inside Namespaces.

    namespace Config {
        var MAX_RETRIES = 5
    }
    

File Structure

  • Use .aeg extension for all scripts.
  • Place reusable code in a lib/ or modules/ folder.
  • Wrap library code in a namespace to 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:

  1. Lists: Ordered, dynamic arrays of values.
  2. Dictionaries: Key-value mappings (HashMaps).
  3. 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.

MethodDescriptionExample
.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

MethodDescriptionExample
.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

MethodDescriptionExample
.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, and for_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:

KeywordScopeDescription
publicGlobalAccessible from anywhere. This is the default if no modifier is used.
protectedHierarchyAccessible only within the class and its subclasses.
privateInternalAccessible 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, and throw.

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 import expression returns the last value evaluated in the script (or null if 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()

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

ModuleImport PathDescription
Systemstdlib/system.aegArgs, Environment vars, CLI tools.
Filestdlib/fs.aegRead/Write files and Path manipulation.
Httpstdlib/http.aegWeb client (GET, POST).
Jsonstdlib/json.aegParsing and stringifying JSON.
Mathstdlib/math.aegAdvanced math and trigonometry.
Teststdlib/test.aegUnit 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"

FunctionDescription
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"

FunctionDescription
Time.sleep(ms)Pauses execution for the specified milliseconds.
Time.now()Returns the current system timestamp (integer).

Date

Import: import "stdlib/date.aeg"

FunctionDescription
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")
FunctionDescription
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"

FunctionDescription
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).

FunctionDescription
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"

FunctionDescription
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
FunctionDescription
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"

FunctionDescription
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.

ConstantValueDescription
Math.PI3.14159...Ratio of a circle's circumference to its diameter.
Math.TAU6.28318...Equal to 2 * PI.
Math.E2.71828...Euler's number.

Basic Utilities

Helper functions for everyday logic.

FunctionDescription
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): Calculates base raised to the power of exp.
  • Math.sqrt(n): Calculates the square root of n using the Newton-Raphson approximation method.
    • Note: Returns -1 if n is negative.

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 of x (in radians).
  • Math.cos(x): Cosine of x (in radians).
  • Math.tan(x): Tangent of x (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

FunctionDescription
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

FunctionDescription
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.

FunctionDescription
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.

FunctionDescription
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).

FunctionDescription
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.

MethodDescription
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:

  1. Architecture: The difference between the v0.1 Tree-Walk Interpreter and the current v0.2 Bytecode Virtual Machine.
  2. 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:

  1. Lexing (Scanner): The source code string is broken down into a stream of Tokens (Keywords, Identifiers, Literals).
  2. Parsing: The tokens are analyzed to build an Abstract Syntax Tree (AST). This represents the logical structure of the code.
  3. Compilation: The AST is traversed once to generate a flat Chunk of Bytecode. Control flow (if/while) is converted into Jump instructions.
  4. 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

InstructionStack StateDescription
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 Value enum (~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 (.dll for Windows, .so for Linux and .dylib for MacOS)
  • The Aegis files (.aeg) created in the packages/ folder
  • An aegis.toml manifest 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..."