# Modules & `require` `a` provides a sandboxed `require` for splitting a script into multiple files. It follows stock Lua's conventions — dot-separated module names, one execution per module, cached results — so editors and the Lua language server understand the imports without special setup. ```lua local helper = require "lib.helper" -- lib/helper.lua local db = require "db" -- db.lua or db/init.lua local mod, path = require "lib.helper" -- second value: the resolved file path ``` ## Module names A name is one or more dot-separated segments; each segment may contain `A–Z a–z 0–9 _ -`. Dots map to directory separators, so `require "db.migrations"` looks for `db/migrations.lua`. Paths (`/`, `..`), absolute names, and empty segments are rejected — a module can only ever be loaded from inside the search roots. ## Search roots Names are resolved against a fixed list of directories, in order: 1. **The main script's directory.** Symlinks to the script are resolved first, so a script symlinked into `~/.local/bin` still loads modules (and its `.env`, below) from next to the real file. In the REPL, the current working directory is used instead. 2. **Each entry of `$A_INCLUDE_PATHS`**, colon-separated like `$PATH`. In every root, two candidates are tried (the Lua language server's default templates): ``` /.lua //init.lua ``` The first hit wins. If a later root (or the `init.lua` form) also matches, a warning is logged about the shadowed file. When nothing matches, the error lists every path that was tried, in the stock Lua format. ## `.env` Before resolving anything, `a` loads a `.env` file from the main script's real directory (the CWD in the REPL), if one exists. Variables already set in the real environment take precedence. This is the intended place to set `A_INCLUDE_PATHS` for a project: ``` # myproject/.env A_INCLUDE_PATHS=/home/me/lua-libs:/opt/shared-lua ``` Since the script's symlink is resolved first, `myproject/main.lua` symlinked as `~/.local/bin/mytool` still picks up `myproject/.env`. ## Semantics - **A module file is a plain chunk.** Its `local`s are private to the file; it shares globals with the rest of the program; whatever it returns is the module's value (conventionally a table). Like stock Lua, the chunk receives the module name and the resolved file path as `...`. - **Executed once.** The return value is cached by the file's canonical path, so two names or symlinks reaching the same file share one instance, and `require` of an already-loaded module is just a table lookup. A module that returns nothing is cached as `true`. - **Return values.** `require` returns the module value and, for file modules, the resolved path as a second value. - **Built-ins.** `require "http"`, `require "utils"`, etc. return the same table as the corresponding global, so code written for stock Lua's habits works unchanged. - **Cycles.** A require cycle (`a` requires `b` requires `a`) raises a `circular require` error instead of recursing forever. - **Errors.** If a module raises while loading, the error propagates to the requiring code and nothing is cached — a later `require` retries the load. - Module files may use the async stdlib (`http`, `os.sleep`, …) at top level, and may start with a shebang and/or UTF-8 BOM, same as the main script. ## Editor / LSP setup The resolution rules match the [Lua language server](https://luals.github.io/) defaults, so a minimal `.luarc.json` next to your project gives go-to-definition and type inference across `require` boundaries: ```json { "runtime.version": "Lua 5.4", "runtime.path": ["?.lua", "?/init.lua"], "workspace.library": ["/home/me/lua-libs", "/opt/shared-lua"] } ``` List the same directories in `workspace.library` as in `A_INCLUDE_PATHS`; modules under the script's own directory are found via the workspace root. ## Example layout ``` myproject/ ├── main.lua -- #!/usr/bin/env a; symlinked to ~/.local/bin/mytool ├── .env -- A_INCLUDE_PATHS=... ├── .luarc.json └── lib/ ├── helper.lua -- require "lib.helper" └── db/ └── init.lua -- require "lib.db" ```