rot13.gno
1.66 Kb · 50 lines
1// Package rot13 ports Go's classic ROT13 example — the one used to teach
2// strings.Map and io.Reader in the standard library docs — to gno as a reusable
3// pure package. The core is a pure letter-rotation cipher over ASCII: ROT13
4// shifts each letter 13 places, which (since the alphabet has 26 letters)
5// makes ROT13 its own inverse. Caesar generalizes it to any shift.
6//
7// Everything here is pure strings/unicode logic: no state, no randomness,
8// no clock.
9//
10// A live demo of this package is at
11// [r/moul/x/daily/rot13demo](/r/moul/x/daily/rot13demo/v0).
12package rot13
13
14import "strings"
15
16// Rot13 applies the ROT13 substitution cipher, rotating ASCII letters by 13
17// and leaving every other byte untouched. Because 13 is half of 26,
18// Rot13(Rot13(s)) == s — the cipher is its own inverse. This mirrors the
19// canonical strings.Map example from the Go docs.
20func Rot13(s string) string {
21 return strings.Map(rot13Rune, s)
22}
23
24// rot13Rune is the mapping function handed to strings.Map — the heart of the
25// classic example.
26func rot13Rune(r rune) rune {
27 switch {
28 case r >= 'a' && r <= 'z':
29 return 'a' + (r-'a'+13)%26
30 case r >= 'A' && r <= 'Z':
31 return 'A' + (r-'A'+13)%26
32 }
33 return r
34}
35
36// Caesar generalizes ROT13 to an arbitrary shift. Negative and large shifts
37// are normalized into [0,26). Only ASCII letters move; anything else passes
38// through unchanged. Caesar(s, 13) is exactly Rot13(s).
39func Caesar(s string, shift int) string {
40 sh := rune(((shift % 26) + 26) % 26)
41 return strings.Map(func(r rune) rune {
42 switch {
43 case r >= 'a' && r <= 'z':
44 return 'a' + (r-'a'+sh)%26
45 case r >= 'A' && r <= 'Z':
46 return 'A' + (r-'A'+sh)%26
47 }
48 return r
49 }, s)
50}