A scripting language for people who enjoy the simpler things in life.

Version 0.5.37


A module is a Pyro file loaded as a library. Modules are loaded using the import keyword.

Assume we have a file called math.pyro containing math functions:

import math;

var foo = math::abs(-1);

Use the member access operator :: to access a module's top-level variables, functions, and classes.

The module's name becomes a variable in the importing scope.


Modules can contain submodules. Assume we have a directory called math containing a file called trig.pyro:

import math::trig;

var foo = trig::cos(1);

The top-level module math can live alongside the directory that contains its submodules:

|-- math.pyro
|-- math/
    |-- trig.pyro

Alternatively, it can live inside the math directory in a file called self.pyro:

|-- math/
    |-- self.pyro
    |-- trig.pyro

Finally, the top-level math module can be empty and simply function as a container for its submodules:

|-- math/
    |-- trig.pyro

Submodules can contain submodules of their own — there's no hard limit to how deep the nesting can go.

Parents, Children, Siblings

Imagine we have the following module structure:

|-- math/
    |-- calc.pyro
    |-- self.pyro
    |-- trig.pyro

A parent module can import its child – e.g. in root/math/self.pyro:

import math::trig;

A child module can import its parent – e.g. in root/math/calc.pyro:

import math;

A child module can import its sibling – e.g. in root/math/calc.pyro:

import math::trig;

Circular Imports

In general, circular imports are fine — module foo can import module bar and module bar can import module foo.

Do be aware that trying to use a member from an imported module that hasn't finished initializing can result in a panic.

Note that you can avoid the problems that sometimes result from top-level circular imports by importing the required module inside a function, e.g.

def do_some_math(value) {
    import math;
    return math::abs(value) * math::pi;

This import will only fire when the function is run. (Imported modules are cached so the module won't be re-imported every time the function runs.)


You can import a module under an alias using an import ... as ... statement:

import math as alias;

var foo = alias::abs(-1);

Submodules can similarly be aliased:

import math::trig as alias;

var foo = alias::cos(1);

Importing Members

You can import a top-level member from a module by wrapping its name in braces, e.g.

import $std::math::{abs};

assert abs(-1) == 1;

You can alias the imported member using an import ... as ... statement, e.g.

import $std::math::{abs} as foo;

assert foo(-1) == 1;

You can import multiple members in a single import statement by separating their names with commas, e.g.

import $std::math::{cos, sin};

You can alias the imported members using a comma-separated as list, e.g.

import $std::math::{cos, sin} as foo, bar;

Import Roots

The global $roots vector contains the list of root directories that Pyro checks when attempting to import a module.

When Pyro is executing a script it adds the parent directory containing that script to the $roots vector, along with a modules directory within that parent directory. (This means that the modules required by a script can be located either in the same directory as the script or in a modules directory alongside the script.)

When Pyro is executing the REPL it adds the current working directory to the $roots vector.

You can add or remove entries from the $roots vector to customize Pyro's default behaviour.

Executing Module Directories

You can execute a module directory if it contains a self.pyro file:

$ pyro path/to/module/directory

Pyro executes the self.pyro file, then the $main() function if it finds one.