glossa 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/glossa/language.rb +313 -313
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e411201740c0977631838ecc4e8e87341fa5918
|
4
|
+
data.tar.gz: 6c5ac0dbfdb66aefff3fb72c832c57748b32920c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9cf7fc1d3f04219fee3a989f15fb20e47aa654f7f407e272698669a1408a5166d5f1a5ed721c03391e72821329dcd2576ea2858f757bbd7124c9e6c6f63c95da
|
7
|
+
data.tar.gz: d34bcaa18462414143ee0944329460ff0cfefa4684fb8879ec4138cc223e496003e8092e93acb9949bf43044eef9741b4f593967aadc782ca9daa9967824cb28
|
data/lib/glossa/language.rb
CHANGED
@@ -1,315 +1,315 @@
|
|
1
1
|
class Glossa::Language
|
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
|
-
|
2
|
+
attr_accessor :phonemes, :structure, :exponent, :restricts, :cortho, :vortho, :noortho, :nomorph, :nowordpool, :minsyll, :maxsyll, :morphemes, :words, :names, :genitive, :definitive, :joiner, :maxchar, :minchar
|
3
|
+
|
4
|
+
def initialize(random = false, options = nil)
|
5
|
+
if random
|
6
|
+
@phonemes = {
|
7
|
+
"C" => shuffle(choose(Glossa::CON_SETS, 2)[:C]),
|
8
|
+
"V" => shuffle(choose(Glossa::VOW_SETS, 2)[:V]),
|
9
|
+
"L" => shuffle(choose(Glossa::L_SETS, 2)[:L]),
|
10
|
+
"S" => shuffle(choose(Glossa::S_SETS, 2)[:S]),
|
11
|
+
"F" => shuffle(choose(Glossa::F_SETS, 2)[:F])
|
12
|
+
}
|
13
|
+
@noortho = false
|
14
|
+
@nomorph = false
|
15
|
+
@nowordpool = false
|
16
|
+
@structure = choose(Glossa::SYLL_STRUCTS)
|
17
|
+
@exponent = rand(1..3) # a larger exponent means less variation when randomly choosing some elements
|
18
|
+
@restricts = Glossa::RESTRICT_SETS[2][:res]
|
19
|
+
@cortho = choose(Glossa::C_ORTH_SETS, 2)[:orth]
|
20
|
+
@vortho = choose(Glossa::V_ORTH_SETS, 2)[:orth]
|
21
|
+
@morphemes = {}
|
22
|
+
@words = {}
|
23
|
+
@names = []
|
24
|
+
@joiner = choose(' -')
|
25
|
+
@maxchar = rand(10...15)
|
26
|
+
@minchar = rand(3...5)
|
27
|
+
@minsyll = rand(1...3)
|
28
|
+
@maxsyll = rand(@minsyll + 1...7)
|
29
|
+
|
30
|
+
if @structure.length < 3
|
31
|
+
@minsyll += 1;
|
32
|
+
end
|
33
|
+
else
|
34
|
+
options ||= {}
|
35
|
+
@phonemes = options[:phonemes] || {
|
36
|
+
"C" => "ptkmnls",
|
37
|
+
"V" => "aeiou",
|
38
|
+
"S" => "s",
|
39
|
+
"F" => "mn",
|
40
|
+
"L" => "rl"
|
41
|
+
}
|
42
|
+
@structure = options['structure'] || "CVC"
|
43
|
+
@exponent = options['exponent'] || 2
|
44
|
+
@restricts = options['restricts'] || []
|
45
|
+
@cortho = options['cortho'] || {}
|
46
|
+
@vortho = options['vortho'] || {}
|
47
|
+
@noortho = options['noortho'] || true
|
48
|
+
@nomorph = options['nomorph'] || true
|
49
|
+
@nowordpool = options['nowordpool'] || true
|
50
|
+
@minsyll = options['minsyll'] || 1
|
51
|
+
@maxsyll = options['maxsyll'] || 1
|
52
|
+
@morphemes = options['morphemes'] || {}
|
53
|
+
@words = options['words'] || {}
|
54
|
+
@names = options['names'] || []
|
55
|
+
@joiner = options['joiner'] || ' '
|
56
|
+
@maxchar = options['maxchar'] || 12
|
57
|
+
@minchar = options['minchar'] || 5
|
58
|
+
end
|
59
|
+
|
60
|
+
@genitive = get_morpheme('of')
|
61
|
+
@definitive = get_morpheme('the')
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Takes an array and picks a semi-random element, with the first
|
66
|
+
# elements weighted more frequently the the last elements by using
|
67
|
+
# the power of a given exponent.
|
68
|
+
def choose(list, exponent = 1)
|
69
|
+
if !exponent.kind_of?(Integer) || !exponent.kind_of?(Float) then exponent = exponent.to_i end
|
70
|
+
listIndex = ((rand ** exponent) * list.length).floor
|
71
|
+
list[listIndex]
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Takes an array or string and shuffles it into a random order.
|
76
|
+
def shuffle(list)
|
77
|
+
is_string = list.is_a? String
|
78
|
+
l = is_string ? list.chars : list
|
79
|
+
new_list = l.dup
|
80
|
+
|
81
|
+
i = 0;
|
82
|
+
l.each do |item|
|
83
|
+
tmp = item
|
84
|
+
rand_index = rand(i)
|
85
|
+
new_list[i] = new_list[rand_index]
|
86
|
+
new_list[rand_index] = tmp
|
87
|
+
i += 1;
|
88
|
+
end
|
89
|
+
|
90
|
+
if is_string
|
91
|
+
return new_list.join
|
92
|
+
else
|
93
|
+
return new_list
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Takes an array of strings and an optional joiner string
|
99
|
+
# and concatenates them into a single string
|
100
|
+
def join(list, sep = '')
|
101
|
+
return '' if list.length == 0
|
102
|
+
first_word = list.shift
|
103
|
+
list.each do |item|
|
104
|
+
first_word << sep << item
|
105
|
+
end
|
106
|
+
|
107
|
+
first_word
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Takes an array of phonetic syllables, and spells them using the languages orthography
|
112
|
+
def spell(syllables)
|
113
|
+
return syllables if self.noortho
|
114
|
+
s = syllables.chars
|
115
|
+
word = ''
|
116
|
+
s.each do |syllable|
|
117
|
+
if self.cortho[syllable]
|
118
|
+
word << self.cortho[syllable]
|
119
|
+
elsif self.vortho[syllable]
|
120
|
+
word << self.vortho[syllable]
|
121
|
+
elsif Glossa::DEFAULT_ORTHO[syllable]
|
122
|
+
word << Glossa::DEFAULT_ORTHO[syllable]
|
123
|
+
else
|
124
|
+
word << syllable
|
125
|
+
end
|
126
|
+
end
|
127
|
+
word
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Creates a spelled (see the spell() method) syllable, according to
|
132
|
+
# the language's syllable structure. It does this by selecting a semi-random
|
133
|
+
# phonetic letter for the appropriate phoneme type in the structure, making sure
|
134
|
+
# that it doesn't conflict with a restricted pattern, and then spells the
|
135
|
+
# phonetic word according to the language's orthography.
|
136
|
+
def make_syllable
|
137
|
+
structure = self.structure.chars
|
138
|
+
|
139
|
+
while true
|
140
|
+
syll = ''
|
141
|
+
structure.each do |ptype|
|
142
|
+
# If the char is '?', skip with 50% chance to remove last character
|
143
|
+
# (think RegEx usage of '?')
|
144
|
+
if ptype == '?'
|
145
|
+
if rand < 0.5
|
146
|
+
syll = syll[0...syll.length - 1]
|
147
|
+
end
|
148
|
+
next
|
149
|
+
end
|
150
|
+
|
151
|
+
syll << choose(self.phonemes[ptype], self.exponent)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Make sure this syllable doesn't violate a restriction
|
155
|
+
bad = false
|
156
|
+
self.restricts.each do |regex|
|
157
|
+
if /#{regex}/ =~ syll
|
158
|
+
bad = true
|
159
|
+
break
|
160
|
+
end
|
161
|
+
end
|
162
|
+
next if bad
|
163
|
+
|
164
|
+
return spell(syll)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# The lowest common-denominator "word" that we will store for our language.
|
170
|
+
# Morphemes are the smallest unit of language that has a meaning.
|
171
|
+
# A "morpheme," in this sense, is a unique syllable (spelled according to our orthography)
|
172
|
+
# with a type (key). Whenever a morpheme is created, we store it in the class instance,
|
173
|
+
# so as to make sure we don't create duplicates. Morphemes comprise words.
|
174
|
+
def get_morpheme(key = '')
|
175
|
+
return make_syllable if self.nomorph
|
176
|
+
|
177
|
+
# make_word will sometimes pass in nil
|
178
|
+
if key.nil?
|
179
|
+
key = ''
|
180
|
+
end
|
181
|
+
|
182
|
+
# Use the morphemes we've already made for this kind of word if possible
|
183
|
+
list = self.morphemes[key] || []
|
184
|
+
|
185
|
+
# If a key is not specified, make 10 generic morphemes
|
186
|
+
# otherwise, just make one new one
|
187
|
+
extras = key == '' ? 10 : 1
|
188
|
+
|
189
|
+
while true
|
190
|
+
# As more morphemes are created, there is a
|
191
|
+
# diminishing chance that a new one will be created.
|
192
|
+
n = rand(list.length + extras)
|
193
|
+
return list[n] if list[n]
|
194
|
+
|
195
|
+
# An existing morpheme was not selected, so create a new one
|
196
|
+
morph = make_syllable
|
197
|
+
|
198
|
+
# No duplicates!
|
199
|
+
bad = false
|
200
|
+
self.morphemes.each do |k|
|
201
|
+
next if self.morphemes[k].nil?
|
202
|
+
if self.morphemes[k].include? morph
|
203
|
+
bad = true
|
204
|
+
break
|
205
|
+
end
|
206
|
+
end
|
207
|
+
next if bad
|
208
|
+
list << morph
|
209
|
+
self.morphemes[key] = list
|
210
|
+
|
211
|
+
return morph
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
##
|
216
|
+
# Given the min- and max-syllables for our language, create a new
|
217
|
+
# word out of a random number of morphemes.
|
218
|
+
def make_word(key)
|
219
|
+
num_sylls = rand(self.minsyll..self.maxsyll)
|
220
|
+
word = ''
|
221
|
+
keys = []
|
222
|
+
|
223
|
+
# If a key is defined, then select one of the syllables
|
224
|
+
# to have a morpheme of that type.
|
225
|
+
keys[rand(num_sylls)] = key
|
226
|
+
num_sylls.times { |i| word << get_morpheme(keys[i]) }
|
227
|
+
|
228
|
+
word
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# This method has a chance to use an existing word, or create a new
|
233
|
+
# one of the type (key) specified using the make_word method. If a
|
234
|
+
# new word is created, it will make sure that it is not duplicating
|
235
|
+
# an existing word, and then add it to the list of stored words.
|
236
|
+
def get_word(key = '')
|
237
|
+
words = self.words[key] || []
|
238
|
+
extras = key == '' ? 2 : 3
|
239
|
+
|
240
|
+
while true
|
241
|
+
n = rand(words.length + extras)
|
242
|
+
existing_word = words[n]
|
243
|
+
return existing_word if existing_word
|
244
|
+
|
245
|
+
new_word = make_word(key)
|
246
|
+
bad = false
|
247
|
+
self.words.each do |word|
|
248
|
+
if word.include? new_word
|
249
|
+
bad = true
|
250
|
+
break
|
251
|
+
end
|
252
|
+
end
|
253
|
+
next if bad
|
254
|
+
words << new_word
|
255
|
+
self.words[key] = words
|
256
|
+
|
257
|
+
return new_word
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
##
|
262
|
+
# A wrapper with some additional logic around the get_word method.
|
263
|
+
# make_name will create a name, using get_word (so the words created
|
264
|
+
# to make up the name will be saved by the specified key), and have
|
265
|
+
# a 50% chance to add an additional word, and potentially the genitive
|
266
|
+
# and definitive words. After checking to make sure that it's an ok size
|
267
|
+
# and isn't already used, it saves and returns the name
|
268
|
+
def make_name(key = '')
|
269
|
+
## If you don't dup these, words will get concatenated onto them during the join process
|
270
|
+
genitive = self.genitive.dup
|
271
|
+
definitive = self.definitive.dup
|
272
|
+
|
273
|
+
while true
|
274
|
+
# 50% chance that the name will be a single word, 50% chance that it will be two words combined somehow
|
275
|
+
name = rand < 0.5 ? get_word(key) : nil
|
276
|
+
if name.nil?
|
277
|
+
# 60% chance that each word will use the same key as invoked
|
278
|
+
w1_key = rand < 0.6 ? key : ''
|
279
|
+
w1 = get_word(w1_key).capitalize
|
280
|
+
w2_key = rand < 0.6 ? key : ''
|
281
|
+
w2 = get_word(w2_key).capitalize
|
282
|
+
|
283
|
+
# 50% chance to be joined without the lang's genitive word
|
284
|
+
if rand > 0.5
|
285
|
+
name = join([w1, w2], self.joiner)
|
286
|
+
else
|
287
|
+
name = join([w1, genitive, w2], self.joiner)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# 10% to prefix with definitive
|
292
|
+
name = join([definitive, name], self.joiner) if rand < 0.1
|
293
|
+
|
294
|
+
# Generate another one if this doesn't meet the min- or maxchar reqs
|
295
|
+
next if (name.length < self.minchar) || (name.length > self.maxchar)
|
296
|
+
|
297
|
+
name.capitalize!
|
298
|
+
|
299
|
+
# Check to see if this string has already been generated
|
300
|
+
used = false
|
301
|
+
self.names.each do |lang_name|
|
302
|
+
if (name.include? lang_name) || (lang_name.include? name)
|
303
|
+
used = true
|
304
|
+
break
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Start over if this name exists already
|
309
|
+
next if used
|
310
|
+
|
311
|
+
self.names << name
|
312
|
+
return name
|
313
|
+
end
|
314
|
+
end
|
315
315
|
end
|