examples/checksum/
CRC-32 e Adler-32 per controllare i dati, Base64 per convertirli in testo e recuperarli.
- 738 righe
- 4 moduli
I test confrontano ogni algoritmo con i risultati del documento che lo definisce. L’output riproduce la tabella di esempi Base64 dello standard. La decodifica restituisce byte: Heroes non ha una funzione che converta direttamente un byte in un carattere.
Eseguilo
BASE64("") = ""
BASE64("f") = "Zg=="
BASE64("fo") = "Zm8="
BASE64("foo") = "Zm9v"
BASE64("foob") = "Zm9vYg=="
BASE64("fooba") = "Zm9vYmE="
BASE64("foobar") = "Zm9vYmFy"
crc32("123456789") = 0xcbf43926
adler32("123456789") = 0x091e01de
bytes = 9
decoded("Zm9vYmE=") = 102 111 111 98 97
exit 0 · 11 righe
Il programma
# Three published instruments over the same bytes: CRC-32, Adler-32 and
# Base64, each checked against the document that defines it.
#
# Corpus material (CLAUDE.md §9), and the third of M-corpus-depth's oracle
# family. What the three modules beside this one have in common is that
# **nothing here decides whether it is right** — the CRC catalogue, RFC 1950
# and RFC 4648 do, and each module's header quotes its source with the URL and
# the day it was fetched.
#
# WHAT THIS PROGRAM PRINTS is RFC 4648 § 10's BASE64 table, in the RFC's own
# spelling, as this program computes it — so the first seven lines of
# `main.expected` are the standard's test-vector block rather than this
# program's opinion of itself. Measured with `diff` against the
# lines fetched from `rfc-editor.org`: **identical**, once the RFC's
# three-space indent is removed. Then the three numbers the other two
# documents publish.
#
# WHAT IT IS FOR, beyond the oracles. The corpus was measured
# and `u8`, `u16` and `u64` were **declared nowhere** in it: every program that
# touched a width converted through it and went back to `i64`. Here the widths
# are in the types — `Digest` holds two `u32` and a `u64`, `adler.Halves` holds
# two `u16`, and `base64.decoded` gives back a `[u8]` — while every
# calculation happens in `i64`, because the bitwise operators are `i64` only
# (spec § Operators). That split is the whole shape of a 32-bit checksum in
# this language, and `crc.hero`'s header is where it is argued.
use adler
use base64
use crc
# RFC 4648 § 10's seven inputs, in the order the RFC lists them.
constant VECTORS: [str]
[
""
"f"
"fo"
"foo"
"foob"
"fooba"
"foobar"
]
constant DIGITS: str
"0123456789abcdef"
# Everything the three instruments say about one message. The two checksums
# are `u32` because that is what they are, and the length is a `u64` because
# that is what a size is — neither is an `i64` that happens to be small.
record Digest
checksum: u32
rolling: u32
length: u64
function digest_of(text: str) -> Digest
return Digest(
checksum: crc.crc32(text),
rolling: adler.adler32(text),
length: text.len().to_u64().must()
)
# A `u32` written the way both standards write it: `0x` and eight digits, low
# ones included, because a checksum with a leading zero dropped is a checksum
# nobody can compare by eye.
function hex(value: u32) -> str
out: [str] @= []
v = value.to_i64().must()
shift: i64 @= 28
while shift >= 0
nibble = v >> shift & 15
out @ out.push(DIGITS.slice(from: nibble, to: nibble + 1))
shift @ shift - 4
return "0x" + join(out, "")
# The bytes of a decode, as numbers — the only way to show them, because
# Heroes cannot build a character from a byte value (`base64.hero`'s header
# says why, and it is a language fact rather than a gap in this program).
function shown(data: [u8]) -> [str]
out: [str] @= []
for one in data
out @ out.push(one.to_str())
return out
function main()
# RFC 4648 § 10, recomputed. Compare this block with the standard's.
for text in VECTORS
print("BASE64(\"", text, "\") = \"", base64.encoded(text), "\"")
# The CRC catalogue's check value for CRC-32/ISO-HDLC, and RFC 1950's
# Adler-32 over the same nine characters.
nine = digest_of("123456789")
print("crc32(\"123456789\") = ", hex(nine.checksum))
print("adler32(\"123456789\") = ", hex(nine.rolling))
print("bytes = ", nine.length.to_str())
# And one decode, shown as the bytes it is: 102 111 111 98 97 is `fooba`
# in ASCII, which is the vector two lines above read backwards.
back = base64.decoded("Zm9vYmE=").must()
print("decoded(\"Zm9vYmE=\") = ", join(shown(back), " "))
test "the printed table is RFC 4648's, entry by entry"
# The same seven the encoder's own module asserts, asserted again HERE
# because this is the list `main` prints from: a vector dropped from
# `VECTORS` would change the output and break no test in `base64.hero`.
assert VECTORS.len() == 7
assert base64.encoded(VECTORS[0]) == ""
assert base64.encoded(VECTORS[1]) == "Zg=="
assert base64.encoded(VECTORS[2]) == "Zm8="
assert base64.encoded(VECTORS[3]) == "Zm9v"
assert base64.encoded(VECTORS[4]) == "Zm9vYg=="
assert base64.encoded(VECTORS[5]) == "Zm9vYmE="
assert base64.encoded(VECTORS[6]) == "Zm9vYmFy"
test "the digest of the nine characters every one of the three documents uses"
nine = digest_of("123456789")
assert nine.checksum == 0xcbf43926
assert nine.rolling == 0x091e01de
assert nine.length == 9
test "hex prints eight digits, keeping the zeros a reader needs"
assert hex(0x00000001.to_u32().must()) == "0x00000001"
assert hex(0xcbf43926.to_u32().must()) == "0xcbf43926"
assert hex(0x091e01de.to_u32().must()) == "0x091e01de"
assert hex(0xffffffff.to_u32().must()) == "0xffffffff"
assert hex(0.to_u32().must()) == "0x00000000"
test "the empty message, through all three instruments at once"
# Each of the three has a defined answer for nothing at all, and they are
# three different answers: 0, 1, and the empty text.
nothing = digest_of("")
assert nothing.checksum == 0
assert nothing.rolling == 1
assert nothing.length == 0
assert base64.encoded("") == ""
test "the three instruments disagree about which messages collide"
# Why a program carries more than one: `ab` and `ba` have the same byte
# sum, and both checksums notice; a checksum is not a hash, so what is
# asserted here is that they are DIFFERENT functions of the same bytes.
left = digest_of("ab")
right = digest_of("ba")
assert left.checksum != right.checksum
assert left.rolling != right.rolling
assert left.length == right.length
assert left.checksum.to_i64().must() != left.rolling.to_i64().must()
test "a digest survives the round trip through base64"
# The two halves of the program meeting: encode a message, decode it, and
# the decoded bytes must checksum to what the original did.
text = "the quick brown fox jumps over the lazy dog"
there = base64.encoded(text)
assert there == "dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
home = base64.decoded(there).must()
assert home == base64.bytes_of(text)
assert crc.crc32(text) == digest_of(text).checksum
assert home.len() == 43
adler.hero · 132 righe
# Adler-32, straight out of the sentence that defines it.
#
# Corpus material (CLAUDE.md §9). This module is the other half of `crc.hero`'s
# point: a 32-bit checksum with **no bitwise operator in it at all**. Adler-32
# is two running sums modulo a prime, so it needs `+` and `%` and nothing else,
# and its 32-bit result is an arrangement of two 16-bit halves rather than an
# overflowing register. Where CRC-32 shows what Heroes refuses (`u32`
# arithmetic), this shows what it does not have to refuse.
#
# THE DEFINITION, quoted from its source and fetched — RFC 1950
# § 2.2, the zlib format specification, where Adler-32 is normative:
#
# Adler-32 is composed of two sums accumulated per byte: s1 is the sum of
# all bytes, s2 is the sum of all s1 values. Both sums are done modulo
# 65521. s1 is initialized to 1, s2 to zero. The Adler-32 checksum is
# stored as s2*65536 + s1 in most-significant-byte first (network) order.
#
# https://www.rfc-editor.org/rfc/rfc1950.txt
#
# THE ORACLE is Python 3's `zlib.adler32`, run — an implementation
# this one shares no line with: `0x00000001` for the empty string, `0x00620062`
# for `a`, `0x091e01de` for `123456789`, `0x08ab027a` for `foobar`,
# `0x11e60398` for `Wikipedia` and `0x0231016d` for the two bytes of `é`. All
# six are asserted below. The `Wikipedia` value is also the one this checksum
# is published with almost everywhere, which is why it is in the list.
#
# WHY 65521, and why the program says it rather than 65536: it is the largest
# prime below 2^16, and the primality is the whole reason the sum catches
# transpositions that a plain sum does not. The test at the bottom asserts it
# is prime, so the number is a fact in this program and not a magic constant.
# The modulus: the largest prime below 65536.
constant BASE: i64
65521
# The two halves of an Adler-32, each of them a 16-bit value by construction
# because both are taken modulo `BASE`. Declared `u16` so the width is in the
# type rather than in a comment — `s2 * 65536 + s1` then says how they are put
# together, exactly as RFC 1950 writes it.
record Halves
s1: u16
s2: u16
# The two sums of `text`, in the order the RFC names them.
function halves(text: str) -> Halves
s1: i64 @= 1
s2: i64 @= 0
i: i64 @= 0
while i < text.len()
byte: u8 = text[i]
s1 @ (s1 + byte.to_i64().must()) % BASE
s2 @ (s2 + s1) % BASE
i @ i + 1
return Halves(s1: s1.to_u16().must(), s2: s2.to_u16().must())
# The checksum itself: `s2 * 65536 + s1`, the RFC's own arrangement.
function adler32(text: str) -> u32
two = halves(text)
return (two.s2.to_i64().must() * 65536 + two.s1.to_i64().must()).to_u32().must()
# Whether `n` has no divisor but itself and one — used only by the test that
# asserts `BASE` is prime.
function is_prime(n: i64) -> bool
if n < 2
return false
d: i64 @= 2
while d * d <= n
if n % d == 0
return false
d @ d + 1
return true
test "six values from an implementation this one shares no line with"
# Run against Python 3's `zlib.adler32`.
assert adler32("") == 0x00000001
assert adler32("a") == 0x00620062
assert adler32("123456789") == 0x091e01de
assert adler32("foobar") == 0x08ab027a
assert adler32("Wikipedia") == 0x11e60398
assert adler32("é") == 0x0231016d
test "the empty string is s1 = 1 and s2 = 0, which is the RFC's initialisation"
# The one value the definition gives directly, asserted through the halves
# rather than through the packed number, so a mistake in the packing and a
# mistake in the initialisation cannot hide each other.
empty = halves("")
assert empty.s1 == 1
assert empty.s2 == 0
assert adler32("") == 1
test "s1 is the sum of the bytes, plus the one it started at"
# `a` is 97, `b` is 98, `c` is 99, and a character literal is an integer
# (spec § Types), so the sum can be written out rather than trusted.
three = halves("abc")
assert three.s1.to_i64().must() == 1 + 'a' + 'b' + 'c'
# s2 accumulates s1 after each byte: (1+97) + (1+97+98) + (1+97+98+99).
assert three.s2.to_i64().must() == 98 + 196 + 295
test "moving a byte changes the checksum, which a plain sum would not notice"
# This is what the second sum BUYS, and it is the reason Adler-32 is not
# just s1. The first assertion is the one a plain byte sum fails.
assert halves("ab").s1 == halves("ba").s1
assert adler32("ab") != adler32("ba")
assert adler32("abcd") != adler32("dcba")
test "the modulus is the largest prime below sixty-five thousand five hundred and thirty-six"
# `BASE` is a fact in this program rather than a number somebody copied.
assert is_prime(BASE)
assert BASE < 65536
n: i64 @= BASE + 1
while n < 65536
assert !is_prime(n)
n @ n + 1
test "both halves stay inside sixteen bits, whatever the message"
# The claim `record Halves` makes in its types, made falsifiable: if either
# sum could exceed 65535 the `to_u16()` would abort, and this walks a
# message long enough to have wrapped both sums many times.
long = repeat("the quick brown fox jumps over the lazy dog", 200)
assert long.len() == 8600
wide = halves(long)
assert wide.s1.to_i64().must() < BASE
assert wide.s2.to_i64().must() < BASE
# And the packed value still agrees with the halves it was built from.
assert adler32(long).to_i64().must() == wide.s2.to_i64().must() * 65536 + wide.s1.to_i64().must()
base64.hero · 283 righe
# Base64, both ways, against the vectors in the standard that defines it.
#
# Corpus material (CLAUDE.md §9). Where `crc.hero` and `adler.hero` turn bytes
# into a number, this turns bytes into text and back, which is the other half
# of what a checksum program does for a living: six bits at a time out of
# every three bytes, and the two padded shapes at the end that everybody gets
# wrong.
#
# THE ORACLE, fetched and quoted whole — RFC 4648 § 10, *Test
# Vectors*, the section the standard provides for exactly this purpose:
#
# BASE64("") = ""
# BASE64("f") = "Zg=="
# BASE64("fo") = "Zm8="
# BASE64("foo") = "Zm9v"
# BASE64("foob") = "Zm9vYg=="
# BASE64("fooba") = "Zm9vYmE="
# BASE64("foobar") = "Zm9vYmFy"
#
# https://www.rfc-editor.org/rfc/rfc4648.txt
#
# All seven are asserted below in BOTH directions, which is more than the RFC
# writes down: the vectors are an encoding table, and reading them backwards is
# a second program.
#
# WHY DECODING STOPS AT BYTES, and this is the language rather than a choice.
# `decoded` returns `[u8]` and not `str`, because **Heroes has no way to build
# a character from a byte value**: the spec's conversions go the other way
# (`to_u8` takes a number and gives a number), a string literal is ASCII-only
# with six escapes and none of them numeric, and the runtime's byte-to-text
# door is `hero_str_from_bytes`, which takes a `ptr` — while `[u8]` in an
# `extern` position is `error[ffi_type]` today. So a decoder written in Heroes
# alone can hand back the bytes and nothing else, and the way to check them is
# `bytes_of` on the text they should be. This is a witness for M-core-packages'
# opening question (iii), the byte buffer, and it was found by writing the
# program rather than by reading the spec.
constant ALPHABET: str
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
constant PAD: str
"="
constant ERR_LENGTH: str
"base64_length"
constant ERR_CHARACTER: str
"base64_character"
constant ERR_PADDING: str
"base64_padding"
# The bytes of `text`, which is what a `str` already is (spec § Types) — this
# only changes the type, so that a caller can hand text to a byte-oriented
# function without the two ever being confused.
function bytes_of(text: str) -> [u8]
out: [u8] @= []
i: i64 @= 0
while i < text.len()
out @ out.push(text[i])
i @ i + 1
return out
# The character six bits stand for.
function letter(value: i64) -> str
return ALPHABET.slice(from: value, to: value + 1)
# The six bits a character stands for, or a refusal naming it.
function value_of(written: str) -> i64?
i: i64 @= 0
while i < 64
if ALPHABET.slice(from: i, to: i + 1) == written
return ok(i)
i @ i + 1
return fail(code: ERR_CHARACTER, msg: "`" + written + "` is not one of base64's 64 characters")
# `text`, encoded. The general one takes bytes; this is the spelling the RFC's
# vectors are written in.
function encoded(text: str) -> str
return encoded_bytes(bytes_of(text))
# Three bytes in, four characters out — and where there are not three, the
# missing sixth-bit groups become `=`, which is what makes the length of the
# output say how many bytes the last group held.
function encoded_bytes(data: [u8]) -> str
out: [str] @= []
i: i64 @= 0
n = data.len()
while i < n
b0 = data[i].to_i64().must()
b1: i64 @= 0
b2: i64 @= 0
held: i64 @= 1
if i + 1 < n
b1 @ data[i + 1].to_i64().must()
held @ 2
if i + 2 < n
b2 @ data[i + 2].to_i64().must()
held @ 3
# `b0 << 16 | b1 << 8 | b2` is `((b0 << 16) | (b1 << 8)) | b2`: in
# Heroes a shift binds tighter than `|` (spec § Operators), so the
# parentheses a C programmer writes here are the ones `heroes fmt`
# deletes.
block = b0 << 16 | b1 << 8 | b2
out @ out.push(letter(block >> 18 & 63))
out @ out.push(letter(block >> 12 & 63))
if held >= 2
out @ out.push(letter(block >> 6 & 63))
else
out @ out.push(PAD)
if held == 3
out @ out.push(letter(block & 63))
else
out @ out.push(PAD)
i @ i + 3
return join(out, "")
# How many `=` close a four-character group, refusing the shapes that cannot
# occur: padding in the first two places, and a gap in the middle.
function padding_of(group: str) -> i64?
third = group.slice(from: 2, to: 3)
fourth = group.slice(from: 3, to: 4)
if group.slice(from: 0, to: 1) == PAD || group.slice(from: 1, to: 2) == PAD
return fail(
code: ERR_PADDING,
msg: "`=` cannot stand in the first two places of a group, and `" + group + "` has one there"
)
if third == PAD && fourth != PAD
return fail(
code: ERR_PADDING,
msg: "`" + group + "` pads its third character and not its fourth, which no encoder writes"
)
if third == PAD
return ok(2)
if fourth == PAD
return ok(1)
return ok(0)
# `text`, decoded to the bytes it stands for — see the header for why this
# stops at bytes.
function decoded(text: str) -> [u8]?
if text.len() % 4 != 0
return fail(
code: ERR_LENGTH,
msg: "a base64 text is a multiple of four characters long, and this one is " + text.len().to_str()
)
out: [u8] @= []
i: i64 @= 0
while i < text.len()
group = text.slice(from: i, to: i + 4)
pad = padding_of(group)?
if pad > 0 && i + 4 < text.len()
return fail(code: ERR_PADDING, msg: "`" + group + "` is padded and is not the last group")
v0 = value_of(group.slice(from: 0, to: 1))?
v1 = value_of(group.slice(from: 1, to: 2))?
v2: i64 @= 0
v3: i64 @= 0
if pad < 2
v2 @ value_of(group.slice(from: 2, to: 3))?
if pad < 1
v3 @ value_of(group.slice(from: 3, to: 4))?
block = v0 << 18 | v1 << 12 | v2 << 6 | v3
out @ out.push((block >> 16 & 255).to_u8().must())
if pad < 2
out @ out.push((block >> 8 & 255).to_u8().must())
if pad < 1
out @ out.push((block & 255).to_u8().must())
i @ i + 4
return ok(out)
# The code a decode refused with, or a sentence saying it did not — so a test
# can assert WHICH refusal happened in one line.
function refusal_of(said: [u8]?) -> str
return match said
.ok _ => "nothing was refused"
.err e => e.code
test "the seven vectors of RFC 4648, encoding"
# § 10 Test Vectors, fetched.
assert encoded("") == ""
assert encoded("f") == "Zg=="
assert encoded("fo") == "Zm8="
assert encoded("foo") == "Zm9v"
assert encoded("foob") == "Zm9vYg=="
assert encoded("fooba") == "Zm9vYmE="
assert encoded("foobar") == "Zm9vYmFy"
test "the same seven, read backwards, which the RFC does not write down"
assert decoded("").must() == bytes_of("")
assert decoded("Zg==").must() == bytes_of("f")
assert decoded("Zm8=").must() == bytes_of("fo")
assert decoded("Zm9v").must() == bytes_of("foo")
assert decoded("Zm9vYg==").must() == bytes_of("foob")
assert decoded("Zm9vYmE=").must() == bytes_of("fooba")
assert decoded("Zm9vYmFy").must() == bytes_of("foobar")
test "every length from nothing to seventeen bytes survives the round trip"
# The three padded shapes repeat with period three, so seventeen lengths
# cover each of them five times and the empty case once.
text = "the quick brown fox"
n: i64 @= 0
while n <= 17
piece = text.slice(from: 0, to: n)
assert decoded(encoded(piece)).must() == bytes_of(piece)
n @ n + 1
test "the length of the output says how many bytes the last group held"
# Four characters per three bytes, always, and the `=` count is 3 - n % 3
# where it is not zero.
assert encoded("f").len() == 4
assert encoded("fo").len() == 4
assert encoded("foo").len() == 4
assert encoded("foob").len() == 8
assert encoded("the quick brown fox").len() == 28
test "a byte above ASCII goes through untouched, because a str is bytes"
# `é` is two bytes (195 and 169), so it is one padded group, and the
# decoder gives both bytes back.
assert "é".len() == 2
assert encoded("é") == "w6k="
assert decoded("w6k=").must() == bytes_of("é")
assert decoded("w6k=").must().len() == 2
test "the five refusals, each asserted by the code that names it"
# Named refusals rather than a bare `is_err()`, which is `examples/ini/`'s
# discipline: a decoder that refuses everything for one reason is a
# decoder nobody can debug, and a test that only asks `is_err()` cannot
# tell the two apart either.
assert refusal_of(decoded("Zm9")) == ERR_LENGTH
assert refusal_of(decoded("Zm9$")) == ERR_CHARACTER
assert refusal_of(decoded("=m9v")) == ERR_PADDING
assert refusal_of(decoded("Zm=v")) == ERR_PADDING
assert refusal_of(decoded("Zg==Zg==")) == ERR_PADDING
# And what is NOT refused: the last group may be padded, and only it.
assert refusal_of(decoded("Zm9vYg==")) == "nothing was refused"
test "the alphabet is the standard one, in the standard order"
# RFC 4648 § 4's table, asserted at its four corners and its length, so a
# transposed character cannot hide behind a vector that happens to pass.
assert ALPHABET.len() == 64
assert letter(0) == "A"
assert letter(25) == "Z"
assert letter(26) == "a"
assert letter(51) == "z"
assert letter(52) == "0"
assert letter(61) == "9"
assert letter(62) == "+"
assert letter(63) == "/"
test "a character and its value are each other's inverse, all sixty-four"
i: i64 @= 0
while i < 64
assert value_of(letter(i)).must() == i
i @ i + 1
assert value_of("=").is_err()
assert value_of(" ").is_err()
crc.hero · 164 righe
# CRC-32, with the catalogue's parameters written down rather than folded into
# the code — and the catalogue's own check value asserted below.
#
# Corpus material (CLAUDE.md §9). What this module is here for is
# the one place where Heroes and a 32-bit checksum disagree: **the bitwise
# operators are `i64` only** (spec § Operators), so there is no 32-bit register
# to overflow. The register is an `i64` that never grows, because every step is
# a shift RIGHT and an exclusive-or with a 32-bit constant, and the result is
# handed back as a `u32` at the boundary. A program that reached for `u32`
# arithmetic inside the loop would not compile, and that is the language
# refusing the shape rather than the author choosing one.
#
# THE ORACLE, fetched from the CRC catalogue and quoted whole:
#
# CRC-32/ISO-HDLC
# width=32 poly=0x04c11db7 init=0xffffffff refin=true refout=true
# xorout=0xffffffff check=0xcbf43926 residue=0xdebb20e3
# Alias: CRC-32, CRC-32/ADCCP, CRC-32/V-42, CRC-32/XZ, PKZIP
#
# https://reveng.sourceforge.io/crc-catalogue/17plus.htm
#
# and `check` is defined by that catalogue as *the contents of the register
# after initialising, reading the UTF-8 string "123456789" (as 8-bit
# characters), optionally reflecting, and applying the final XOR*. So
# `crc32("123456789") == 0xcbf43926` is not this program's opinion.
#
# A SECOND SOURCE, measured the same day: Python 3's `zlib.crc32` — an
# implementation nothing here shares a line with — gives `0xcbf43926` for
# `123456789`, `0x00000000` for the empty string, `0xe8b7be43` for `a` and
# `0x9ef61f95` for `foobar`. All four are asserted below.
#
# WHY THE POLYNOMIAL IS WRITTEN 0xedb88320 IN THE LOOP. The catalogue's
# `poly` is 0x04c11db7 read most-significant-bit first; `refin=true` says the
# message bits arrive least-significant first, and the standard way to obey
# that is to walk the register rightwards with the polynomial's bits reversed.
# 0xedb88320 IS 0x04c11db7 reversed, and the test below asserts exactly that
# rather than asking the reader to take it on trust.
# The catalogue's `poly`, as the catalogue writes it.
constant POLY: i64
0x04c11db7
# The same polynomial, bit-reversed, which is the form a rightwards walk needs.
constant POLY_REFLECTED: i64
0xedb88320
# The catalogue's `init` and `xorout`. They are the same value here, which is
# true of CRC-32 and not of every model, so both are named.
constant INIT: i64
0xffffffff
constant XOROUT: i64
0xffffffff
# The CRC-32 of `text`, byte by byte.
#
# `reg` is an `i64` because `>>` and `^` are `i64`-only, and it stays inside 32
# bits without a mask: it starts at 0xffffffff, every step shifts it right, and
# the only value ever xored in is 32 bits wide. The `.to_u32()` at the end is
# where the width becomes part of the type instead of a fact about the loop.
function crc32(text: str) -> u32
reg: i64 @= INIT
i: i64 @= 0
while i < text.len()
byte: u8 = text[i]
reg @ reg ^ byte.to_i64().must()
bit: i64 @= 0
while bit < 8
# `reg & 1 == 1` is `(reg & 1) == 1`: in Heroes `&` binds tighter
# than a comparison, the other way round from C, which is
# `examples/palette/`'s lesson and is why there are no parentheses
# here — `heroes fmt` deletes them.
if reg & 1 == 1
reg @ reg >> 1 ^ POLY_REFLECTED
else
reg @ reg >> 1
bit @ bit + 1
i @ i + 1
return (reg ^ XOROUT).to_u32().must()
# The bits of `value`, most significant first, reversed — used only by the test
# that checks the reflected polynomial against the catalogue's.
function reversed_bits(value: i64, width: i64) -> i64
out: i64 @= 0
i: i64 @= 0
while i < width
out @ out << 1 | value >> i & 1
i @ i + 1
return out
test "the catalogue's check value, which this program did not invent"
# CRC-32/ISO-HDLC, check=0xcbf43926 over "123456789". Fetched.
assert crc32("123456789") == 0xcbf43926
test "three more values from an implementation this one shares no line with"
# Python 3 `zlib.crc32`, run.
assert crc32("") == 0x00000000
assert crc32("a") == 0xe8b7be43
assert crc32("foobar") == 0x9ef61f95
test "the reflected polynomial is the catalogue's polynomial, reversed"
# The one derived constant in this module, derived in a test instead of in
# a comment. If somebody mistypes `POLY_REFLECTED`, this fails before the
# check value does, and it says which of the two is wrong.
assert reversed_bits(value: POLY, width: 32) == POLY_REFLECTED
assert reversed_bits(value: POLY_REFLECTED, width: 32) == POLY
test "reversing bits twice is where it started"
assert reversed_bits(value: 1, width: 32) == 0x80000000
assert reversed_bits(value: 0x80000000, width: 32) == 1
assert reversed_bits(value: 0, width: 32) == 0
assert reversed_bits(value: 0xffffffff, width: 32) == 0xffffffff
assert reversed_bits(value: reversed_bits(value: 0x12345678, width: 32), width: 32) == 0x12345678
test "the empty string is init xored with xorout, which is zero here"
# Not a coincidence worth hiding: with init == xorout the empty message's
# CRC is 0, and the assertion says so in the parameters rather than in hex.
assert crc32("") == (INIT ^ XOROUT).to_u32().must()
test "one changed byte changes the checksum, and so does one moved byte"
# What a checksum is FOR, asserted rather than assumed.
assert crc32("123456789") != crc32("123456780")
assert crc32("ab") != crc32("ba")
assert crc32("a") != crc32("aa")
test "the loop walks bytes and not characters"
# A `str` is measured in bytes (spec § Types), so `é` is two of them and
# the checksum covers both. The value is `zlib.crc32` on those two bytes,
# 195 and 169, run — and it is here because a loop over
# `text.chars()` would give a different answer while looking just as
# reasonable.
assert "é".len() == 2
assert crc32("é") == 0x0e048d3e
test "the register never leaves 32 bits, at every step of a real message"
# The claim the module's header rests on, made falsifiable: if any step
# grew past 32 bits the mask-free implementation would be wrong, and the
# check value alone would not say where.
reg: i64 @= INIT
i: i64 @= 0
text = "123456789"
while i < text.len()
reg @ reg ^ text[i].to_i64().must()
bit: i64 @= 0
while bit < 8
if reg & 1 == 1
reg @ reg >> 1 ^ POLY_REFLECTED
else
reg @ reg >> 1
assert reg >= 0
assert reg <= 0xffffffff
bit @ bit + 1
i @ i + 1
assert (reg ^ XOROUT).to_u32().must() == 0xcbf43926
impronta sha256 6c83e19f665c · 5 file · index.json
Successivo: diff. Tutti gli esempi. ← Precedente: binarytrees
Eseguine uno: i quattro comandi. Impara il linguaggio, oppure leggi la specifica completa in un solo file.