Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

io - input/output

Enable with use io;. It provides formatted output - printf (stdout), eprintf (stderr), and sprintf (to a string), which share a Go-style format-string mini-language - and stdin input: readLine / eof for lines, readBytes / readChars for binary.

printf

Writes formatted output to standard output. Three calling shapes:

io.printf("hi\n");                              # literal string (no verbs)
io.printf($x);                                  # single non-string value, displayed
io.printf("you are %d years old!\n", $age);     # format string + arguments
io.printf("%s = %d, ok=%t\n", "answer", 42, true);

Never pass a dynamic string as the format argument. A single string argument is always the format string, so io.printf(s) scans s for verbs. That is safe only for a string you wrote. For any value you did not author - a generated password, user input, file contents - use io.printf("%s", s) (or "%s\n"). A stray % in the data would otherwise be read as a verb: %c fails as an unknown verb, %s as a missing argument, so io.printf(password) is a latent bug. This is deliberate - there is no separate verbatim-print builtin (see technical/rejected.md); printf("%s", s) is the one canonical way to print a dynamic string.

eprintf

Exactly like printf - same argument forms and verbs - but writes to standard error instead of standard output. Use it for diagnostics, warnings, and logs that must not mix into a program’s stdout data (so a pipeline consuming the stdout stays clean).

io.eprintf("warning: retrying (%d/%d)\n", $attempt, $max);   # goes to stderr

sprintf

Same arguments as printf but returns the formatted string instead of writing it.

def msg as string init io.sprintf("%d + %d = %d", 1, 2, 3);
io.printf("%s\n", $msg);   # "1 + 2 = 3"

Format verbs

VerbRequired kindNotes
%dintdecimal
%ffloatshortest round-trip
%sstringraw
%tbooltrue / false
%vanyuses the value’s display form
%%-literal %

Mismatches (wrong verb for the value kind, too few or too many args, dangling %, unknown verb) all produce runtime errors.

Escaping the meta-characters:

  • A literal % in any string passed to printf/sprintf must be doubled to %%.
  • A literal | immediately after a verb must be doubled to ||, because | otherwise starts a modifier list (see Format modifiers). The escape consumes one of the two |s; the other appears in the output. Pipes that don’t touch a verb are normal characters and need no escaping: io.printf("a|b %s||c|d\n", "X") prints a|b X|c|d - the || after %s is the escape, while the |s in a|b and c|d sit between non-verb characters and pass through unchanged.

Format modifiers

Each verb (except %v) accepts an optional pipe-separated modifier list:

%verb[|key=value]*

Modifiers are order-independent flags - %d|pad=5|fill=0|align=right and %d|align=right|fill=0|pad=5 produce the same output. The list runs left-to-right until it hits a byte that isn’t part of a key or value. To put a literal | immediately after a verb, double it: || writes one | and ends the modifier list (same shape as %%). Unknown keys, bad values, missing companions (e.g. group= without sep=), and the same key set twice are all runtime errors.

Evaluation order within one verb:

  1. Null check. If the value is null and the spec includes a null= modifier, the verb-specific render is skipped and the configured replacement is used.
  2. Verb-specific render. mode, base, prec, sci, sign, group/sep, case apply here.
  3. Layout. max truncates (rune-aware), then pad+fill+align extends. Layout still applies to the null replacement, so columns line up.

null= (shared by %s, %d, %f, %t)

FormOutput when value is null
null=empty""
null=null"null"
null=literal("X")X - the quoted text, with Jennifer string escapes parsed

Without a null= modifier, a null value is still a type-mismatch error against any verb except %v. null= wins over every other modifier on its verb: %s|mode=quote|null=literal("X") on a null prints X, not "X".

%s modifiers

KeyValuesDefaultEffect
padnon-negative integer-minimum rune width
maxnon-negative integer-truncate to N runes
alignleft, right, centerleftwhich side gets the pad spaces; center splits the pad evenly (odd leftover goes right)
moderaw, quote, escaperawwrap in "..." (quote) / show escapes (escape)
nullsee above-substitute when value is null

mode=quote wraps the string in double quotes and escapes interior \, ", and control bytes. mode=escape does the same escaping without the wrapping - useful for showing a string’s structure in debug output.

io.printf("[%s|pad=8|align=right]\n", "hi");        # [      hi]
io.printf("[%s|max=3]\n", "abcdef");                # [abc]
io.printf("%s|mode=quote\n", "a\nb");               # "a\nb"

%d modifiers

KeyValuesDefaultEffect
padnon-negative integer-minimum width
fill0spacezero-pad between sign and digits; requires align=right (the default)
alignleft, rightrightwhich side gets the pad
base2, 8, 10, 1610digit base; hex uses lowercase
signnegative, always, spacenegativesign for non-negative values
grouppositive integer-digit-group size, reading right-to-left
sepone of _, ,, ., -, :-group separator; required with group= and vice versa
nullsee above-substitute when value is null
io.printf("%d|base=2\n", 5);                                # 101
io.printf("%d|base=16|group=4|sep=_\n", 3735928559);        # dead_beef
io.printf("%d|pad=5|fill=0|sign=always\n", 42);             # +0042

%f modifiers

KeyValuesDefaultEffect
precnon-negative integershortestfraction digits (or mantissa fraction digits when sci=true)
trimtrue, falsefalsestrip trailing fraction zeros and the . if all zero
scitrue, falsefalseforce scientific notation (1.23e+03)
padnon-negative integer-minimum width
alignleft, rightrightwhich side gets the pad
signnegative, always, spacenegativesign for non-negative values
nullsee above-substitute when value is null
io.printf("%f|prec=2\n", 3.14159);              # 3.14
io.printf("%f|prec=4|trim=true\n", 3.0);        # 3
io.printf("%f|sci=true|prec=2\n", 0.00123);     # 1.23e-03

%t modifiers

KeyValuesDefaultEffect
caselower, upper, titlelowertrue/false, TRUE/FALSE, True/False
nullsee above-substitute when value is null

%v modifiers

%v takes no modifiers - it is deliberately the “I don’t care, just print it” verb. Use a typed verb plus modifiers when you want to shape the output.

%a modifiers

%a is the aggregate verb: it renders a list or map in literal-like shape, recursing into nested aggregates. Non-collection input is a runtime error.

KeyValuesDefaultEffect
sepquoted string", "element separator (between list items, between map entries)
kvquoted string": "key/value separator for map entries
openquoted string[ (list) / { (map)opening bracket
closequoted string] (list) / } (map)closing bracket
depthnon-negative integerunlimitedmax recursion depth; deeper levels collapse to [...] / {...} (depth=0 collapses at the top)
nullskip-omit null list elements and null map values

Modifier values can be double-quoted to include spaces, brackets, or other characters reserved by the modifier-list grammar. The escape set is the standard \n \r \t \\ \":

def xs as list of int init [1, 2, 3];
io.printf("%a\n", $xs);                              # [1, 2, 3]
io.printf("%a|sep=\" | \"\n", $xs);                  # 1 | 2 | 3 (with brackets)
io.printf("%a|open=\"<\"|close=\">\"\n", $xs);       # <1, 2, 3>

def grid as list of list of int init [[1, 2], [3, 4]];
io.printf("%a\n", $grid);            # [[1, 2], [3, 4]]
io.printf("%a|depth=1\n", $grid);    # [[...], [...]]

def m as map of string to int init {"a": 1, "b": 2};
io.printf("%a\n", $m);                       # {a: 1, b: 2}
io.printf("%a|kv=\"=\"|sep=\" \"\n", $m);    # {a=1 b=2}

Per-element rendering uses the same display form as %v, so primitive values inside an aggregate look the way they would in a print statement. null=skip is only valid on %a - it omits null elements entirely; the other null= modes (empty, null, literal) are rejected for %a because they don’t have a sensible per-element meaning.

Input from stdin

Three builtins for reading lines from standard input. They are output-symmetric with printf / sprintf and intentionally minimal - line at a time, with an explicit end-of-input predicate so nothing happens implicitly.

io.readLine() -> string

Read one line from stdin. The trailing \r\n or \n is stripped; the returned string never carries a newline. Calling at end-of-input is a positioned runtime error (readLine: end of input), so the caller must check io.eof() first.

A final line that has no trailing newline is returned normally on the call that reaches it; the subsequent call errors.

io.readLine(prompt) -> string

Same as io.readLine() but writes prompt to stdout (no newline added) before reading. The prompt is written unconditionally, even when stdin is piped - explicit beats silently skipping the prompt off a non-TTY.

def name as string init io.readLine("name: ");
io.printf("hi, %s\n", $name);

io.eof() -> bool

True if and only if the next io.readLine() (or io.readBytes / io.readChars) would return less than requested. Implemented by peeking one byte through a buffered reader; the byte stays in the buffer for the next read. Once true, io.eof() stays true for the rest of the run.

io.readBytes(n) -> bytes

Reads exactly n bytes from stdin and returns them as a bytes value. If EOF is hit before n bytes are available, returns the partial result and io.eof() becomes true on the next call. n must be a non-negative int.

use io;
def first as bytes init io.readBytes(8);   # exactly 8 bytes or less at EOF
io.printf("got %d bytes\n", len($first));

io.readChars(n) -> string

Reads exactly n Unicode code points from stdin, decoded from UTF-8, and returns them as a string. Same EOF behavior as readBytes (partial result, sticky io.eof()). n is a rune count, not a byte count - one Unicode character can be 1-4 bytes wide.

use io;
def first as string init io.readChars(3);  # exactly 3 runes
io.printf("got %d runes (%d bytes)\n", len($first), 0);  # len = 3

Canonical loop

use io;
while (not io.eof()) {
    def line as string init io.readLine();
    io.printf("[%s]\n", $line);
}

This is the only pattern the language asks you to learn. There is no for line in stdin shortcut, no lines() that slurps the whole stream, and io.readLine() does not return a sentinel value at EOF - they were considered and rejected because the existing trio is already complete and adding parallels would violate Jennifer’s “one way per thing” stance.

REPL limitation

The interactive REPL owns stdin via its line editor, so readLine and eof both refuse inside the REPL with a clear error:

readLine: stdin is owned by the REPL editor

A proper side-channel for REPL input is a future milestone. To play with the input functions today, put your program in a .j file and run it with stdin piped or redirected: jennifer run prog.j < input.txt.

Float display

Floats always display with a decimal point so the value’s type stays visible: 5.0 prints as "5.0", not "5". That matters most after the Python 3 division change - 4 / 2 is the float 2.0, and you can tell at a glance rather than wondering whether it’s an int.

See also: ../user-guide/index.md, ../technical/interpreter.md, index.md.