CLI reference

Every subcommand of bin/penelope.

Workflow

$ pen build [-O0|-O1|-O2] foo.pen       # source → foo.penc bytecode
$ pen exec foo.penc                      # run pre-compiled bytecode
$ pen run [-O0|-O1|-O2] foo.pen          # compile in memory + run (auto-build)
$ pen resume foo.penz [--time N] [--no-replay] [--event N=V]
$ pen fork src.penz dst.penz             # cp snapshot

Inspection

$ pen disasm foo.penc                    # pretty-print bytecode
$ pen inspect foo.penz                   # render v3 snapshot
$ pen check foo.pen                      # static type check
$ pen profile [-O0|-O1|-O2] foo.pen      # opcode counts + hot ips
$ pen bench foo.pen                      # compare -O0/-O1/-O2 timings
$ pen repl                               # interactive REPL
$ pen fmt [--write] foo.pen              # AST-based code formatter
$ pen test foo.pen                       # doctest runner (// EXPECT: lines)
$ pen run --watch foo.pen                # re-run on file change

Flags

FlagWhereEffect
-O0 / -O1 / -O2build, run, profileOptimization level (default -O1)
--time Nrun, resumeOverride now() to N (ms since epoch)
--no-replayrun, resumeSkip effect log; re-execute everything
--event NAME=VALresumeInject value for wait_for("NAME")
--watchrunRe-run on file change; clears screen between runs
--writefmtRewrite the file in place (without it: prints to stdout)
NO_COLOR=1 envanyDisable ANSI colors in error output

pen fmt

Idempotent formatter — fmt(fmt(x)) === fmt(x). Round-trips through the AST, so comments are dropped (they don't survive parsing).

$ pen fmt examples/10-sort.pen           # prints to stdout
$ pen fmt --write examples/10-sort.pen   # rewrites in place

pen test (doctest)

Annotate expected stdout lines with // EXPECT: <text> or // EXPECTS: <prefix>. pen test runs the program and asserts each emitted line matches in order.

// add.pen
print(to_str(1 + 2));
// EXPECT: 3
print("ok");
// EXPECT: ok
$ pen test add.pen
✓ add.pen  (2 expectations)

pen run --watch

Re-runs on file save. Clears the terminal between runs. Useful for tight feedback loops.

$ pen run --watch examples/09-fib.pen
watching examples/09-fib.pen (Ctrl-C to exit)

6765

[15:23:01] waiting for changes
# edit file...
6766

[15:23:14] waiting for changes

Error formatting

All compile-time and run-time errors come with a Rust-style source pointer:

$ pen run broken.pen
error: undefined variable 'foo'
  --> broken.pen:3:9
   |
 3 | let x = foo + 1;
   |         ^

Examples

# Build with optimizations, then run the bytecode directly
$ pen build -O2 examples/09-fib.pen
wrote examples/09-fib.penc (14 opcodes, 2 constants, -O2)
$ pen exec examples/09-fib.penc
6765

# Run + pause + resume cycle
$ pen run examples/07-wait-for.pen
waiting for approval
paused at ip 5 → examples/07-wait-for.penz

$ pen resume examples/07-wait-for.penz --event approval=true
got: true

# Profile a recursive program
$ pen profile examples/09-fib.pen
profile: examples/09-fib.pen (-O1, 15.47 ms)
opcode counts (top 20):
  LOAD_VAR            76618   22.6%
  BIN_OP              54726   16.1%
  ...

# Static type check
$ pen check broken.pen
type error: binop '+' requires int+int or str+str, got int+str at line 1 col 11
1 type error

# Interactive
$ pen repl
pen> let x = 42
pen> x * 2
84