Modules

    Cyrus features a powerful module system designed for incremental compilation and efficient caching. Modules provide clear boundaries for scoping, code organization, and reusability.

    Module Files

    In Cyrus, every file is a module.

    • Naming: Files must use snake_case.
    • Extension: Files must end in .cyrus.
    • Visibility: By default, everything is private. Only symbols marked with the pub keyword are exported and accessible to other modules.

    Example: auth_utils.cyrus defines a module named auth_utils.

    // auth_utils.cyrus
    pub fn login(user: char*) {
        // ...
    }
    
    fn internal_helper() {
        // Not visible outside this file
    }
    

    Importing Modules

    You use the import keyword to bring other modules or specific symbols into scope.

    Basic Import

    Imports the entire module. Symbols are accessed via the :: namespace separator.

    import auth_utils;
    
    pub fn main() {
        auth_utils::login("Cyrus");
    }
    

    Single & Named Symbols

    You can selectively import symbols to avoid repeating the module name.

    import auth_utils{login};
    
    pub fn main() {
        login("Cyrus");
    }
    

    Grouped Imports

    For better readability, Cyrus supports grouping multiple imports. It is idiomatic to separate standard library/libc imports from local project modules.

    import (
        std::libc{printf},
        std::math{fabs}
    );
    
    import (
        auth_utils{login},
        network_ops as net
    );
    

    Renaming (Aliasing)

    Use the as keyword to rename modules or symbols to resolve naming conflicts.

    import math_ops as m;           // Rename a module
    import user_utils{greet as hi}; // Rename a symbol
    

    Module Resolution

    Cyrus resolves imports based on the environment and project configuration:

    1. Single File Compilation

    If you are compiling a single file directly, modules are resolved relative to the directory of that file.

    2. Project Compilation

    When compiling a project containing a Project.toml, the compiler resolves imports using the paths defined in the sources key.

    [compiler]
    sources = ["./src", "./vendor"]
    

    If you import my_mod, the compiler will search for my_mod.cyrus inside ./src and then ./vendor.

    3. The std Namespace

    Modules starting with std:: (like std::libc or std::math) are reserved for the Cyrus Standard Library and are resolved from the compiler's built-in library paths.

    The entry file (the file passed to the compiler as the main entry point) is considered the Root Module. While the root can import other modules, it cannot be imported by them to prevent circular dependencies at the top level.

    Nested Paths and Full Qualification

    Cyrus supports importing nested modules using the :: separator. You can also use the fully qualified path of a module directly in your code if you have imported its parent or the module itself.

    import std::libc;
    
    pub fn main() {
        // Accessing via the full path
        std::libc::printf("Hello World\n");
    }
    
    // Importing a specific symbol from a deep path
    import graphics::utils::canvas{draw_rect};
    

    Internal Namespaces

    While every file is a module, you can further organize code within a single file using the mod keyword. This creates a nested namespace.

    • pub mod: Accessible from outside the file.
    • mod: Private to the file where it is defined.

    Example: foo.cyrus

    import std::libc;
    
    pub mod graphics {
        // Available only inside the 'graphics' namespace
        const INTERNAL_SCALE = 1.0;
    
        // Available externally
        pub const DEFAULT_COLOR = 0xFFFFFF;
    
        pub fn render() {
            libc::printf("Rendering...\n");
            prepare(); // Internal call
        }
    
        fn prepare() {
            libc::printf("Preparing buffer...\n");
        }
    }
    
    mod internal_logic {
        // This whole namespace is private to foo.cyrus
    }
    

    Accessing Namespaces

    When importing a file, you can bring its internal namespaces into scope just like any other symbol.

    // main.cyrus
    import foo::graphics;
    import foo{graphics as gfx};
    
    pub fn main() {
        graphics::render();
        const c = graphics::DEFAULT_COLOR;
    
        // graphics::prepare();      // ERROR: prepare is not public
        // graphics::INTERNAL_SCALE; // ERROR: INTERNAL_SCALE is not public
        // foo::internal_logic;      // ERROR: internal_logic is private to foo.cyrus
    }
    

    Module Uniqueness and Conflicts

    Cyrus enforces a strict naming rule to prevent ambiguity in the module tree. A module name must be unique within its parent directory regardless of whether it is a file or a folder.

    A module cannot exist as both a file and a directory simultaneously.

    project/
      ├── auth.cyrus
      └── auth/
           └── index.cyrus
    
    
    ERROR: Conflict detected!
    

    If the compiler finds both auth.cyrus and a directory named auth/, it will trigger a Compile Error because the import path import auth; would be ambiguous.

    Directory Modules

    Cyrus allows you to treat a directory as a single module by using an index.cyrus file. This is useful for organizing a large module into multiple sub-files while exposing a clean top-level API.

    If a directory contains an index.cyrus, the directory name becomes the module name.

    Example Structure:

    my_lib/
      ├── index.cyrus
      ├── internal_math.cyrus
      └── internal_io.cyrus
    

    Inside my_lib/index.cyrus, you might export or define symbols:

    // my_lib/index.cyrus
    import my_lib::internal_math{add};
    pub fn run() { ... }
    

    When you import my_lib, you are actually interacting with the contents of index.cyrus:

    import my_lib;
    
    pub fn main() {
        my_lib::run(); // Accesses code from index.cyrus
    }
    

    This pattern is ideal for "facade" modules, where index.cyrus imports internal logic from other files in the same directory and re-exports the public API.