Modules
Modules in Axe provide a mechanism for organizing code, managing dependencies, and controlling visibility. They establish clear boundaries between different parts of a program and enable code reuse.
Module System Overview
Axe's module system is file-based with explicit imports. Each .axe or .axec file is a module, identified by its path relative to the project root.
project/
├─ main.axe # module: main
├─ utils.axe # module: utils
├─ src/
│ ├─ database.axe # module: src.database
│ └─ api.axe # module: src.api
└─ std/
├─ io.axec # module: std.io
├─ string.axec # module: std.string
└─ lists.axec # module: std.lists
Module Naming Convention
Module names are derived from file paths:
- File:
src/utils/string_helpers.axe→ Module:src.utils.string_helpers - File:
math.axe→ Module:math - File:
std/io.axec→ Module:std.io
Use Statements
Import modules and their exports with use statements:
// Import entire module
use std.io;
// Import specific symbols
use std.string (string, concat);
// Import from local module
use utils;
// Import multiple items
use std.lists (
StringList,
append,
contains
);
Use Statement Syntax
use <module_path> [( <symbol_list> )];
Examples:
// Import module (all public symbols available)
use std.io;
println("message"); // requires std.io.println
// Import specific symbols
use std.io (println);
println("message"); // can use directly
// Nested imports
use database (
connect,
disconnect,
Connection
);
// Import from project module
use src.api (get_user, User);
Public and Private Visibility
Use the pub keyword to make symbols public:
// Public: accessible from other modules
pub def calculate(x: i32, y: i32): i32 {
return x + y;
}
pub model Point {
x: i32;
y: i32;
}
// Private: only accessible within this module
def internal_helper(): string {
return str("internal");
}
model InternalState {
data: string;
}
Public Functions
/// Calculate the sum of two numbers
pub def add(a: i32, b: i32): i32 {
return a + b;
}
/// Calculate the difference
pub def subtract(a: i32, b: i32): i32 {
return a - b;
}
// Helper used only internally
def validate_numbers(a: i32, b: i32): bool {
return a >= 0 and b >= 0;
}
Public Models
/// Represents an HTTP response
pub model HttpResponse {
status_code: i32;
body: string;
headers: string;
}
/// Internal cache structure
model CacheEntry {
key: string;
value: string;
ttl: i32;
}
Module Organization
Single Responsibility
Each module should have a clear, focused purpose:
modules/
├─ http.axe # HTTP request/response handling
├─ json.axe # JSON encoding/decoding
├─ crypto.axe # Cryptographic functions
├─ database.axe # Database operations
└─ config.axe # Configuration management
File Structure
// mymodule.axe
// 1. File header with description
/// Module for managing user accounts
/// Handles authentication, profile management, and permissions
// 2. Use statements
use std.io;
use std.string;
use std.lists;
// 3. Public models and types
pub model User {
id: i32;
name: string;
email: string;
role: string;
}
pub model UserError {
code: i32;
message: string;
}
// 4. Internal types and globals
mut g_users: ref StringList;
mut g_max_id: i32 = 1000;
// 5. Public functions
pub def create_user(name: string, email: string): User {
// implementation
}
pub def get_user(id: i32): User {
// implementation
}
// 6. Private helper functions
def validate_email(email: string): bool {
// implementation
}
def hash_password(password: string): string {
// implementation
}
// 7. Tests
test {
// test cases
}
Creating Reusable Modules
Module Library Structure
my_library/
├─ axe.mod # Module metadata
├─ README.md # Documentation
├─ src/
│ ├─ core.axe # Core functionality
│ ├─ utils.axe # Utilities
│ └─ api.axe # Public API
└─ tests/
├─ test_core.axe
└─ test_api.axe
Example: Utility Module
/// src/utils.axe
/// Common utility functions
use std.string;
use std.io;
/// Check if a string is a valid email
pub def is_valid_email(email: string): bool {
if find_substr(email, str("@")) < 0 {
return false;
}
if find_substr(email, str(".")) < 0 {
return false;
}
return true;
}
/// Trim whitespace from string
pub def trim_whitespace(s: string): string {
mut start: i32 = 0;
mut end: i32 = str_len(s) - 1;
// Find first non-whitespace
for mut i = 0; i < str_len(s); i++ {
val ch: char = get_char(s, i);
if ch != ' ' and ch != '\t' {
start = i;
break;
}
}
// Find last non-whitespace
for mut i = str_len(s) - 1; i >= 0; i = i - 1 {
val ch: char = get_char(s, i);
if ch != ' ' and ch != '\t' {
end = i;
break;
}
}
return substring_se(s, start, end + 1);
}
/// Convert string to lowercase
pub def to_lower(s: string): string {
mut result: string = str("");
for mut i = 0; i < str_len(s); i++ {
val ch: char = get_char(s, i);
if ch >= 'A' and ch <= 'Z' {
result = concat_c(result, ch + 32);
} else {
result = concat_c(result, ch);
}
}
return result;
}
test {
assert is_valid_email(str("user@example.com")), "Valid email";
assert !is_valid_email(str("invalid-email")), "Invalid email";
}
Usage of Module
// main.axe
use src.utils (is_valid_email, to_lower, trim_whitespace);
use std.io (println);
def main() {
val email: string = str("User@Example.Com");
if is_valid_email(email) {
val lower: string = to_lower(email);
println lower;
}
}
Dependency Management
Using axe.mod
The axe.mod file declares module metadata and dependencies:
name: my-project
version: 0.1.0
entry: main.axe
license: MIT
description: Example Axe project
dependency: https://github.com/axelang/stdlib.git@abc1234
dependency: https://github.com/user/utils.git@def5678
Importing Dependencies
use std.io; // Standard library module
use my_utils; // Local module
use external_lib (
exported_function,
ExportedType
);
Module Best Practices
1. Minimize Public API
// Good: Small, focused public interface
pub def process(data: string): string {
return do_internal_processing(data);
}
// Internal implementation details
def do_internal_processing(data: string): string {
// complex logic
}
// Poor: Everything is public
pub def process(data: string): string { }
pub def internal_step_1(data: string): string { }
pub def internal_step_2(data: string): string { }
pub def internal_step_3(data: string): string { }
2. Document Public API
/// Process user input and return formatted result
///
/// Args:
/// input: raw user input string
///
/// Returns:
/// formatted result string
///
/// Errors:
/// Returns empty string if input is invalid
pub def process_input(input: string): string {
if str_len(input) == 0 {
return str("");
}
// implementation
}
3. Use Consistent Naming
// Good: Consistent, clear naming
pub def create_connection(host: string): Connection { }
pub def close_connection(conn: Connection): bool { }
pub def send_data(conn: Connection, data: string): bool { }
// Poor: Inconsistent naming
pub def new_conn(host: string): Connection { }
pub def kill_link(conn: Connection): bool { }
pub def transmit(conn: Connection, data: string): bool { }
4. Avoid Circular Dependencies
// Good: Acyclic dependency graph
core → utils → api → main
↓
services
// Bad: Circular dependencies
core ↔ utils
api ↔ main
utils ↔ api
5. Group Related Functionality
// Good: Related functions in one module
pub def create_user(name: string): User { }
pub def delete_user(id: i32): bool { }
pub def get_user(id: i32): User { }
pub def list_users(): ref StringList { }
// Poor: Scattered related functionality
// database.axe
pub def execute_query(query: string): string { }
// user.axe
pub def create_user(name: string): User { }
// auth.axe
pub def get_user(id: i32): User { }
Module Import Patterns
Selective Imports
// Import only what you need
use std.string (concat, substr);
use std.io (println);
def process() {
val result: string = concat(str("hello"), str(" world"));
println result;
}
Wildcard Imports
// Import all public symbols (use sparingly)
use std.io;
def process() {
println "Using all io symbols";
}
Nested Module Imports
use database.models (User, Post);
use database.queries (find_user, list_posts);
def load_user_posts(user_id: i32) {
val user: User = find_user(user_id);
val posts: ref StringList = list_posts(user_id);
}
Common Module Patterns
Facade Pattern
A module that simplifies access to complex subsystems:
// Good: Simple public interface hides complexity
pub def initialize_app(config_path: string): bool {
// Internally calls many setup functions
if !load_config(config_path) { return false; }
if !init_database() { return false; }
if !init_cache() { return false; }
if !load_plugins() { return false; }
return true;
}
// Internal complexity hidden
def load_config(path: string): bool { }
def init_database(): bool { }
def init_cache(): bool { }
def load_plugins(): bool { }
Repository Pattern
Encapsulate data access logic:
pub model User {
id: i32;
name: string;
email: string;
}
pub def find_user(id: i32): User {
// Database query logic
}
pub def save_user(user: User): bool {
// Database insert/update logic
}
pub def delete_user(id: i32): bool {
// Database delete logic
}
// Internal query building
def build_query(id: i32): string { }
def execute_query(query: string): string { }