Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

piglatin.gno

4.57 Kb · 168 lines
  1// Package piglatin ports the classic "Pig Latin" translator — a staple Go
  2// beginner exercise — to gno.land as a reusable pure package.
  3//
  4// Rules implemented (the standard English game):
  5//   - A word that starts with a vowel gets "way" appended:   "apple" -> "appleway"
  6//   - A word that starts with one or more consonants has that leading
  7//     consonant cluster moved to the end, followed by "ay":  "string" -> "ingstray"
  8//   - "y" acts as a consonant only when it is the first letter of the word
  9//     ("yellow" -> "ellowyay"); elsewhere it counts as a vowel ("myth" -> "ythmay").
 10//   - Original capitalization of the word is preserved (title-case in, title-case
 11//     out): "Hello" -> "Ellohay".
 12//   - Trailing/leading punctuation attached to a word is preserved in place:
 13//     "Hello," -> "Ellohay,".
 14//
 15// Everything is pure strings/unicode — deterministic and reproducible on-chain.
 16//
 17// A live demo of this package (an interactive sentence translator) is at
 18// [r/moul/x/daily/piglatindemo](/r/nym-moultest123/x/daily/piglatindemo/v0).
 19package piglatin
 20
 21import (
 22	"strings"
 23	"unicode"
 24)
 25
 26const suffixVowel = "way"
 27const suffixConsonant = "ay"
 28
 29func isVowel(r rune) bool {
 30	switch unicode.ToLower(r) {
 31	case 'a', 'e', 'i', 'o', 'u':
 32		return true
 33	}
 34	return false
 35}
 36
 37// isLetter reports whether r is an ASCII/unicode letter (word character).
 38func isLetter(r rune) bool {
 39	return unicode.IsLetter(r)
 40}
 41
 42// translateWord converts a single "core" alphabetic word (no surrounding
 43// punctuation) to Pig Latin, preserving its capitalization pattern.
 44func translateWord(word string) string {
 45	if word == "" {
 46		return word
 47	}
 48	runes := []rune(word)
 49
 50	// Find the leading consonant cluster. 'y' is a consonant only in position 0.
 51	start := 0
 52	for i, r := range runes {
 53		if isVowel(r) {
 54			break
 55		}
 56		// 'y' after the first letter behaves like a vowel: stop the cluster.
 57		if i > 0 && unicode.ToLower(r) == 'y' {
 58			break
 59		}
 60		start = i + 1
 61	}
 62
 63	var out []rune
 64	if start == 0 {
 65		// Starts with a vowel.
 66		out = append(out, runes...)
 67		out = append(out, []rune(suffixVowel)...)
 68	} else if start >= len(runes) {
 69		// All consonants (no vowel found), e.g. "shh" — just append "ay".
 70		out = append(out, runes...)
 71		out = append(out, []rune(suffixConsonant)...)
 72	} else {
 73		out = append(out, runes[start:]...)
 74		out = append(out, runes[:start]...)
 75		out = append(out, []rune(suffixConsonant)...)
 76	}
 77
 78	return applyCase(word, string(out))
 79}
 80
 81// applyCase re-applies the capitalization shape of the original word to the
 82// translated word. Two common shapes are handled: ALL CAPS and Title-case;
 83// everything else is returned lowercase.
 84func applyCase(orig, translated string) string {
 85	origRunes := []rune(orig)
 86	if len(origRunes) == 0 {
 87		return translated
 88	}
 89
 90	// Count letters and uppercase letters in the original.
 91	letters, uppers := 0, 0
 92	for _, r := range origRunes {
 93		if unicode.IsLetter(r) {
 94			letters++
 95			if unicode.IsUpper(r) {
 96				uppers++
 97			}
 98		}
 99	}
100
101	low := strings.ToLower(translated)
102	switch {
103	case letters > 0 && uppers == letters && letters > 1:
104		// ALL CAPS -> keep upper.
105		return strings.ToUpper(low)
106	case unicode.IsUpper(origRunes[0]):
107		// Title-case -> capitalize first letter of the result.
108		tr := []rune(low)
109		if len(tr) > 0 {
110			tr[0] = unicode.ToUpper(tr[0])
111		}
112		return string(tr)
113	default:
114		return low
115	}
116}
117
118// splitAffixes separates a token into (leading punctuation, core word,
119// trailing punctuation) where the core is the contiguous run of letters
120// (apostrophes inside are kept as part of the core, e.g. "don't").
121func splitAffixes(token string) (string, string, string) {
122	runes := []rune(token)
123	i := 0
124	for i < len(runes) && !isLetter(runes[i]) {
125		i++
126	}
127	j := len(runes)
128	for j > i && !isLetter(runes[j-1]) {
129		j--
130	}
131	if i >= j {
132		return token, "", "" // no letters at all
133	}
134	return string(runes[:i]), string(runes[i:j]), string(runes[j:])
135}
136
137// TranslateToken translates a single whitespace-delimited token, preserving
138// any punctuation glued to its edges.
139func TranslateToken(token string) string {
140	lead, core, trail := splitAffixes(token)
141	if core == "" {
142		return token
143	}
144	return lead + translateWord(core) + trail
145}
146
147// Translate applies Pig Latin to every word in the sentence while preserving
148// the original whitespace between words.
149func Translate(sentence string) string {
150	var b strings.Builder
151	var word strings.Builder
152	flush := func() {
153		if word.Len() > 0 {
154			b.WriteString(TranslateToken(word.String()))
155			word.Reset()
156		}
157	}
158	for _, r := range sentence {
159		if unicode.IsSpace(r) {
160			flush()
161			b.WriteRune(r)
162			continue
163		}
164		word.WriteRune(r)
165	}
166	flush()
167	return b.String()
168}