Skip to content

Latest commit

 

History

History
120 lines (85 loc) · 3.85 KB

09-modules.md

File metadata and controls

120 lines (85 loc) · 3.85 KB

09: Modules

Every Polyscript source file is a module. A module offers names to other modules with export, and takes names from them with import. There is no shared global namespace: a name is visible in a file only if that file declares it or imports it.

Exporting

An export statement lists the names a module makes available. Functions, structs, enums, traits, type aliases, and constants may all be exported. This is the whole of stdlib/process.ps:

import { endproc } from "@intrinsics";

fn exit(code: int): never {
    endproc(code);
}

export { exit };

Several names may be exported at once, and the statement may appear anywhere in the file. Placing it last, where it reads as a summary of the module's surface, is the convention the standard library follows.

const PI: float = 3.141592653589793;
const TAU: float = 6.283185307179586;
const E: float = 2.718281828459045;

fn area(radius: float): float { PI * radius * radius }

export { PI, TAU, E, area };

Whatever a module does not export stays private to it.

Importing

An import statement names the items to take and the file to take them from.

import { print, println, panic } from "stdlib/io.ps";
import { Vec } from "stdlib/vec.ps";
import { INT_MIN } from "stdlib/consts.ps";

Imported names are bound straight into the importing file's scope, and are used exactly as though they had been declared there. There is no module object and no qualified module.name form.

import { Vec } from "stdlib/vec.ps";

let numbers = [Vec<int>].new(); // Used unqualified.

Resolving Paths

How an import path is resolved depends on how it starts.

Path Resolved against
"stdlib/io.ps" The standard library directory.
"helpers.ps", "../shared/util.ps" The directory holding the importing file.
"@intrinsics" The compiler itself.
import { Vec } from "stdlib/vec.ps";     // From the standard library.
// import { helper } from "helpers.ps";  // From a file alongside this one.

A module that has already been loaded is not loaded again, so two modules may import each other without difficulty.

@intrinsics

@intrinsics is a module supplied by the compiler rather than by a file. It holds the primitive operations the language is built from: allocation, reference counting, raw character output, process control, and the mathematical operations that map onto hardware.

import { calloc, realloc, memcpy, free, decref, print_char, strlen, ref, deref } from "@intrinsics";

@intrinsics is available to the standard library, which is what allows the standard library to implement safe types like Vec and String on top of raw memory. Programs reach those operations through the types and functions the standard library exports rather than through @intrinsics directly. The full list is in the standard library reference.

Exports and Visibility Are Separate

Two mechanisms control access, and they answer different questions.

export decides which names leave a module.

public and private, described in Implementations, decide which members of a type are reachable, and they apply the same way everywhere — a private field is out of reach outside its own implementations, including within the file that declared it.

The two compose, which is how a module hands out a usable type while keeping its internals sealed.

struct Vec<T> {
    private size: int;
    private capacity: int;
    private data: int;
}

impl<T> Vec<T> {
    public fn length(self): int {
        self.size // Reachable: inside an implementation of `Vec<T>`.
    }
}

export { Vec };

Importers receive Vec and can call length, but size, capacity, and data remain unreachable. Exporting a type exports its public surface, never its private members.