P1SCode — Modules, imports, and the std story

This page is the contract-oriented story for how modules work today and how virtual (“std-like”) namespaces are reserved for the future. It complements the tutorial tone of P1SCode Language Guide and the stack view in P1SCode — Architecture (language & SDK).


1. Two kinds of imports

1.1 File imports (project modules)

import "lib/bump";
import "utils/helpers";

Resolution (same rules in p1sc.py and p1sc_syntax::import_resolve):

  • Paths are relative to the directory of the file that contains the import.

  • Try join(base_dir, target) as a path, then the same path with .p1s appended.

  • Cycles: revisiting an already-loaded path during merge yields an empty chunk for that edge (no infinite recursion).

After merge, the import "…" line is not present in the merged compilation unit; dependency text is prepended, then the importer’s non-import lines.

1.2 Virtual imports (no file on disk)

An import target is virtual if it starts with any of:

Prefix

Role today

os/

Used in practice for import "os/sys" so user code can call sys.* (transpiler wired). No file os/sys exists.

std/

Virtual merge (strip import line). Subpath std/io documents and enables io.* in p1sc.py — see §3.

p1/

Reserved for platform/SDK surface (p1window-class ideas). Same as std/ for merge: strip only.

Implementation: _is_virtual_import in p1sc.py and is_virtual_import in SDK/p1sdk/p1sc_syntax/src/import_resolve.rs — keep them in sync when adding a prefix.


2. Why import "os/sys" but sys.print in code?

The string inside quotes is only the module path key for the import machinery and documentation. The transpiler recognizes sys, math, str, fs, crypto, ui, and (after import "std/io") io as top-level spellings in source (see P1SCode Language Guide §9).

So today:

  • import "os/sys" is the conventional way to say “I use the syscall-facing sys surface.”

  • There is no separate file that defines sys; it is provided by codegen and boilerplate.

import "std/io" also introduces the io top-level (same convention as sys after import "os/sys") — see §3.


3. std/io — standard I/O module

This section defines std/io and the io.* surface. Virtual import behavior is unchanged (§3.1). The transpiler (p1sc.py transpile()) implements every io.* member in §3.3 (including io.read_line on stdin / fd 0).

3.1 Import

import "std/io";
  • Merge: line is removed (virtual import); like os/sys, there is no std/io.p1s on disk.

  • Intent: programs that import "std/io" may call io.… APIs below without pulling in os/sys explicitly for those calls.

3.2 Scope: what “I/O” means here

Stream / concept

P1Start notes

Standard output

Process fd 1; line-oriented print is the primary portable hook (today: sys.print).

Standard input

Process fd 0; io.read_line() uses syscall read (see Syscalls reference (curated)) in a loop; behavior follows the host’s PTY/pipe stdin story.

Standard error

Often fd 2; may start as alias of stdout for simple programs unless split is required.

Filesystem

Paths under the VFS; binary vs text is a real distinction (today: fs.read_file vs fs.read_text, sys.read_file).

3.3 API surface (io.*)

Names and signatures are proposed; tweak only with a fixture + this doc update. Codegen status is noted per row.

Member

Role

Notes

io.print(expr)

Print a line (string or debug-format)

Implemented — mirrors sys.print (fd 1); primary portable “console out” for std/io users.

io.print_err(expr)

Print to stderr (if distinct)

Implemented — mirrors print on fd 2.

io.read_line()

Read one line from stdin

Implemented — read(0, …) until newline or EOF; strips a trailing CR; String::from_utf8_lossy. No arguments.

io.read_file(path)

Read entire file as text

Implemented — same lowering as sys.read_file.

io.read_text(path)

Read entire file as text

Implemented — same lowering as fs.read_text.

io.write_file(path, data)

Write text file

Implemented — same lowering as sys.write_file.

io.read_bytes(path)

Read file as raw bytes

Implemented — same lowering as fs.read_file (Vec<u8>).

io.write_bytes(path, bytes)

Write raw bytes

Implemented — same lowering as fs.write_file.

io.sleep(ms)

Sleep

Implemented — same lowering as sys.sleep.

io.execute(path)

Exec helper

Implemented — same lowering as sys.execute.

Non-goals for an initial std/io drop:

  • Buffered stdio with flush modes (can stay sys-level later).

  • Async I/O (language has no async contract).

3.4 Relationship to sys and fs

Today (works in transpiler)

Role

sys.print, sys.read_file, sys.write_file, sys.sleep, sys.execute

Syscall-forward helpers; import "os/sys".

fs.read_file, fs.read_text, fs.write_file

Binary vs text file emphasis.

After std/io, io.read_file / io.write_file (and siblings) share codegen with sys/fs so behavior does not diverge without an RFC-style doc change.

3.5 Mapping: io.*, sys.*, and fs.*

You want (portable intent)

Prefer

Print line

import "std/io" → io.print(…) or import "os/sys" → sys.print(…)

Print to stderr

io.print_err(…)

Read whole file as string

io.read_file(path) or sys.read_file(path) or fs.read_text(path)

Read raw bytes

io.read_bytes(path) or fs.read_file(path)

Write text file

io.write_file(path, data) or sys.write_file(path, data)

Write bytes

io.write_bytes(path, buf) or fs.write_file(path, buf)

Sleep / execute

io.sleep / io.execute or sys equivalents

Read one line from stdin

io.read_line()

3.6 Implementation checklist (std/io)

When you extend io.* in p1sc.py:

  1. [x] Add io. token patterns mirroring sys. / fs. (§3.3).

  2. [x] Add fixtures std_io_print.p1s, std_io_read_line.p1s, std_io_print_err.p1s, std_io_sleep.p1s (import "std/io").

  3. [x] Update P1SCode — Language reference (concise) builtins table.

  4. [x] Native p1sc_syntax: merge + parse tests cover import "std/io" (see import_resolve::virtual_import_std_io_stripped, fixture_std_io_* in lib.rs); p1sc.elf remains parse/check-only until a Rust transpiler exists — no ABI change for check when syntax is unchanged.

  5. [x] Document fd 0 / io.read_line in newdocs/syscalls-reference.md (read row).


4. Host-only: import_root / stdin

For unsaved buffers, Python supports --stdin-source, --stdin-path, and --stdin-import-root. The root frame of merge uses import_root as base_dir for resolving file imports; nested imports still use dirname(resolved path).

Native p1sc.elf exposes --import-root for the same root-frame semantics.


5. What “a P1SCode standard library” means (roadmap)

Not contract today:

  • A single std tree of .p1s shipped on the ramdisk that the compiler loads like Rust’s std.

Current pattern:

  • Virtual prefixes declare intent and strip duplicate-import clutter after merge.

  • Builtin behavior is largely transpiler specials mapping to cstd (and friends).

  • Project code shares logic via file imports under lib/ (template pattern).

Recommended evolution:

  1. Document each virtual submodule as it gets transpiler + (optional) parser support.

  2. Add fixtures for every new std/... or p1/... import path before claiming stability.

  3. Consider SDK/lib/p1scode_std/ (or similar) as real .p1s only when the driver can load both virtual and file-based std without ambiguity—until then, prefer virtual + codegen for thin layers.


6. Checklist when adding a module story

  • [ ] Update import_resolve virtual set if new prefix.

  • [ ] Update p1sc.py _is_virtual_import to match.

  • [ ] Teach transpiler (and language guide / reference) any new xyz. surface.

  • [ ] Add fixture .p1s + cases.json or error_cases.json as appropriate.

  • [ ] Extend P1SCode — Language reference (concise) builtins table if user-facing.


See also