#!/usr/bin/lua5.1
local marigold = require 'marigold'
local lang = require 'language'
local h = marigold.h
local metavars = marigold.get_metavars()
local settings = marigold.decode_query(metavars.query_string or '')
local body
if not settings.help then
settings.randomize_seed = settings.randomize_seed or false
if settings.randomize_seed then
settings.seed = os.time()
else
settings.seed = tonumber(settings.seed) or os.time()
end
math.randomseed(settings.seed)
settings.phonemes = settings.phonemes or [[
C=mnbdTLshzwrly
V=aeiou
T=1324
]]
settings.syllables = settings.syllables or [[
CVT
CVTl
CVTn
]]
settings.orthography = settings.orthography or [[
T=th
L=lh
z=zh
(%V)1=%1́
(%V)2=%1%1́
(%V)3=%1
(%V)4=%1́%1
]]
settings.phonemes_decay = settings.phonemes_decay or 'Q'
settings.syllables_decay = settings.syllables_decay or 'Q'
settings.len_min = settings.len_min or "1"
settings.len_max = settings.len_max or "3"
settings.count = settings.count or "20"
local distributions = {
U=lang.uniform,
Q=lang.quadratic,
}
local p_dist = distributions[settings.phonemes_decay]
local s_dist = distributions[settings.syllables_decay]
local l = lang.Language(
settings.phonemes,
settings.syllables,
settings.orthography,
tonumber(settings.len_min),
tonumber(settings.len_max),
p_dist,
s_dist
)
local syllable_html = {}
for i=1,tonumber(settings.count) do
table.insert(
syllable_html,
h('li', l:romanize(l:word()))
)
end
syllable_html = h('ol', syllable_html)
function radiobutton(label, name, value, checked)
local radio = h('input', {
type="radio",
name=name,
id = name .. value,
value = value,
checked = (checked and "true") or nil
})
local lbl = {}
lbl['for'] = name .. value
local label = h('label', label, lbl)
return h('div', { radio, label })
end
function textarea(name, text, decay)
local children = { class="noborder",
h('legend', name),
h('textarea', text, { name=name, rows="6", spellcheck="false" }),
}
if decay then table.insert(children, h('fieldset', {
h('legend', name .. ' decay'),
radiobutton("uniform", name .. '_decay', 'U', settings[name .. '_decay'] == 'U'),
radiobutton("quadratic", name .. '_decay', 'Q', settings[name .. '_decay'] == 'Q'),
})
) end
return h('fieldset', children)
end
local form = h('form', {
h('label', 'seed', {
h('input', { name="seed", type="number", value=tostring(settings.seed) }),
}),
h('input', { value="update", type="Submit" } ),
h('br'),
h('label', 'randomize seed', {
h('input', {
name="randomize_seed",
type="checkbox",
checked=(settings.randomize_seed and "true") or nil,
value=(settings.randomize_seed and "on") or nil,
}),
}),
h('br'),
h('div', { class="form-flex",
textarea('phonemes', settings.phonemes, true),
textarea('syllables', settings.syllables, true),
textarea('orthography', settings.orthography, false),
}),
h('br'),
h('label', 'min syllables', {
h('input', { name="len_min", type="number", min="1", value=settings.len_min }),
}),
h('br'),
h('label', 'max syllables', {
h('input', { name="len_max", type="number", min="1", value=settings.len_max }),
}),
h('br'),
h('label', '# to generate', {
h('input', { name="count", type="number", min="1", value=settings.count }),
}),
h('input', { value="update", type="Submit" } ),
})
if settings.hide then
form = h('div', {
h('form', { h('input', { value="Show details", type="Submit" })}),
h('form', {
h('input', { name="hide", value="1", type="hidden" }),
h('input', { value="Refresh", type="Submit" })
})
})
end
local self_path = '#'
body = h('body', { h('div', { id="content",
h('a', '<- projects', { href='https://sanine.net/projects/' }),
h('br'),
form,
h('hr'),
syllable_html,
h('a', 'share this link!', {
href=self_path .. '?' .. metavars.query_string
}),
h('a', 'what the heck is this?', { href='?help=1' }),
h('a', 'source code', { href="https://sanine.net/git/amaryllis" }),
})})
else
-- help page!
body = h('body', { h('div', { id="content",
h('a', '<- projects', { href='https://sanine.net/projects/' }),
h('p', 'This is a tool for generating words in invented languages. You can set up your basic phonology (int the phonemes tab), your phonotactics (in the syllables tab), and romanization/orthography issues (in the orthography tab).'),
h('p', "The 'seed' value, at the top, is the mathematical value used for all of the other random generation. Change it to get new words, or leave it alone to tinker with the settings and see how they change the words you get."),
h('p', "In the phonemes tab you should pick out the phonemes for your language. Because of how the syllables work, you should sort them into classes. Each line, you write something like V=aeiou
, where V is the class and a, e, i, o, and u are the phones that are members of the class. (In this case the class letter V was selected to represent the vowels). The class name can only be one character long, but it can be any character. Take care not to make it a character that occurs within your phones; this may lead to strange results!"),
h('p', "In the syllables tab, you define which syllables are possible for your language, using the phoneme classes defined earlier. Each line defines one syllable type. For instance, if we have a C and V class, then CVC
would be a traditional CVC syllable. If you use letters in your syllable definitions which are not phoneme classes, they will simply be output as part of that syllable type verbatim."),
h('p', "In the orthography tab, you define rewrite rules for text. Each rule looks like [pattern]=[replacement]. Anything that matches the pattern will be replaced with replacement. The special sequence (%x), where x is one of your phoneme classes, will match any one of the phones in that class. The special sequence %1 will then be replaced in the replacement with whichever phone matched the (%x) pattern. This can be useful to avoid writing a distinct rule for each element in the class."),
h('p', "Phonemes and syllables each have an additional parameter called 'decay' which is essentially the distribution of the probabilities. Uniform means that each element has an equal probability of being selected. Quadratic means that earlier elements have a higher probability of being selected than later elements, making the sequencing important."),
h('p', "Finally, min/max syllables is simply the minimum/maximum number of syllables to generate per word, and '# to generate' is the number of words to generate."),
h('p', "Have fun!!!"),
})})
end
local head = h('head', {
h('meta', { charset="utf-8" }),
h('meta', { name="viewport", content="width=device-width, initial-scale=1" }),
h('title', 'amaryllis | sanine.net'),
h('style', [[
:root {
--light: #eee;
--dark: #1c1c1c;
--highlight: #f5ae2e;
}
body {
color: var(--light);
background: var(--dark);
font: 1.3em monospace;
text-size-adjust: auto;
}
#content {
max-width: 40em;
margin: auto;
}
.noborder {
border: none;
padding: 0px;
}
a {
color: var(--highlight);
}
a:hover {
color: var(--dark);
background: var(--highlight);
text-decoration: none;
}
.form-flex {
display: flex;
flex-wrap: wrap;
wrap: 100%;
}
textarea {
color: var(--light);
background: #191919;
border: 1px solid var(--highlight);
}
input {
font-family: monospace;
color: var(--highlight);
background: var(--dark);
border: 1px solid var(--highlight);
}
]]
),
})
print('Content-type: text/html\n')
print(marigold.html(h('html', { head, body })))