Modules & Build System

· updated 2026-05-06

CLEAR uses a simple, explicit module system based on the REQUIRE keyword. It separates code into Files, Packages, and Native Modules.

REQUIRE — Importing Code

Importing a CLEAR module makes its PUB and package-private symbols available under a namespace.

Local File Imports

Use a relative path string to import files in your own project:

REQUIRE "math_utils.cht";

FN main() RETURNS Void ->
    res = math_utils.add(10, 20);
END

Package Imports

Use the pkg: prefix to import registered libraries:

REQUIRE "pkg:geometry";

FN main() RETURNS Void ->
    p = geometry.Point{ x: 1, y: 2 };
END

Aliasing (AS)

You can rename any import to avoid collisions:

REQUIRE "math_utils.cht" AS m;
REQUIRE "pkg:geometry" AS geo;

FN main() RETURNS Void ->
    sq = m.square(geo.distance(p1, p2));
END

Visibility

CLEAR enforces three levels of visibility. Access control is checked at compile-time by the semantic annotator.

KeywordLevelVisibility
PRIVATEFile PrivateOnly accessible within the .cht file where it is defined.
(None)Package PrivateAccessible to any file in the same directory.
PUBPublicAccessible to any file that REQUIREs this module or package.

Example

# internal_helper.cht

PRIVATE FN secret_calc() RETURNS Int64 -> ... END

FN package_helper() RETURNS Int64 ->
    RETURN secret_calc();  # OK: same file
END

PUB FN public_api() RETURNS Int64 ->
    RETURN package_helper(); # OK: same package
END
# main.cht
REQUIRE "internal_helper.cht";

FN main() RETURNS Void ->
    internal_helper.public_api();     # OK: PUB
    internal_helper.package_helper();  # OK: same directory
    # internal_helper.secret_calc();  -- ERROR: PRIVATE
END

Future: STRICT Mode

In future versions (v0.2+), PUB declarations at a package boundary will require explicit effect declarations (e.g., EFFECTS :alloc) when compiled in STRICT mode. This ensures that library side-effects are always documented and verified.


Build System

CLEAR features a built-in dependency manager that handles recursive REQUIRE calls, prevents circular dependencies, and manages the transpilation graph.

Package Registration

Packages are mapped to file paths at compile-time using the --pkg flag:

ruby src/transpiler.rb --pkg geometry=./libs/geometry/lib.cht main.cht > main.zig

Module Transpilation

To compile a library as a standalone Zig module (without the CLEAR runtime footer), use the --module flag. This is useful when building CLEAR code to be consumed by native Zig applications.

ruby src/transpiler.rb --module my_lib.cht > my_lib.zig

How it Works

  1. Local Imports: The transpiler inlines the generated Zig code of the local file into a const struct namespace in the caller's Zig file.
  2. Package Imports: The transpiler emits a Zig @import("namespace"). The actual wiring of these modules is handled by the Zig build system using the --dep and -M flags.
  3. Deduplication: Each file is parsed and annotated exactly once, even if required by multiple files.
my_project/
├── main.cht            # Entry point
├── logic.cht           # Local module
└── lib/
    └── math/
        ├── lib.cht     # Package entry point (exports PUB symbols)
        └── private.cht  # Internal logic (default visibility)

Source: docs/modules.md