agentlang-index · task easy

Parse two unsigned integers and write their sum, or error on parse failure or u32 overflow

017-checked-add-overflow. 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.

# 017-checked-add-overflow

Read two lines from standard input. Each line contains an unsigned
decimal integer that fits in u32 (range 0 through 4294967295
inclusive). Parse both. Compute the sum `a + b` and write the result
as decimal followed by a single newline.

If any of the following hold, write the literal string `error\n`
instead and exit:

1. Either line fails to parse as a u32 (empty input, non-digit
   characters in the body, leading `+` sign, value exceeds u32
   maximum).
2. The sum `a + b` would exceed 4294967295 (u32 overflow).

Trailing whitespace on either digit line is tolerated and trimmed
before parsing. Leading zeros are accepted (`007` parses as 7).

Output exactly one line followed by `\n`. Do not write to standard
error. Exit with status 0 in every case.

## Examples

Input (stdin):

```
2
3
```

Output: `5\n`

Input (stdin):

```
0
4294967295
```

Output: `4294967295\n` (boundary, sum equals u32 max)

Input (stdin):

```
4294967295
1
```

Output: `error\n` (smallest u32 overflow)

Input (stdin):

```
2147483648
2147483648
```

Output: `error\n` (2^31 + 2^31 = 2^32, exact wrap)

Input (stdin):

```
abc
5
```

Output: `error\n` (parse fails on first line)

## Zero input convention

Zero 0.1.2 has no exposed stdin capability. The Zero reference
reads `a` from `argv[1]` and `b` from `argv[2]`; values are
interpreted exactly as the two stdin lines for other languages.

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 error-handling, parsing, arithmetic, overflow

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 ZeroTypeScriptRustGoPython
gpt-4o compile
gpt-4o-mini compile
gpt-5 wrong output

Failure excerpts

3 of 15 attempts failed. Each card is one attempt, with the captured first line of the diagnostic.

  1. gpt-4o Zero compile
    ref.zero:1:1 IMP001: unknown package-local import 'std'
  2. gpt-4o-mini Zero compile
    ref.zero:3:13 PAR100: expected '{' before block
  3. gpt-5 Zero wrong output
    (no diagnostic captured)

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 141 lines
// Checked u32 add-with-overflow reference for AgentLang Index.
//
// Zero 0.1.2 has no exposed stdin, so a and b come from argv[1..2].
// std.parse.parseU32 cannot accept runtime String values on the
// direct ELF64 backend (CGEN004: requires literal text), so the
// parse + overflow check is implemented inline: u64 accumulator,
// digit-by-digit, reject any non-digit byte, reject if final value
// exceeds u32::MAX.
//
// Three failure modes collapse to "error\n":
//   - argv[1] or argv[2] missing
//   - either argv has non-digit bytes, is empty, or exceeds u32::MAX
//   - the sum a + b exceeds u32::MAX (computed in u64 to dodge wrap)
//
// main ends with an explicit `return` to dodge the trailing-write
// byte-count-as-exit-code codegen quirk surfaced on task 012.

pub fun main(world: World) -> Void raises {
    let maybe_a = std.args.get(1)
    let maybe_b = std.args.get(2)
    if maybe_a.has == false {
        check world.out.write("error\n")
        return
    }
    if maybe_b.has == false {
        check world.out.write("error\n")
        return
    }

    let a_in = std.mem.span(maybe_a.value)
    let b_in = std.mem.span(maybe_b.value)

    // ---- Parse a as u32, reject non-digit / empty / > u32::MAX ----
    let a_len = std.mem.len(a_in)
    let mut a_acc: u64 = 0_u64
    let mut a_have: Bool = false
    let mut a_ok: Bool = true
    let mut a_i: usize = 0
    while a_i < a_len {
        let c = a_in[a_i]
        if c >= 48_u8 {
            if c <= 57_u8 {
                let d: u64 = (c - 48_u8) as u64
                a_acc = a_acc * 10_u64 + d
                a_have = true
                a_i = a_i + 1
            } else {
                a_ok = false
                a_i = a_len
            }
        } else {
            a_ok = false
            a_i = a_len
        }
    }
    if a_ok == false {
        check world.out.write("error\n")
        return
    }
    if a_have == false {
        check world.out.write("error\n")
        return
    }
    if a_acc > 4294967295_u64 {
        check world.out.write("error\n")
        return
    }

    // ---- Parse b as u32 ----
    let b_len = std.mem.len(b_in)
    let mut b_acc: u64 = 0_u64
    let mut b_have: Bool = false
    let mut b_ok: Bool = true
    let mut b_i: usize = 0
    while b_i < b_len {
        let c = b_in[b_i]
        if c >= 48_u8 {
            if c <= 57_u8 {
                let d: u64 = (c - 48_u8) as u64
                b_acc = b_acc * 10_u64 + d
                b_have = true
                b_i = b_i + 1
            } else {
                b_ok = false
                b_i = b_len
            }
        } else {
            b_ok = false
            b_i = b_len
        }
    }
    if b_ok == false {
        check world.out.write("error\n")
        return
    }
    if b_have == false {
        check world.out.write("error\n")
        return
    }
    if b_acc > 4294967295_u64 {
        check world.out.write("error\n")
        return
    }

    // ---- Sum in u64, bounds-check against u32::MAX ----
    let sum: u64 = a_acc + b_acc
    if sum > 4294967295_u64 {
        check world.out.write("error\n")
        return
    }
    let s_val: u32 = sum as u32

    // ---- Render s_val as decimal + newline ----
    let mut out: [16]u8 = [0_u8; 16]
    let mut out_n: usize = 0
    if s_val == 0_u32 {
        out[out_n] = 48_u8
        out_n = out_n + 1
    } else {
        let mut tmp: [16]u8 = [0_u8; 16]
        let mut tn: usize = 0
        let mut v: u32 = s_val
        while v > 0_u32 {
            let digit_u32: u32 = 48_u32 + (v % 10_u32)
            tmp[tn] = digit_u32 as u8
            tn = tn + 1
            v = v / 10_u32
        }
        let mut wj: usize = 0
        while wj < tn {
            out[out_n] = tmp[tn - 1 - wj]
            out_n = out_n + 1
            wj = wj + 1
        }
    }
    out[out_n] = 10_u8
    out_n = out_n + 1
    check world.out.write(out[0..out_n])
    return
}
TypeScript 49 lines
const U32_MAX = 4294967295n;

function parseU32(s: string): bigint | 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;
    }
    try {
        const v = BigInt(t);
        if (v > U32_MAX) return null;
        return v;
    } catch {
        return null;
    }
}

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 a = parseU32(lines[0]);
    const b = parseU32(lines[1]);
    if (a === null || b === null) {
        process.stdout.write("error\n");
        return;
    }
    const sum = a + b;
    if (sum > U32_MAX) {
        process.stdout.write("error\n");
        return;
    }
    process.stdout.write(`${sum}\n`);
}

main();
Rust 45 lines
use std::io::{self, Read, Write};

fn parse_u32(s: &str) -> Option<u32> {
    let t = s.trim();
    if t.is_empty() {
        return None;
    }
    for b in t.bytes() {
        if !(b'0'..=b'9').contains(&b) {
            return None;
        }
    }
    t.parse::<u32>().ok()
}

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_a = lines.next();
    let line_b = lines.next();
    let (Some(la), Some(lb)) = (line_a, line_b) else {
        let _ = io::stdout().write_all(b"error\n");
        return;
    };
    let a = parse_u32(la);
    let b = parse_u32(lb);
    match (a, b) {
        (Some(av), Some(bv)) => match av.checked_add(bv) {
            Some(sum) => {
                let _ = writeln!(io::stdout(), "{}", sum);
            }
            None => {
                let _ = io::stdout().write_all(b"error\n");
            }
        },
        _ => {
            let _ = io::stdout().write_all(b"error\n");
        }
    }
}
Go 67 lines
package main

import (
	"bufio"
	"fmt"
	"math"
	"os"
	"strconv"
	"strings"
)

func parseU32(s string) (uint32, 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
	}
	return uint32(v), true
}

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
	}
	a, okA := parseU32(parts[0])
	b, okB := parseU32(parts[1])
	if !okA || !okB {
		fmt.Fprint(w, "error\n")
		return
	}
	sum := uint64(a) + uint64(b)
	if sum > math.MaxUint32 {
		fmt.Fprint(w, "error\n")
		return
	}
	fmt.Fprintf(w, "%d\n", uint32(sum))
}

func readAll(r *bufio.Reader) (string, error) {
	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(), nil
}
Python 43 lines
#!/usr/bin/env python3
import sys

U32_MAX = 4294967295


def parse_u32(s: str):
    s = s.strip()
    if not s:
        return None
    for ch in s:
        if not ('0' <= ch <= '9'):
            return None
    try:
        v = int(s)
    except ValueError:
        return None
    if v > U32_MAX:
        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
    a = parse_u32(lines[0])
    b = parse_u32(lines[1])
    if a is None or b is None:
        sys.stdout.write("error\n")
        return
    s = a + b
    if s > U32_MAX:
        sys.stdout.write("error\n")
        return
    sys.stdout.write(f"{s}\n")


if __name__ == "__main__":
    main()

Design notes

Algorithm, failure modes, cross-language parity, and where Zero needed a workaround. From corpus/017-checked-add-overflow/notes.md.

Algorithm

Read two unsigned decimal integers (each in u32 range). Parse both, reject leading sign and non-digit bodies. If either parse fails or a + b would exceed u32::MAX = 4294967295, write error\n. Otherwise write the sum as decimal followed by \n. Process exit is 0 in every case.

Failure modes

Three independent paths collapse to the same error\n:

  1. argv/stdin shape missing (under two lines or two argv values).
  2. Either integer fails to parse (empty body, non-digit byte, leading +/-, or value exceeds u32::MAX = 4294967295).
  3. The sum a + b would exceed u32::MAX (u32 add wrap).

Leading zeros are accepted (007 parses to 7). Trailing whitespace on the stdin lines is tolerated and trimmed before parsing; for the Zero argv path the values are taken exactly as the OS passes them (no embedded whitespace).

Overflow-check shape per language

  • Python / TypeScript: arithmetic is unbounded (BigInt in TS, native int in Python). Sum then compare > U32_MAX. No wraparound trap.
  • Rust: u32::checked_add returns Option<u32>None on overflow. The match arm pattern threads it cleanly with the parse failures (Some(av).checked_add(bv) then match).
  • Go: cast both addends to uint64, sum, compare against math.MaxUint32. (Could also use bits.Add32 to read the carry bit; the u64 widen is simpler and equivalent.)
  • Zero: accumulate both parsed values in u64 directly, sum in u64, compare > 4294967295_u64. Identical pattern to the per-value bounds check on the parse path — u64 is already the working type.

Arc closure (third structural distinct shape)

This task closes the error-handling arc at 3/3:

  • 015 (checked-divide-u32): three flat failure paths that short-circuit at gate checks BEFORE the work (missing argv, parse failure, divisor zero). The work is a single divide; no arithmetic-side failure surface.
  • 016 (parse-list-sum): a counted loop where ANY iteration can fail (parse failure OR running-sum overflow). Failure is mid-work and must terminate the loop cleanly.
  • 017 (checked-add-overflow): the work itself is the failure surface. Both inputs parse cleanly yet the operation can still fail — overflow is detected by computing in a wider type and bounds-checking, OR by using a checked-arithmetic primitive that signals overflow as a sum-time return value.

Across the three: pre-work gate, in-loop failure with state, post-parse arithmetic failure. The error-output collapse is uniform (error\n + exit 0), but the where-it-fails dimension is structurally distinct each time.

Cross-implementation parity

All five share the same dispatch:

  1. parse a (u32)
  2. parse b (u32)
  3. check a + b <= u32::MAX
  4. emit sum + \n

Byte-exact agreement on every case.

Zero-specific notes

  • argv[1..2] carry a and b (no exposed stdin in Zero 0.1.2).
  • std.args.get(i) returns Maybe<String>. The .value is a String, which std.mem.span(.) converts to a Span<u8> for the manual digit scan.
  • std.parse.parseU32 is still unusable for runtime data per the eighth quirk surfaced on task 015 (CGEN004 on direct ELF64 for non-literal input).
  • Parse helpers are inlined twice (once for a, once for b) per the seventh quirk forbidding Span<u8>-taking user functions on direct ELF64. The two parse blocks are byte-identical apart from the local-variable prefix.
  • main ends with an explicit return to dodge the trailing-write byte-count-as-exit-code codegen quirk surfaced on task 012.
  • The overflow check is if sum > 4294967295_u64 { error } where sum is a_acc + b_acc both already validated <= 4294967295_u64. Worst-case sum is 2 * (2^32 - 1) = 2^33 - 2, comfortably within u64 range (no second-order wrap).
  • The render loop uses a small-divisor (10) decimal split, dodging the 2^32-divisor SIGFPE quirk noted for direct ELF64 in the AgentLang Index notes.

No new codegen quirks surfaced during 017 — the eight quirks from tasks 012-015 were already in scope, and this task follows the patterns established in 015/016.


Cost

Model Prompt tokens Completion tokens API ms
gpt-4o 3,265 1,198 11,668
gpt-4o-mini 3,265 1,326 22,780
gpt-5 3,260 23,903 203,552

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.