Skip to content

Add readKeyTimeout primitive for non-blocking keystroke reads#45

Open
chapelsoftware wants to merge 2 commits into
sigilante:masterfrom
chapelsoftware:feat/readKeyTimeout
Open

Add readKeyTimeout primitive for non-blocking keystroke reads#45
chapelsoftware wants to merge 2 commits into
sigilante:masterfrom
chapelsoftware:feat/readKeyTimeout

Conversation

@chapelsoftware
Copy link
Copy Markdown

@chapelsoftware chapelsoftware commented Apr 15, 2026

Summary

Adds readKeyTimeout : ℤ → ⟨Bool, ℤ⟩ — a non-blocking keystroke read with
millisecond precision.

  • ⟨⊤, keycode⟩ when a key arrives within the timeout
  • ⟨⊥, 0⟩ when the timeout elapses first
  • Option-shape matches stdlib/option.goth convention

Implementation uses libc::poll under #[cfg(unix)], matching the existing
rawModeEnter / rawModeExit precedent (Unix-only terminal primitives,
not_implemented elsewhere).

Motivation

The current readKey primitive blocks indefinitely on stdin, which makes
timer-driven TUI programs impossible: tickers, blinking cursors, animated
displays, updatetime-style features, ambiguous-keybind timeouts (vim's
jk escape), autosave ticks. Every such program needs a read-with-timeout.

docs/PROJECT-STATUS.md lists "TUI demo compilation (tui_demo.goth)" as
active work. This primitive is the smallest step toward enabling it.

Demo

examples/tui/timer.goth: a live elapsed-seconds counter. space pauses,
q quits. Without readKeyTimeout, readKey would block and the counter
couldn't tick.

Elapsed: 7s  [running] (space=pause, q=quit)

Design notes

  • Argument unit is milliseconds: keeps the contract in the type with
    no hidden granularity footgun.
  • Result shape matches Option (⟨Bool, α⟩ per stdlib/option.goth):
    reuses the project's existing vocabulary rather than introducing a new one.
  • ◇io effect: the type signature's comment notes the ◇io annotation
    as aspirational — see "Parser note" below.
  • Argument clamping: negative timeouts clamp to 0 (non-blocking poll);
    values above i32::MAX saturate.

Test plan

  • Primitive returns ⟨⊤, keycode⟩ when a byte is piped in
  • Primitive returns ⟨⊥, 0⟩ when stdin stays open but quiet for the timeout
  • Zero-timeout returns immediately if input is ready
  • Negative timeout clamps to 0
  • cargo test --package goth-eval — 216/216 pass
  • cargo test --package goth-check — 1/1 pass
  • Regression-checked existing examples (pipeline, square, sha256) produce identical output
  • Interactive TTY test of examples/tui/timer.goth

Parser note (separate finding)

While writing this I noticed stdlib/tui.goth does not currently parse. A
minimal reproducer:

╭─ foo : () → ()
│  ◇io
╰─ print "hi"

fails with Unexpected token: Some(Diamond), expected identifier. This
contradicts docs/EFFECT-SYSTEM-ROADMAP.md, which lists "Lexer/Parser: The
token… recognized in function box middle lines" under "What Works".
Because of this, I intentionally did not add │ ◇io annotations to the
new primitive's stdlib wrapper and kept the demo using primitives
directly. Happy to submit a follow-up PR targeting the parser if useful.

Scope

No stdlib or interpreter behavior changes outside the new primitive and
its registration. No dependencies added (libc was already a target-cfg(unix)
dep). No new error variants.

🤖 Generated with Claude Code

Ben Woodring and others added 2 commits April 14, 2026 20:30
Adds readKeyTimeout : ℤ → ⟨Bool, ℤ⟩ — reads a single byte from stdin
with a millisecond timeout. Returns ⟨⊤, keycode⟩ if a key arrives
within the timeout, ⟨⊥, 0⟩ if it elapses first. Option-shape matches
stdlib/option.goth convention.

Implementation uses libc::poll under #[cfg(unix)], matching the
existing rawModeEnter / rawModeExit precedent (Unix-only terminal
primitives, Err(not_implemented) elsewhere).

Unblocks timer-driven TUI programs — tickers, blinking cursors,
animated displays, updatetime-style features, ambiguous-keybind
timeouts — that are impossible with the blocking readKey.

Includes examples/tui/timer.goth: a live elapsed-seconds counter
that pauses with space and quits with q. Without readKeyTimeout,
readKey would block and the timer couldn't tick.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Add three unit tests for readKeyTimeout error paths (arity 0, arity 2,
  wrong type). Success/timeout paths depend on stdin state and are left
  for interactive verification, as reflected in the test names.
- Reword the builtins.rs comment to accurately describe the ◇io
  annotation's current status — "not currently accepted by the parser"
  rather than the earlier "parsed but not enforced," which contradicted
  the PR's own finding.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant