@zsnout/ithkuil
Version:
A set of tools which can generate and parse romanized Ithkuil text and which can generate Ithkuil script from text and JSON data.
164 lines (163 loc) • 6.46 kB
JavaScript
import { allPermutationsOf } from "../../helpers/permutations.js";
import { isLegalConsonantForm, isLegalWordInitialConsonantForm, } from "../../phonotactics/index.js";
import { isLegalWordFinalConsonantForm } from "../../phonotactics/word-final.js";
import { referentialPerspectiveToIthkuil, referentialPerspectiveToIthkuilAlt, } from "../perspective.js";
import { referentToIthkuil } from "./index.js";
/**
* Converts a list of referents into Ithkuil. Does not attempt to change the
* order of the referents to create phonotactically permissible consonant
* clusters. As such, the generated clusters may be phonotactically invalid.
*
* To automatically rearrange the referents to optimize for phonotactically
* correct consonant clusters, use {@link referentListToIthkuil} instead.
*
* @param referents A list of referents.
* @param perspective The perspective to attach to said referents.
* @param isReferentialAffix Whether this is used in a referential affix.
* @param isSecondReferent Whether this is the second referent in a dual
* referential.
* @returns Romanized Ithkuilic text representing the referent list.
*/
export function assembleReferentList(referents, perspective, isReferentialAffix, isSecondReferent) {
const text = referents.map((referent) => referentToIthkuil(referent, isReferentialAffix));
let output = "";
let index = 0;
if (isSecondReferent) {
for (; index < text.length; index++) {
output += text[index];
}
}
else {
for (; index < text.length; index++) {
if (isLegalWordInitialConsonantForm(output + text[index])) {
output += text[index];
}
else {
output = "ë" + output + text.slice(index);
break;
}
}
}
const persp = referentialPerspectiveToIthkuil(perspective);
const persp2 = referentialPerspectiveToIthkuilAlt(perspective);
if (output.startsWith("ë")) {
if (isLegalConsonantForm(output.slice(1) + persp)) {
return output + persp;
}
if (isLegalConsonantForm(persp + output.slice(1))) {
return "ë" + persp + output.slice(1);
}
// The following may be phonotactically invalid.
return output + persp;
}
const isLegal = isSecondReferent ?
isLegalWordFinalConsonantForm
: isLegalWordInitialConsonantForm;
if (isLegal(output + persp)) {
return output + persp;
}
if (isLegal(persp + output)) {
return persp + output;
}
if (isLegal(output + persp2)) {
return output + persp2;
}
if (isLegal(persp2 + output)) {
return persp2 + output;
}
if (isSecondReferent) {
// The following may be phonotactically invalid.
return output + persp;
}
else {
// The following may be phonotactically invalid.
return "ë" + output + persp;
}
}
/**
* Converts a list of referents into Ithkuil. Will automatically rearrange the
* order of the referents to create phonotactically permissible consonant
* clusters. As such, the generated clusters will most often be phonotactically
* valid.
*
* To avoid automatically rearranging the referents, use
* {@link assembleReferentList} instead.
*
* @param referents A list of referents.
* @param perspective The perspective to attach to said referents.
* @param isSecondReferent Whether this is the second referent in a dual
* referential.
* @returns Romanized Ithkuilic text representing the referent list.
*/
export function referentListToIthkuil(referents, perspective, isSecondReferent) {
const all = allPermutationsOf(referents.slice().sort())
.map((referentList) => assembleReferentList(referentList, perspective, false, isSecondReferent))
.sort((a, b) => a.length < b.length ? -1
: a.length > b.length ? 1
: 0);
if (isSecondReferent) {
const valid = all.find((text) => isLegalWordFinalConsonantForm(text));
return valid ?? all[0];
}
const valid = all.find((text) => isLegalWordInitialConsonantForm(text));
if (valid) {
return valid;
}
const valid2 = all.find((text) => text.startsWith("ë"));
return valid2 || all[0];
}
/**
* Converts a `ReferentList` into an Ithkuilic referential affix.
*
* @param referents The `ReferentList` to be converted.
* @param perspective The perspective to be attatched to the referent.
* @returns Romanized Ithkuilic text representing the referent.
*/
export function referentialAffixToIthkuil(referents, perspective) {
if (
// @ts-ignore
perspective == "A") {
throw new Error("Referents may not be marked Abstract in referential affixes.");
}
const options = allPermutationsOf(referents.slice().sort()).flatMap((referents) => [
referents.map((referent) => referentToIthkuil(referent, true)).join("") +
referentialPerspectiveToIthkuil(perspective),
referents.map((referent) => referentToIthkuil(referent, true)).join("") +
referentialPerspectiveToIthkuilAlt(perspective),
referentialPerspectiveToIthkuil(perspective) +
referents.map((referent) => referentToIthkuil(referent, true)).join(""),
referentialPerspectiveToIthkuilAlt(perspective) +
referents.map((referent) => referentToIthkuil(referent, true)).join(""),
]);
for (const option of options) {
if (isLegalConsonantForm(option)) {
return option;
}
}
if (options[0]) {
return options[0];
}
throw new Error("Unable to construct referential affix.");
}
function nonReferentialAffixReferentToIthkuil(referent) {
return referentToIthkuil(referent, false);
}
/**
* Converts a list of referents to an Ithkuilic personal reference root.
*
* @param list The referents to be converted.
* @returns Romanized Ithkuilic text representing the referent list.
*/
export function referentListToPersonalReferenceRoot(list) {
const sorted = list.slice().sort().reverse();
const fallback = sorted.map(nonReferentialAffixReferentToIthkuil).join("");
for (const permututation of allPermutationsOf(sorted)) {
const output = permututation
.map(nonReferentialAffixReferentToIthkuil)
.join("");
if (isLegalConsonantForm(output)) {
return output;
}
}
return fallback;
}