Modules and Imports
Each source file defines a module containing its top-level bindings and executable initialization expressions.
Relative Source Imports
An import path resolves relative to the file containing the import:
from p"lib.dewy" import (answer add)
import p"lib.dewy" as library
Selective imports bind the requested names directly. as renames a selected binding. A namespace import retains qualification. Importing a path without a selection splats its top-level bindings into the current scope.
A bare name as the import source is a module of the library — import units, from units import (m kg), from linux.system import _exit for a subfolder — looked up by name in the compiler's library directory (a p"…" is always a file, relative to the importing one; a bare name that is a binding holding an exact path, let source = p"lib.dewy" then from source import …, is that path). The forms never collide, and a vendored library is a matter of which directory the lookup searches first.
The from path import names and import names from path orders are equivalent.
Import Sources
p is an ordinary prelude function producing a structural path value. Any exact compile-time object with the required string path field satisfies the source-import contract:
from [path="lib.dewy"] import answer
Source imports must be known while constructing the module graph. Runtime strings cannot select source modules.
Binding Kinds
Values, constants, functions, overload sets, and type aliases are importable. Imported type values remain compile-time values in the receiving module.
Namespace-qualified types can be constructed directly: library.Point[x=1 y=2]
or library.Point(1 2). Minting with type of creates identity at the
declaration, independently of spelling. Two modules may each declare a Token
family; their types remain distinct, while importing the same declaration
under another name preserves its identity.
Graph and Initialization
Reachable source modules share a coherent type environment, initialize once in dependency order, and reject unresolved names, cycles, and collisions. A source suffix is conventional and does not select different Dewy semantics.
Targets
$target is a compile-time string naming the backend (x86_64, riscv, arm, c, wasm32). Comparing $target with a string literal (=?, not =?, in? and not in? against a literal list, combined with not, and, or) folds during checking; an if whose condition is such a comparison skips its dead arms without checking them — they may import files that exist only for other targets — and splices the live arm's {} body into the enclosing scope so gated imports and declarations bind there. Plain literal conditions keep ordinary flow semantics.
$supported_targets = ["x86_64" ...] lists the backends a module accepts; compiling for another target is an error.
Prelude
Before checking an ordinary module, the compiler supplies a source prelude of shadowable bindings: paths, printing, rational and fixed numbers, time (the second and its scales, Duration), and the current target's services layer. Every other unit of measure is imported from the library module units. $no_prelude = true disables those implicit bindings for its containing module only. Imported modules retain their own prelude decision.
Provisional Package Facilities
Installed package lookup, directory or glob imports, non-source artifacts, project-wide freestanding policy, and domain-library naming remain provisional. They must extend rather than contradict file-relative module identity and one-time initialization.
Paths
p"…" is a path literal and p(text) builds a path at runtime; both are the prelude's Path, a value holding its text (.path). A path interpolates as its text (Path declares the conversion method __as__ = ():>string => path, so path as string is the text too), so p"{root}/{name}" is how paths are joined — there is no join method, since interpolation is strictly more flexible. The methods follow Python's pathlib:
- lexical:
name,stem,suffix(with its dot; a dotfile has none),parent,parts(a leading/is the root part),is_absolute,with_name(name),with_stem(stem),with_suffix(suffix); - the file system, every outcome a value:
exists,is_file,is_dir,list(the directory's entries),read_text(string | FileNotFound | FileAccessDenied | IsDirectory | FileExists | FileError | InvalidUtf8),read_bytes,write_text(text)andwrite_bytes(bytes)(the byte count or an error),mkdir,rmdir,unlink(trueor an error). Zero-argument methods are read like fields:source.parent.name.
let source = p"{project}/src/main.dewy"
match source.read_text {
text:string => compile(text)
<FileNotFound> => report"no such file: {source}"
_ => report"cannot read {source}"
}
let out = source.with_suffix(".udewy")
The same operations exist as free functions on a path's text (read_text(path:string), write_text, file_exists, is_file, is_directory, make_directory, remove_directory, remove_file), provided by the target's file-system module (library/linux/files.dewy).
Processes
The prelude runs programs as child processes (library/linux/process.dewy); every outcome is a value. program is a path — the kernel does no PATH search, so run("/usr/bin/env" ["python3" …]) is how to get one — and args are the arguments after it. A child inherits the environment, and a failed exec reports 127 like a shell would. A status is the exit code, or 128 + n when signal n ended the child; SpawnError means the child could not be started.
run(program args):>int64 | SpawnErrorwaits for the status; the child shares the standard streams.run_silentsends its stdout and stderr to/dev/null.spawn(program args):>Child | SpawnErrorstarts the child and returns it;child.waityields the status. Several children may run at once.capture(program args):>Output | SpawnErrorwaits with the child's output collected:status,stdoutandstderr(bytes, drained as the pipes fill, so a large output cannot block the child), andstdout_text/stderr_text(string | none: the bytes decoded,nonewhen they are not UTF-8).environment(name):>string | nonereads one of this process's own environment variables.
let main = ():>int64 => {
match capture("/bin/sh" ["-c" "echo out; echo err 1>&2; exit 3"]) {
result:Output => {
match result.stdout_text { text:string => printl"{text}" <none> => {} }
return result.status # 3
}
<SpawnError> => return 1
}
}
Still ahead: a working directory and a custom environment for the child, and reading a child's output as it runs.