eluna-scripts-ornfelt/lua/WowEmulationScriptPack/Lua/ItemReload/namegen/namegen.lua
2024-09-23 07:59:23 +02:00

500 lines
14 KiB
Lua

--[[
symbol group name parser field
------ ---------- ------------
s syllablesStart parser_data["start"]
m syllablesMiddle parser_data["middle"]
e syllablesEnd parser_data["end"]
P syllablesPre parser_data["pre"]
p syllablesPost parser_data["post"]
v phonemesVocals parser_data["vocals"]
c phonemesConsonants parser_data["consonants"]
A customGroupA parser_data["cga"]
B customGroupB parser_data["cgb"]
... (custom groups exist from `A` to `O`)
N customGroupN parser_data["cgn"]
O customGroupO parser_data["cgo"]
? phonemesVocals/ parser_data["vocals"]/
phonemesConsonants parser_data["consonants"]
]]
local PATH = string.match(debug.getinfo(1,"S").source,
"^@(.+/)[%a%-%d_]+%.lua$") or "./"
local function loadrequire(module)
local loaded
local function requiref(module)
loaded = require(module)
end
pcall(requiref, module)
return loaded
end
local RangedTable = require("namegen.rangedtable")
-- ======================
-- VARIABLES
-- ======================
-- the table containing the generators
local namegen_generators_list = {}
local namegen = {}
-- ======================
-- UTILITIES
-- ======================
local function file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end
local function get_path(filename)
if filename:find("%.cfg$") then
return PATH .. filename
else
return PATH .. filename .. ".cfg"
end
end
local function split(str)
local t = {}
for v in string.gmatch(str:gsub(",", ""), "%S+") do
t[#t + 1] = v
end
return t
end
local function ng_debug(str, ...)
if DEBUG == true then
if ... then
print(string.format(str, ...))
else
print(str)
end
end
end
-- ======================
-- PARSERS
-- ======================
local function parse_rules(rules)
local rules_weight = {}
local rule_pattern = [[^%%*(%d*)(%S+)]]
for _, v in pairs(rules) do
local chance, rule = string.match(v, rule_pattern)
ng_debug([[chance: "%s", rule: "%s"]], chance, rule)
chance = chance == "" and 100 or tonumber(chance)
rules_weight[#rules_weight + 1] = {chance, rule}
end
return RangedTable(rules_weight)
end
local function parse_property(name, value, parser_data)
local v = name == "name" and value or split(value)
if name == "name" then
ng_debug([[parse_property: "%s"]], value)
parser_data["name"] = v
elseif name == "syllablesStart" then
parser_data["start"] = v
elseif name == "syllablesMiddle" then
parser_data["middle"] = v
elseif name == "syllablesEnd" then
parser_data["end"] = v
elseif name =="syllablesPre" then
parser_data["pre"] = v
elseif name =="syllablesPost" then
parser_data["post"] = v
elseif name == "phonemesVocals" then
parser_data["vocals"] = v
elseif name =="phonemesConsonants" then
parser_data.consonants = v
elseif name == "rules" then
parser_data["rules"] = parse_rules(v)
elseif name == "illegal" then
-- /* illegal strings are converted to lowercase */
parser_data.illegal = split(string.lower(value))
else
local cg = string.match(name, "^customGroup(%a)")
if cg then
parser_data["cg" .. cg:lower()] = v
else
ng_debug("parse_property else")
return false
end
end
return true
end
local function parse_lines(path, data)
ng_debug("starting `parse_lines`", path)
local data
local name_pattern = [[name ?"(.+)" ?{]]
local property_pattern = [[ +(.+) = "(.+)"]]
local end_body_pattern = [[}]]
local body = false
for line in io.lines(path) do
local name = string.match(line, name_pattern)
if name ~= nil then
namegen_generators_list[name] = {}
data = namegen_generators_list[name]
parse_property("name", name, data)
body = true
elseif body == true then
if string.match(line, end_body_pattern) then
body = false
else
local name, value = string.match(line, property_pattern)
if name and value then
parse_property(name, value, data)
end
end
end
end
ng_debug("ending `parse_lines`")
end
local function parse_file(filename)
ng_debug("starting `parse_file`")
local path = get_path(filename)
if not file_exists(path) then
error(string.format("File \"%s\" not found!\n",path))
end
parse_lines(get_path(filename))
ng_debug("ending `parse_file`")
end
local function parse_index()
ng_debug("starting `parse_index`")
parse_file("itemdefinitions.cfg")
ng_debug("ending `parse_index`")
end
-- ======================
-- WORD VALIDATION
-- ======================
-- check for occurrences of triple characters (case-insensitive)
local function word_has_triples(str)
local str = str:lower()
for i = 1, #str - 2 do
local a = str:sub(i, i)
local b = str:sub(i+1, i+1)
local c = str:sub(i+2, i+2)
if a == b and a == c then
return true
end
end
return false
end
-- check for occurrences of illegal strings (case-insensitive)
local function word_has_illegal(data, str)
local str = str:lower()
if not data.illegal then return false end
for i = 1, #data.illegal do
if str:find(data.illegal[i]) then
return true
end
end
return false
end
-- check for repeated syllables (case-insensitive)
local function word_repeated_syllables(str)
local word = str:lower():gsub("['%-_]", "")
for step = 2, math.min(5, math.floor(#str / 2)) do
for i = 1, #word - step + 1 do
local search = word:sub(i, i + step - 1)
local sub = word:sub(i + step, (i + step) + step - 1)
if search == sub then
return true
end
ng_debug("not repeatted", str, step, search, sub)
end
end
return false
end
-- verify if the word passes the above checks
local function word_is_ok(data, str)
return ((#str > 0) and
not word_has_triples(str) and
not word_has_illegal(data, str) and
not word_repeated_syllables(str))
end
-- removes double, leading and ending spaces
local function word_prune_spaces(str)
str = str:gsub(" +$", "")
str = str:gsub("^ +", "")
str = str:gsub(" +", " ")
return str
end
local function get_lst_from_token(token, data)
if token == 'P' then
ng_debug("token case 1")
return data["pre"]
elseif token == 's' then
ng_debug("token case 2")
return data["start"]
elseif token == 'm' then
ng_debug("token case 3")
return data["middle"]
elseif token == 'e' then
ng_debug("token case 4")
return data["end"]
elseif token == 'p' then
ng_debug("token case 5")
return data["post"]
elseif token == 'v' then
ng_debug("token case 6")
return data["vocals"]
elseif token == 'c' then
ng_debug("token case 7")
return data["consonants"]
elseif token == '?' then
ng_debug("token case 8")
return ((random(1, 2) == 1) and data.vocals or
data.consonants)
elseif token >= "A" and token < "P" then
ng_debug("token case 9")
return data["cg" .. token:lower()]
elseif token == "'" then
ng_debug("token case 10")
return {"'"}
end
end
local function generate_custom(name, rule)
local random = math.random
local data = namegen_generators_list[name]
if data == nil then
error(string.format("The name \"%s\" has not been found.\n",name))
end
-- start name generation
local buf, i, it
repeat
buf = ""
i = 1
while i <= #rule do
it = rule:sub(i, i)
-- append normal character
if ((it >= 'a' and it <= 'z') or
(it >= 'A' and it <= 'Z') or
it == '\'' or it == '-')
then
ng_debug("buf case 1")
buf = buf .. it
elseif it == '/' then
-- special character
i = i + 1
ng_debug("buf case 2")
buf = buf .. it
elseif it == '_' then
-- convert underscore to space
ng_debug("buf case 3")
buf = buf .. " "
-- interpret a wildcard
elseif it == '$' then
local chance = 100;
i = i + 1
local it = rule:sub(i, i)
ng_debug("buf case 4, it: %s", it)
-- food for the randomiser
if it >= '0' and it <= '9' then
ng_debug("buf case 4.1")
chance = 0
while it >= '0' and it <= '9' do
chance = chance * 10
chance = chance + tonumber(it)
i = i + 1
it = rule:sub(i, i);
end
end
-- evaluate the wildcard according to its chance
if chance >= random(100) then
ng_debug("buf case 4.2")
local lst = get_lst_from_token(it, data)
if lst == nil then
error(string.format(
[[Wrong rules syntax(it:"%s", rule:"%s")]],
it, rule))
end
-- got the list, now choose something on it
if #lst == 0 then
error(string.format(
"No data found in the requested string (wildcard %s). Check your name generation rule %s.",
it,rule
))
else
buf = buf .. (lst[random(1, #lst)]:gsub('_', ' '))
end
end
end
ng_debug([[i: %d, buf: %s, it: %s, rule: %s]],
i, buf, it, rule)
i = i + 1
end
until word_is_ok(data, buf)
ng_debug([[ending "generate_custom 'repeat ... until'"]])
-- prune undesired spaces and return the name
return word_prune_spaces(buf)
end
-- generate a new name with one of the rules from set
local function generate(name)
ng_debug([[starting "generate(%s)"]], name)
local data = namegen_generators_list[name]
if data == nil then
error(string.format("The name \"%s\" has not been found.\n",name))
end
-- check if the rules list is present */
if data.rules:size() == 0 then
error("The rules list is empty!")
end
-- choose the rule */
local res = generate_custom(name, data.rules:choice())
ng_debug([[starting "generate(%s)"]], name)
return res
end
local function possible_rules(str)
local res = {[str] = true}
while true do
local changed = false
local count = #res
for rule, _ in pairs(res) do
local chance_rule = rule:match(
"%$?[%a_'%- ]*(%$%d+[%a_'%- ]+)%$*.*$")
-- print(rule, chance_rule)
if chance_rule then
local a = rule:gsub(chance_rule, "")
local b = rule:gsub("%$%d+", "$", 1)
-- print(rule, a, b)
res[rule] = nil
res[a] = true
res[b] = true
changed = true
break
end
end
if not changed then break end
end
--[[
print(str)
for k, _ in pairs(res) do
print(k)
end
]]--
return res
end
local function exhaust_rules(name)
local data = namegen_generators_list[name]
local rules = {}
for v in data.rules:values() do
rules[#rules + 1] = v
end
local plain_rules = {}
for _, rule in ipairs(rules) do
local possible = possible_rules(rule)
for plain, _ in pairs(possible) do
plain_rules[#plain_rules + 1] = plain
end
end
return plain_rules
end
local function combine_strings(...)
local str
for _, v in pairs(...) do
str = (str and " " or "") .. v
end
return str
end
local function map_all(fcn, tab, idx, ...)
-- http://stackoverflow.com/a/13059680/5496529
if idx < 1 then
fcn(...)
else
local t = tab[idx]
for i = 1, #t do map_all(fcn, tab, idx-1, t[i], ...) end
end
end
local function exhaust_set(name)
local names = {}
local data = namegen_generators_list[name]
local rules = exhaust_rules(name)
local function combine(...)
local t = {...}
local str
for i, v in ipairs(t) do
if v ~= nil and v ~= "" then
str = (str and (str .. v) or v)
end
end
print(str)
str = word_prune_spaces(str)
names[str] = str
end
for _, rule in ipairs(rules) do
local groups = {}
local tokens = split(rule:gsub("%$", " "))
for _, token in ipairs(tokens) do
for c in string.gmatch(token, ".") do
local lst = (get_lst_from_token(c, data) or
{[1] = c:gsub("_", " ")})
if lst == nil then
error("invalid list", c)
end
groups[#groups + 1] = lst
end
end
map_all(combine, groups, #groups)
end
return names
end
local function get_sets()
local t = {}
for k, _ in pairs(namegen_generators_list) do
if namegen_generators_list[k].rules then
t[#t + 1] = k
end
end
return t
end
-- ------------------------------
-- load default sets (as specified on `namegen.index`)
-- ------------------------------
parse_index()
-- ------------------------------
-- publicly available functions
-- ------------------------------
namegen.get_sets = get_sets
namegen.parse_file = parse_file
namegen.generate = generate
namegen.generate_custom = generate_custom
namegen.exhaust_set = exhaust_set
namegen.exhaust_rules = exhaust_rules
return namegen