Reading Input: lines, files & basic I/O
The same little cat -n: read input line by line and print each line prefixed with its 1-based line number. Watch how each language gets a stream of lines and how it counts: a buffered scanner with a manual counter (Go), a buffered reader whose .lines() iterator chains straight into enumerate (Rust, Python), the implicit line-number variable $. (Perl), built-in .lines with .kv (Raku), or a recursive read-until-End_of_file loop (OCaml). guji v0 is the outlier: its only I/O primitive is print, so input is modelled as an in-program value split with .lines().
sub main(): Int {
# v0's only I/O primitive is print, so model "input"
# as a value, then read it line by line with .lines().
$input = "alpha\nbeta\ngamma"
mut $n = 1
for $line in $input.lines() {
print("$n: $line")
$n = $n + 1
}
0
}Per the spec (§15.4) print is the only v0 I/O primitive — file, stdin, and stderr APIs are deferred — so genuine reading isn't expressible yet; the input is a Str value split by the prelude's lines() (§15.3). Iteration that has a side effect uses for (§6.3); the counter is an explicit mut $i (§4), since guji is immutable-by-default and has no built-in enumerate. The "$n: $line" interpolation (§12) builds each output line.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
n := 0
for scanner.Scan() {
n++
fmt.Printf("%d: %s\n", n, scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "read error:", err)
}
}bufio.NewScanner(os.Stdin) is the standard line reader: Scan() advances to the next token (lines by default) and Text() returns it with the newline stripped. The counter n is incremented by hand — Go has no enumerate. Idiomatic Go checks scanner.Err() after the loop, since Scan() returning false means either EOF or an error, and the two are distinguished only there.
let () =
let rec loop n =
match input_line stdin with
| line -> Printf.printf "%d: %s\n" n line; loop (n + 1)
| exception End_of_file -> ()
in
loop 1OCaml's stdlib reads a line with input_line, which raises End_of_file at the end rather than returning an option, so the loop is a tail-recursive loop that pattern-matches the call's result — the exception End_of_file arm is OCaml's syntax for catching that exception inline. The line counter is just the recursion argument n, threaded immutably. input_line already strips the trailing newline.
use strict;
use warnings;
while (my $line = <STDIN>) {
chomp $line;
print "$.: $line\n";
}The diamond/angle read <STDIN> yields one line per iteration (including its newline), and the while (my $line = ...) idiom stops cleanly at EOF — even on a final line like 0 — because the assignment is specially tested for definedness. chomp removes the trailing newline, and $. is the magic input line-number variable Perl maintains automatically, so no manual counter is needed.
for $*IN.lines.kv -> $i, $line {
say "{ $i + 1 }: $line";
}$*IN is the standard-input handle and .lines is a lazy sequence of newline-stripped lines, so this streams rather than slurping. .kv pairs each element with its index, bound by the pointy block -> $i, $line; $i is 0-based, so the output adds one. say appends the newline, and { ... } interpolates the expression inside the double-quoted string.
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for (i, line) in stdin.lock().lines().enumerate() {
let line = line.expect("read error");
println!("{}: {}", i + 1, line);
}
}stdin.lock() hands back a buffered StdinLock (it implements BufRead), whose .lines() is an iterator of Result<String, io::Error> with newlines already trimmed. Chaining .enumerate() supplies the 0-based index i, so the program prints i + 1. Each line is a Result, made explicit here by .expect, reflecting that any read can fail — the error is part of the type, not a silent EOF.
import sys
for n, line in enumerate(sys.stdin, start=1):
print(f"{n}: {line.rstrip(chr(10))}")Iterating sys.stdin directly yields lines lazily, one at a time, so the whole input is never held in memory. enumerate(..., start=1) supplies the 1-based line number without a manual counter — the most Pythonic way to count while iterating. Unlike most languages here, the lines keep their trailing newline, so rstrip removes it before the f-string reformats each line.