-
Notifications
You must be signed in to change notification settings - Fork 0
/
generator.go
408 lines (336 loc) · 10.5 KB
/
generator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
package neng
import (
"slices"
"strings"
)
// Iteration limit used by the default Generator
const DEFAULT_ITER_LIMIT int = 1000
/* Generates random phrases or words. */
type Generator struct {
// Main list of adjectives
adjectives []string
// Main list of adverbs
adverbs []string
// Main list of nouns
nouns []string
// Main list of verbs
verbs []string
// Adjectives that are graded by adding suffix (-er, -est)
adjSuf []string
// Non-comparable adjectives
adjNC []string
// Uncountable nouns
nounsUnc []string
// Irregularly graded adjectives
adjIrr [][]string
// Nouns with irregular plural forms
nounsIrr [][]string
// Irregular verbs
verbsIrr [][]string
// Case transformation handler
caser *caser
// A safeguard for Generator.generateModifier and Generator.Noun methods.
// In presence of MOD_COMPARATIVE, MOD_SUPERLATIVE or MOD_PLURAL, these methods
// attempt to generate a comparable adjective, adverb or countable noun until
// one is found. iterLimit was implemented to ensure the looped generation
// does not render the application unresponsive by becoming either too long
// or infinite, depending on the underlying word database.
iterLimit int
}
/*
Generates a single random adjective and transforms it according to mods.
Returns an error if:
- an undefined Mod is received (relays from Generator.Transform)
- an incompatible Mod is received (relays from Generator.Transform)
- iteration limit is reached while attempting to generate a comparable adjective
*/
func (gen *Generator) Adjective(mods ...Mod) (string, error) {
return gen.generateModifier(gen.adjectives, WC_ADJECTIVE, mods...)
}
/*
Generates a single random adverb and transforms it according to mods.
Returns an error if:
- an undefined Mod is received (relays from Generator.Transform)
- an incompatible Mod is received (relays from Generator.Transform)
- iteration limit is reached while attempting to generate a comparable adverb
*/
func (gen *Generator) Adverb(mods ...Mod) (string, error) {
return gen.generateModifier(gen.adverbs, WC_ADVERB, mods...)
}
/*
Generates a single random noun and transforms it according to mods.
Returns an error if:
- an undefined Mod is received (relays from Generator.Transform)
- an incompatible Mod is received (relays from Generator.Transform)
- iteration limit is reached while attempting to generate a countable noun
*/
func (gen *Generator) Noun(mods ...Mod) (string, error) {
n := randItem(gen.nouns)
if slices.Contains(mods, MOD_PLURAL) {
for i := 0; slices.Contains(gen.nounsUnc, n); i++ {
if i == DEFAULT_ITER_LIMIT {
return "", errIterLimit
}
n = randItem(gen.nouns)
}
}
return gen.Transform(n, WC_NOUN, mods...)
}
/*
Generates a phrase given the pattern.
Syntax:
Insertion:
%% - inserts '%' sign
%a - inserts a random adjective
%m - inserts a random adverb
%n - inserts a random noun
%v - inserts a random verb
Transformation:
%2 - transforms a verb into its Past Simple form (2nd form)
%3 - transforms a verb into its Past Participle form (3rd form)
%N - transforms a verb into its Present Simple form (now)
%c - transforms an adjective or an adverb into comparative (better)
%g - transforms a verb into gerund
%p - transform a noun or a verb (Present Simple) into its plural form
%s - transforms an adjective or an adverb into superlative (best)
%l - transform a word to lower case
%t - transform a word to Title Case
%u - transform a word to UPPER CASE
Error is returned if:
- provided pattern is empty
- character other than the above is escaped with a '%' sign
- a single '%' ends the pattern
- incompatible modifier is assigned to the word
Error is not returned if:
- duplicate modifier is assigned to the same word
Example phrase:
"%tn %2v a %ua %un" may produce "Serenade perplexed a STRAY SUPERBUG"
*/
func (gen *Generator) Phrase(pattern string) (string, error) {
if len(pattern) == 0 {
return "", errEmptyPattern
}
var (
// If true, the next character is interpreted as syntax character
escaped bool
// Container for modifiers for the current word
mods []Mod
// Built phrase
phrase strings.Builder
)
for i, c := range pattern {
if escaped {
switch c {
case '%':
phrase.WriteRune(c)
escaped = false
case '2', '3', 'N', 'c', 'g', 'l', 'p', 's', 't', 'u':
mods = append(mods, flagToMod(c))
case 'a', 'm', 'n', 'v':
word, err := gen.getGenerator(c)(mods...)
if err != nil {
return "", err
}
phrase.WriteString(word)
escaped = false
default:
return "", errUnknownCommand
}
} else if c == '%' {
if i == len(pattern)-1 {
return "", errEscapedStrTerm
}
escaped = true
mods = make([]Mod, 0)
} else {
phrase.WriteRune(c)
}
}
return phrase.String(), nil
}
/*
Transforms a word according to specified mods. Not all mods are compatible with every WordClass.
Returns an error if:
- WordClass of the word is not compatible with any Mod in mods
- transformation into comparative or superlative form is requested for non-comparable adjective or adverb
- transformation into plural form is requested for an uncountable noun
*/
func (gen *Generator) Transform(word string, wc WordClass, mods ...Mod) (string, error) {
if !wc.CompatibleWith(mods...) {
return "", errIncompatible
}
switch wc {
case WC_ADJECTIVE, WC_ADVERB:
if (slices.Contains(mods, MOD_COMPARATIVE) || slices.Contains(mods, MOD_SUPERLATIVE)) && slices.Contains(gen.adjNC, word) {
return "", errNonComparable
}
case WC_NOUN:
if slices.Contains(mods, MOD_PLURAL) && slices.Contains(gen.nounsUnc, word) {
return "", errUncountable
}
}
var (
caseTransformation func(string) string
pluralMod bool
)
// Ensure MOD_PLURAL is processed first
slices.Sort(mods)
for _, m := range mods {
switch m {
case MOD_PLURAL:
pluralMod = true
case MOD_GERUND:
word = gerund(word)
case MOD_PRESENT_SIMPLE:
word = presentSimple(word, pluralMod)
case MOD_PAST_SIMPLE:
word = pastSimple(word, gen.verbsIrr, pluralMod)
case MOD_PAST_PARTICIPLE:
word = pastParticiple(word, gen.verbsIrr)
case MOD_COMPARATIVE:
word = comparative(word, gen.adjIrr, gen.adjSuf)
case MOD_SUPERLATIVE:
word = superlative(word, gen.adjIrr, gen.adjSuf)
case MOD_CASE_LOWER:
caseTransformation = gen.caser.toLower
case MOD_CASE_TITLE:
caseTransformation = gen.caser.toTitle
case MOD_CASE_UPPER:
caseTransformation = gen.caser.toUpper
default:
return "", errUndefinedMod
}
}
if pluralMod && wc != WC_VERB {
word = plural(word, gen.nounsIrr)
}
if caseTransformation != nil {
word = caseTransformation(word)
}
return word, nil
}
/*
Generates a single random verb and transforms it according to mods.
Returns an error if an undefined Mod is received.
*/
func (gen *Generator) Verb(mods ...Mod) (string, error) {
return gen.Transform(randItem(gen.verbs), WC_VERB, mods...)
}
/*
A common method used to generate adjectives (noun modifiers) and adverbs (verb modifiers).
Returns error if Generator.iterLimit is reached while attempting to generate a comparable
adjective or adverb. Relays errUndefinedMod from Generator.Transform.
Returns an error if:
- an undefined Mod is received (relays from Generator.Transform)
- an incompatible Mod is received (relays from Generator.Transform)
- Generator.iterLimit is reached while attempting to generate a comparable adjective or adverb
*/
func (gen *Generator) generateModifier(items []string, wc WordClass, mods ...Mod) (string, error) {
a := randItem(items)
if slices.Contains(mods, MOD_COMPARATIVE) || slices.Contains(mods, MOD_SUPERLATIVE) {
for i := 0; slices.Contains(gen.adjNC, a); i++ {
if i == DEFAULT_ITER_LIMIT {
return "", errIterLimit
}
a = randItem(items)
}
}
return gen.Transform(a, wc, mods...)
}
/*
A helper method that was created to make the loop in Generator.Phrase easier to understand.
It accepts an insertion command character and returns the corresponding generator method.
nil is never returned as this method is only called when a valid insertion command
is encountered.
*/
func (gen *Generator) getGenerator(flag rune) func(...Mod) (string, error) {
switch flag {
case 'a':
return gen.Adjective
case 'm':
return gen.Adverb
case 'n':
return gen.Noun
case 'v':
return gen.Verb
default:
return nil
}
}
/* Returns a new Generator with default word lists. */
func DefaultGenerator() (*Generator, error) {
a, err := loadWords("res/adj")
if err != nil {
return nil, err
}
m, err := loadWords("res/adv")
if err != nil {
return nil, err
}
n, err := loadWords("res/noun")
if err != nil {
return nil, err
}
v, err := loadWords("res/verb")
if err != nil {
return nil, err
}
return NewGenerator(a, m, n, v, DEFAULT_ITER_LIMIT)
}
/*
Initializes a new Generator with provided lists. Returns an error if any of the lists is empty.
iterLimit is a safeguard for Generator.Adjective, Generator.Adverb and Generator.Noun methods.
In presence of MOD_COMPARATIVE, MOD_SUPERLATIVE or MOD_PLURAL, those methods generate a word
candidate until they find a comparable / countable one or until iteration limit is reached.
iterLimit value should be set with regards to the size of your word base
and the number of non-comparable adjectives, adverbs and uncountable nouns in it.
For example, to meet the default iterLimit of 1000, the Generator would need to draw
a non-comparable or uncountable word 1,000 times in a row. The embedded database contains
approximately 10,000 adjectives, of which 700 are non-comparable, and 25,000 nouns,
with 1,700 being uncountable. Given these numbers, it is unlikely that the iterLimit
will be reached.
*/
func NewGenerator(adj, adv, noun, verb []string, iterLimit int) (*Generator, error) {
if iterLimit <= 0 {
return nil, errBadIterLimit
}
if len(adj) == 0 || len(adv) == 0 || len(noun) == 0 || len(verb) == 0 {
return nil, errEmptyLists
}
var (
err error
gen = Generator{
adjectives: adj,
adverbs: adv,
nouns: noun,
verbs: verb,
caser: newCaser(),
iterLimit: iterLimit,
}
)
gen.adjIrr, err = loadIrregularWords("res/adj.irr")
if err != nil {
return nil, err
}
gen.adjSuf, err = loadWords("res/adj.suf")
if err != nil {
return nil, err
}
gen.adjNC, err = loadWords("res/adj.ncmp")
if err != nil {
return nil, err
}
gen.nounsUnc, err = loadWords("res/noun.unc")
if err != nil {
return nil, err
}
gen.nounsIrr, err = loadIrregularWords("res/noun.irr")
if err != nil {
return nil, err
}
gen.verbsIrr, err = loadIrregularWords("res/verb.irr")
if err != nil {
return nil, err
}
return &gen, nil
}