Documentation / Core packages / Core IO

Core IO

io gives a Moth program console output and input polling. The prelude makes the bare io namespace available without an import.

Choose the explanation level: Console and input

Console and input

io gives your program console output and input polling. The prelude makes io available without an import, so you can start writing output right away.

Writing to the console

Console helpers take one string and write it to the output surface.


    io.print("loading...")
    io.line("Hello")
    io.debug("debug details")
    io.warn("warning")
    io.error("error")
    

io.line adds a trailing newline. io.print does not.

Formatting values

Console helpers accept strings only. Use a template to interpolate values into text before output.


    name = "Priya"
    io.line([: Hello, [name]])

    count = 3
    io.line([: count = [count]])
    

Reading input

Input polling lives under io.input.*. Create an input handle, poll it each frame, then close it when done.


    input ~= io.input.new() catch:
        assert(false, "input backend unavailable")
    ;

    io.input.update(~input)

    if io.input.key_pressed(input, "d"):
        io.line("pressed d")
    ;

    io.input.close(~input)
    

io.input.new() can fail when the backend does not support input. update and close need mutable access, so they use ~. Polling reads such as key_pressed use shared access.

Pointer input

Pointer helpers report button state and coordinates.


    if io.input.pointer_down(input, "left"):
        x = io.input.pointer_x(input)
        y = io.input.pointer_y(input)

        io.line([: pointer [x], [y]])
    ;
    

Buttons use "left", "middle" and "right". Coordinates use Float values. For the full contract including backend behaviour, teardown rules and deferred surfaces, see Core IO advanced.

@core/io owns console output and input polling. The prelude injects the bare io namespace as a compile-time alias to @core/io, so normal source files can call io.line(...) without an explicit import. An explicit import @core/io binds the same io namespace. An alias such as import @core/io as output creates a file-local namespace name.

@core/io belongs to the binding-backed Core package family. It exposes free functions and one opaque type. It does not expose source-defined receiver methods. See Package origins and backing for origin and backing rules.

Console helpers

Console helpers write string content to the backend output surface. Each helper takes one String value and returns nothing.

| Helper | Output lane | |---|---| | io.print(text) | plain output | | io.line(text) | output with a trailing newline | | io.debug(text) | debug-level output | | io.warn(text) | warning-level output | | io.error(text) | error-level output |

The output boundary accepts String values only. Templates produce String values, so template interpolation provides the way to format non-string content for output.


    io.print("loading...")
    io.line("Hello")
    io.debug("debug details")
    io.warn("warning")
    io.error("error")

    name = "Priya"
    io.line([: Hello, [name]])

    count = 3
    io.line([: count = [count]])
    

Passing a non-string value directly to a console helper fails at the output boundary. Interpolate the value into a template first.

Input polling

Input polling lives under io.input.*. The handle type io.input.Input stays opaque. Pass it to the input functions. Do not construct it with struct syntax or inspect its fields.

io.input.new() returns Input, Error! because backend input support can be unavailable. update and close require mutable access through ~. Polling reads use shared access.


    input ~= io.input.new() catch:
        assert(false, "input backend unavailable")
    ;

    io.input.update(~input)

    if io.input.key_pressed(input, "d"):
        io.line("pressed d")
    ;

    if io.input.pointer_down(input, "left"):
        x = io.input.pointer_x(input)
        y = io.input.pointer_y(input)

        io.line([: pointer [x], [y]])
    ;

    last_key = io.input.last_key_pressed(input)

    if last_key is |key|:
        io.line([: last key: [key]])
    ;

    io.input.close(~input)
    

Keyboard helpers

Keyboard helpers track down, pressed, released and last-edge states.

| Helper | Signature | Access | |---|---|---| | key_down(input, key) | Input, String -> Bool | shared | | key_pressed(input, key) | Input, String -> Bool | shared | | key_released(input, key) | Input, String -> Bool | shared | | last_key_pressed(input) | Input -> String? | shared | | last_key_released(input) | Input -> String? | shared |

Single alphabetic key strings normalise to lowercase. Space uses "Space". Special keys use logical names such as "ArrowLeft", "Enter", "Escape", "Backspace" and "Shift".

Pointer helpers

Pointer helpers track button state and position.

| Helper | Signature | Access | |---|---|---| | pointer_down(input, button) | Input, String -> Bool | shared | | pointer_pressed(input, button) | Input, String -> Bool | shared | | pointer_released(input, button) | Input, String -> Bool | shared | | pointer_x(input) | Input -> Float | shared | | pointer_y(input) | Input -> Float | shared | | last_pointer_pressed(input) | Input -> String? | shared | | last_pointer_released(input) | Input -> String? | shared |

Pointer buttons use "left", "middle" and "right". Pointer coordinates use Float values.

Teardown

io.input.close(~input) releases the input handle. Call it when the input handle no longer needs polling. The handle requires mutable access for close because teardown mutates the underlying host state.

Backend behaviour

HTML-JS maps console helpers to the browser console and input helpers to window and document-level keyboard and pointer polling. HTML-Wasm rejects reachable Core IO calls before lowering until it gains equivalent support.

Browser input events arrive through the host event loop. A synchronous infinite Moth loop can stop new input from arriving. Yield control back to the event loop between polling cycles.

Unsupported source forms

  • Do not construct Input with struct syntax.
  • Do not inspect Input fields directly.
  • Do not pass non-string values to console helpers.
  • Do not pass the io namespace record itself as a runtime value.
  • Direct symbol-path imports such as import @core/io/line face rejection. Use grouped or namespace imports.

Deferred IO surfaces

Deferred IO domains include filesystem and path IO, fetch and network IO, timers, sleep, intervals, frame and tick APIs, targeted DOM and canvas input sources, physical key-code APIs, typed key and button choices, text entry and IME or composition, touch gestures, gamepads, drag and drop, clipboard, wheel scrolling, file picker APIs, full ordered event queues, pressed and released collections, relative or DPI-scaled pointer coordinates, configurable default suppression and non-JS lowerings. See progress matrix for current target availability.

Related concepts