agentlang-index · task easy
Shift a lowercase ASCII string by a Caesar offset, or error on bad input
018-caesar-cipher. Read two lines from standard input.
Prompt
This is the natural-language brief given to every model, verbatim. The harness prefixes a language-specific calling-convention block and suffixes a "return only the source code" instruction. Nothing else.
# 018-caesar-cipher
Read two lines from standard input.
Line 1 is a non-negative decimal integer `shift` in the range 0 through
25 inclusive.
Line 2 is the plaintext: a non-empty string of lowercase ASCII letters
(bytes `a` through `z`).
Rotate each plaintext byte forward by `shift` positions within the
lowercase alphabet, wrapping `z + 1` back to `a`. Write the
resulting ciphertext followed by a single newline.
If any of the following hold, write the literal string `error\n`
instead and exit:
1. Line 1 fails to parse: empty after trimming whitespace, contains
non-digit bytes, or evaluates to a value greater than 25.
2. Line 2 is empty.
3. Line 2 contains any byte outside `a` through `z` (uppercase,
digits, punctuation, whitespace, non-ASCII all reject).
Trailing whitespace on line 1 is tolerated and trimmed before parsing.
Line 2 is taken verbatim (no trimming, no normalization).
Output exactly one line followed by `\n`. Do not write to standard
error. Exit with status 0 in every case.
## Examples
Input (stdin):
```
3
abc
```
Output: `def\n`
Input (stdin):
```
0
hello
```
Output: `hello\n` (shift of zero is the identity)
Input (stdin):
```
25
abc
```
Output: `zab\n` (each byte shifted back one position, with wrap)
Input (stdin):
```
13
xyz
```
Output: `klm\n` (rot13 on the lowercase alphabet)
Input (stdin):
```
26
abc
```
Output: `error\n` (shift > 25 rejects)
Input (stdin):
```
5
Hello
```
Output: `error\n` (uppercase H rejects)
## Zero input convention
Zero 0.1.2 has no exposed stdin capability. The Zero reference is a
multi-file project under `zero/`: `zero.json` is the package manifest,
`src/main.0` is the driver, and `src/lib.0` exports
`is_lowercase_letter` and `shift_letter`. The driver reads `shift`
from `argv[1]` and the plaintext from `argv[2]`; values are
interpreted exactly as the two stdin lines for other languages
(line 1 trimmed, line 2 verbatim). Invoked as
`zero run zero -- <shift> <plaintext>`.
Acceptance
A task counts as passed only when every public and hidden test case agrees on these fields. No fuzzy matching, no "off by one trailing newline is fine."
| stdout (byte-exact, per case) | true |
|---|---|
| stderr (exact bytes) | "" |
| exit code | 0 |
| wall time max (ms) | 5000 |
| tags | string-transform, module-boundary, multi-file |
Results
Each cell is one attempt. Pass means stdout matched byte-exact on every test case, stderr empty, exit zero. Hover a failure to see the captured first line of the diagnostic.
| Model | Zero | TypeScript | Rust | Go | Python |
|---|---|---|---|---|---|
| gpt-4o | compile | ✓ | ✓ | ✓ | ✓ |
| gpt-4o-mini | compile | ✓ | ✓ | other | ✓ |
| gpt-5 | compile | ✓ | ✓ | ✓ | ✓ |
Failure excerpts
4 of 15 attempts failed. Each card is one attempt, with the captured first line of the diagnostic.
-
zero/src/lib.0:8:1 PAR100: unexpected character '`' -
zero/src/main.0:1:1 IMP001: unknown package-local import '"src/lib.0"' -
# command-line-arguments -
zero/src/lib.0:10:1 PAR100: unexpected character '`'
Reference implementations
The hand-written reference each language ships with. Every reference passes the same public and hidden test suite under the pinned toolchain before any model touches the task.
Click a language to expand
Zero 130 lines
// src/main.0
// Caesar cipher main driver for AgentLang Index slot 018.
//
// Zero 0.1.2 has no exposed stdin, so shift and plaintext come
// from argv[1..2]. Lib (src/lib.0) provides is_lowercase_letter
// and shift_letter; main parses + validates + composes.
//
// Failure modes collapse to "error\n":
// - argv[1] or argv[2] missing
// - shift fails to parse, has non-digit bytes, or is > 25
// - plaintext is empty
// - any plaintext byte is outside [a-z]
use lib
pub fun main(world: World) -> Void raises {
let maybe_shift = std.args.get(1)
let maybe_text = std.args.get(2)
if maybe_shift.has == false {
check world.out.write("error\n")
return
}
if maybe_text.has == false {
check world.out.write("error\n")
return
}
let shift_in = std.mem.span(maybe_shift.value)
let text_in = std.mem.span(maybe_text.value)
// ---- Parse shift as u8 in 0..=25 ----
let s_len = std.mem.len(shift_in)
let mut s_acc: u64 = 0_u64
let mut s_have: Bool = false
let mut s_ok: Bool = true
let mut s_i: usize = 0
while s_i < s_len {
let c = shift_in[s_i]
if c >= 48_u8 {
if c <= 57_u8 {
let d: u64 = (c - 48_u8) as u64
s_acc = s_acc * 10_u64 + d
s_have = true
s_i = s_i + 1
} else {
s_ok = false
s_i = s_len
}
} else {
s_ok = false
s_i = s_len
}
}
if s_ok == false {
check world.out.write("error\n")
return
}
if s_have == false {
check world.out.write("error\n")
return
}
if s_acc > 25_u64 {
check world.out.write("error\n")
return
}
let shift_val: u8 = s_acc as u8
// ---- Validate plaintext non-empty + all lowercase letters ----
let t_len = std.mem.len(text_in)
if t_len == 0 {
check world.out.write("error\n")
return
}
let mut text_ok: Bool = true
let mut t_i: usize = 0
while t_i < t_len {
let b = text_in[t_i]
if is_lowercase_letter(b) == false {
text_ok = false
t_i = t_len
} else {
t_i = t_i + 1
}
}
if text_ok == false {
check world.out.write("error\n")
return
}
// ---- Build ciphertext + trailing newline ----
let mut out: [1100]u8 = [0_u8; 1100]
let mut out_n: usize = 0
let mut wi: usize = 0
while wi < t_len {
let b = text_in[wi]
out[out_n] = shift_letter(b, shift_val)
out_n = out_n + 1
wi = wi + 1
}
out[out_n] = 10_u8
out_n = out_n + 1
check world.out.write(out[0..out_n])
return
}
// src/lib.0
// Caesar cipher helpers. Pure scalar-typed exports so the seventh
// direct-backend codegen quirk (no user functions over Span/MutSpan/
// shape values) does not bite at the module boundary.
pub fun is_lowercase_letter(b: u8) -> Bool {
if b < 97_u8 {
return false
}
if b > 122_u8 {
return false
}
return true
}
pub fun shift_letter(b: u8, shift: u8) -> u8 {
// Caller is responsible for is_lowercase_letter(b) == true
// and shift in 0..=25 inclusive. Returns the byte rotated by
// shift positions within a-z.
let zero_based: u8 = b - 97_u8
let shifted: u8 = (zero_based + shift) % 26_u8
return 97_u8 + shifted
}
TypeScript 60 lines
function isLowercaseLetter(b: number): boolean {
return b >= 97 && b <= 122;
}
function shiftLetter(b: number, shift: number): number {
const zeroBased = b - 97;
return 97 + ((zeroBased + shift) % 26);
}
function parseShift(s: string): number | null {
const t = s.trim();
if (t.length === 0) return null;
for (let i = 0; i < t.length; i++) {
const c = t.charCodeAt(i);
if (c < 48 || c > 57) return null;
}
const v = Number(t);
if (!Number.isInteger(v) || v < 0 || v > 25) return null;
return v;
}
async function readAll(): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) {
chunks.push(chunk as Buffer);
}
return Buffer.concat(chunks).toString("utf8");
}
async function main(): Promise<void> {
const data = await readAll();
const lines = data.split("\n");
if (lines.length < 2) {
process.stdout.write("error\n");
return;
}
const shift = parseShift(lines[0]);
if (shift === null) {
process.stdout.write("error\n");
return;
}
const text = lines[1];
if (text.length === 0) {
process.stdout.write("error\n");
return;
}
const out: number[] = [];
for (let i = 0; i < text.length; i++) {
const c = text.charCodeAt(i);
if (!isLowercaseLetter(c)) {
process.stdout.write("error\n");
return;
}
out.push(shiftLetter(c, shift));
}
process.stdout.write(Buffer.from(out).toString("ascii") + "\n");
}
main();
Rust 65 lines
use std::io::{self, Read, Write};
fn is_lowercase_letter(b: u8) -> bool {
(b'a'..=b'z').contains(&b)
}
fn shift_letter(b: u8, shift: u8) -> u8 {
let zero_based = b - b'a';
b'a' + ((zero_based + shift) % 26)
}
fn parse_shift(s: &str) -> Option<u8> {
let t = s.trim();
if t.is_empty() {
return None;
}
for b in t.bytes() {
if !(b'0'..=b'9').contains(&b) {
return None;
}
}
let v: u32 = t.parse().ok()?;
if v > 25 {
return None;
}
Some(v as u8)
}
fn main() {
let mut input = String::new();
if io::stdin().read_to_string(&mut input).is_err() {
let _ = io::stdout().write_all(b"error\n");
return;
}
let mut lines = input.split('\n');
let line_shift = lines.next();
let line_text = lines.next();
let (Some(ls), Some(lt)) = (line_shift, line_text) else {
let _ = io::stdout().write_all(b"error\n");
return;
};
let shift = match parse_shift(ls) {
Some(v) => v,
None => {
let _ = io::stdout().write_all(b"error\n");
return;
}
};
let text_bytes = lt.as_bytes();
if text_bytes.is_empty() {
let _ = io::stdout().write_all(b"error\n");
return;
}
let mut out = Vec::with_capacity(text_bytes.len() + 1);
for &b in text_bytes {
if !is_lowercase_letter(b) {
let _ = io::stdout().write_all(b"error\n");
return;
}
out.push(shift_letter(b, shift));
}
out.push(b'\n');
let _ = io::stdout().write_all(&out);
}
Go 87 lines
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func isLowercaseLetter(b byte) bool {
return b >= 'a' && b <= 'z'
}
func shiftLetter(b, shift byte) byte {
zeroBased := b - 'a'
return 'a' + ((zeroBased + shift) % 26)
}
func parseShift(s string) (byte, bool) {
t := strings.TrimSpace(s)
if t == "" {
return 0, false
}
for _, c := range t {
if c < '0' || c > '9' {
return 0, false
}
}
v, err := strconv.ParseUint(t, 10, 32)
if err != nil {
return 0, false
}
if v > 25 {
return 0, false
}
return byte(v), true
}
func readAll(r *bufio.Reader) string {
var sb strings.Builder
buf := make([]byte, 4096)
for {
n, err := r.Read(buf)
if n > 0 {
sb.Write(buf[:n])
}
if err != nil {
break
}
}
return sb.String()
}
func main() {
w := bufio.NewWriter(os.Stdout)
defer w.Flush()
r := bufio.NewReader(os.Stdin)
data := readAll(r)
parts := strings.SplitN(data, "\n", 3)
if len(parts) < 2 {
fmt.Fprint(w, "error\n")
return
}
shift, ok := parseShift(parts[0])
if !ok {
fmt.Fprint(w, "error\n")
return
}
text := parts[1]
if len(text) == 0 {
fmt.Fprint(w, "error\n")
return
}
out := make([]byte, 0, len(text)+1)
for i := 0; i < len(text); i++ {
b := text[i]
if !isLowercaseLetter(b) {
fmt.Fprint(w, "error\n")
return
}
out = append(out, shiftLetter(b, shift))
}
out = append(out, '\n')
w.Write(out)
}
Python 55 lines
#!/usr/bin/env python3
import sys
def is_lowercase_letter(b: int) -> bool:
return 97 <= b <= 122
def shift_letter(b: int, shift: int) -> int:
zero_based = b - 97
return 97 + ((zero_based + shift) % 26)
def parse_shift(s: str):
t = s.strip()
if not t:
return None
for ch in t:
if not ('0' <= ch <= '9'):
return None
try:
v = int(t)
except ValueError:
return None
if v > 25:
return None
return v
def main() -> None:
data = sys.stdin.read()
lines = data.split("\n")
if len(lines) < 2:
sys.stdout.write("error\n")
return
shift = parse_shift(lines[0])
if shift is None:
sys.stdout.write("error\n")
return
text = lines[1]
if not text:
sys.stdout.write("error\n")
return
text_bytes = text.encode("utf-8")
for b in text_bytes:
if not is_lowercase_letter(b):
sys.stdout.write("error\n")
return
out = bytes(shift_letter(b, shift) for b in text_bytes)
sys.stdout.write(out.decode("ascii") + "\n")
if __name__ == "__main__":
main()
Design notes
Algorithm, failure modes, cross-language parity, and where Zero needed a workaround. From corpus/018-caesar-cipher/notes.md.
Algorithm
Read shift (0..=25) and plaintext (lowercase a-z, non-empty). For each
plaintext byte b, output 'a' + ((b - 'a' + shift) % 26). Trailing
newline on output. If shift fails to parse or exceeds 25, or plaintext
is empty, or any plaintext byte is outside a..z, write error\n.
Process exit is 0 in every case.
Multi-file arc opener
This task opens the multi-file arc. The Zero implementation lives
under zero/ as a proper Zero project:
zero/
zero.json # package metadata (cli exe target, linux-musl-x64)
src/
main.0 # parses argv, validates, composes lib calls
lib.0 # is_lowercase_letter, shift_letter helpers
The driver imports the library with use lib, then calls
is_lowercase_letter(b) on each plaintext byte for validation and
shift_letter(b, shift_val) to produce each ciphertext byte. The
project is invoked as zero run zero -- <shift> <plaintext> —
project directory, double-dash, two argv values.
Lib signature constraint
Zero 0.1.2's direct ELF64 backend forbids user functions that take
or return Span<u8>, MutSpan<u8>, or shape values (the seventh
quirk surfaced on task 014). Lib signatures are pinned to pure
scalars to stay below that line:
pub fun is_lowercase_letter(b: u8) -> Bool
pub fun shift_letter(b: u8, shift: u8) -> u8
The plaintext bytes are walked one at a time in main.0, calling
into lib.0 for each byte. This keeps the module boundary purely
scalar and produces a clean compile across both modules.
Maybe construction constraint (discovery)
I briefly tried to have lib expose a parse_shift(s: Span<u8>) -> Maybe<u8> helper. Two problems compounded:
Span<u8>at the module boundary trips the seventh quirk.- Even with scalar inputs, constructing a
Maybe<u8>for the success case fails: user code cannot constructSome(value). The shape-literal formMaybe<u8>{ has: true, value: x }is rejected with PAR100 "expected '}' after block". User code can onlyreturn nullfor None; Some values come back from stdlib calls returningMaybe<T>directly.
The workaround keeps parse logic inline in main.0 (parses argv[1]
as a u64 accumulator, bounds-checks against 25, casts down to u8)
and exports only the per-byte scalar helpers from lib. This shape
both avoids the boundary quirk AND avoids the Maybe
This is recorded as Zero 0.1.2 quirk #9 in the running tally
(user-code Maybeerror\n without needing to thread Some values across
module boundaries.
Cross-implementation parity
All five share the same dispatch:
- parse shift (0..=25 inclusive)
- validate plaintext non-empty
- validate each plaintext byte in [a-z]
- emit
'a' + ((byte - 'a' + shift) % 26)for each, then\n
Byte-exact agreement on every case across zero/ts/rust/go/python.
Zero-specific notes
- argv[1..2] carry shift and plaintext (no exposed stdin in Zero 0.1.2).
- The output buffer is
[1100]u8 = [0_u8; 1100]— generous fixed upper bound for the test corpus; no dynamic allocation needed. mainends with an explicitreturnto dodge the trailing-write byte-count-as-exit-code codegen quirk surfaced on task 012.std.parse.parseU32would in principle work for shift parsing but it's still unusable for runtime data per the eighth quirk (CGEN004 on direct ELF64 for non-literal input from task 015).- The shift accumulator uses u64 to keep overflow-free across the digit loop, then bounds-checks against 25 before casting to u8.
No new codegen quirks surfaced during 018 — the nine quirks now
known were all visible from prior tasks or from the Maybe
Cost
| Model | Prompt tokens | Completion tokens | API ms |
|---|---|---|---|
| gpt-4o | 4,077 | 1,497 | 12,429 |
| gpt-4o-mini | 4,077 | 1,629 | 28,849 |
| gpt-5 | 4,072 | 22,153 | 227,636 |
Tokens and API ms are summed across the five languages this model attempted for this task.
Compare
Model deep-dives: gpt-4o · gpt-4o-mini · gpt-5 . Back to the leaderboard and methodology.