ephemeral-writer
Version:
A procedural text generation system.
716 lines (715 loc) • 27.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.modifiers = exports.Grammar = exports.createGrammar = void 0;
class EphemeralNode {
constructor(parent, grammar, childIndex, settings) {
this.toString = () => {
return "Node('" + this.raw + "' " + this.type + " d:" + this.depth + ")";
};
this.expandChildren = (childRule, preventRecursion) => {
this.childRule = childRule;
if (this.childRule !== undefined) {
var { sections, errors } = parse(childRule);
if (errors.length > 0) {
this.errors = this.errors.concat(errors);
}
for (var i = 0; i < sections.length; i++) {
this.children[i] = new EphemeralNode(this, this.grammar, i, {
raw: sections[i].raw,
type: sections[i].type,
scratch: this.scratch
});
if (!preventRecursion)
this.children[i].expand(preventRecursion);
this.finishedText += this.children[i].finishedText;
}
}
else {
this.errors.push("No child rule provided, can't expand children");
console.warn("No child rule provided, can't expand children");
}
};
this.expand = (preventRecursion) => {
if (!this.isExpanded) {
this.isExpanded = true;
this.expansionErrors = [];
switch (this.type) {
case -1:
this.expandChildren(this.raw, preventRecursion);
break;
case 0:
this.finishedText = this.raw;
break;
case 1:
this.preactions = [];
this.postactions = [];
var parsed = parseTag(this.raw);
this.symbol = parsed.symbol;
this.modifiers = parsed.modifiers;
for (var i = 0; i < parsed.preactions.length; i++) {
this.preactions.push(new NodeAction(this, parsed.preactions[i]));
}
for (var i = 0; i < parsed.postactions.length; i++) {
}
for (var i = 0; i < this.preactions.length; i++) {
if (this.preactions[i].type === 0) {
const undoAction = this.preactions[i].createUndo();
if (undoAction)
this.postactions.push(undoAction);
}
}
for (var i = 0; i < this.preactions.length; i++) {
this.preactions[i].activate();
}
var selectedRule = this.symbol ? this.grammar.selectRule(this.symbol, this, this.errors) : null;
if (selectedRule)
this.expandChildren(selectedRule, false);
for (var i = 0; i < this.modifiers.length; i++) {
var modName = this.modifiers[i];
var modParams = [];
if (modName.indexOf("(") > 0) {
var regExp = /\(([^)]+)\)/;
var results = regExp.exec(this.modifiers[i]);
if (!results || results.length < 2) {
}
else {
var modParams = results[1].split(",");
modName = this.modifiers[i].substring(0, modName.indexOf("("));
}
}
var mod = this.grammar.modifiers[modName];
if (!mod) {
this.errors.push("Missing modifier " + modName);
this.finishedText += "((." + modName + "))";
}
else {
this.finishedText = mod(this.finishedText, modParams);
}
}
for (var i = 0; i < this.postactions.length; i++) {
this.postactions[i].activate();
}
break;
case 2:
this.action = new NodeAction(this, this.raw);
this.action.activate();
this.finishedText = "";
break;
case 3:
this.varop = new NodeVariableOp(this, this.raw);
this.varop.activate();
switch (this.varop.type) {
case 0:
this.finishedText = this.scratch && this.scratch[this.varop.target] || "";
break;
default:
case 1:
this.finishedText = "";
break;
case 2:
const condition = this.scratch && this.scratch[this.varop.target] == this.varop.value && this.varop.value !== undefined;
if (this.varop.conditional && condition) {
this.preactions = [];
this.postactions = [];
var reparsed = {
symbol: undefined,
modifiers: [],
preactions: [],
postactions: [],
};
var { sections, errors } = parse(this.varop.conditional);
var symbolSection = undefined;
for (var i = 0; i < sections.length; i++) {
if (sections[i].type === 0) {
if (symbolSection === undefined) {
symbolSection = sections[i].raw;
}
}
else {
if (symbolSection === undefined) {
symbolSection = sections[i].raw;
}
}
}
if (symbolSection != undefined) {
var components = symbolSection.split(".");
reparsed.symbol = components[0];
reparsed.modifiers = components.slice(1);
}
this.symbol = reparsed.symbol;
this.modifiers = reparsed.modifiers;
for (var i = 0; i < reparsed.preactions.length; i++) {
this.preactions[i] = new NodeAction(this, reparsed.preactions[i]);
}
for (var i = 0; i < reparsed.postactions.length; i++) {
}
for (var i = 0; i < this.preactions.length; i++) {
const undoAction = this.preactions[i].createUndo();
if (undoAction)
this.postactions.push(undoAction);
}
for (var i = 0; i < this.preactions.length; i++) {
this.preactions[i].activate();
}
this.finishedText = this.raw;
var selectedRule = this.symbol ? this.grammar.selectRule(this.symbol, this, this.errors) : null;
if (selectedRule) {
if (selectedRule.startsWith("((")) {
this.finishedText = this.symbol || "";
}
else {
this.expandChildren(selectedRule, preventRecursion);
}
}
for (var i = 0; i < this.modifiers.length; i++) {
var modName = this.modifiers[i];
var modParams = [];
if (modName.indexOf("(") > 0) {
var regExp = /\(([^)]+)\)/;
var results = regExp.exec(this.modifiers[i]);
if (!results || results.length < 2) {
}
else {
var modParams = results[1].split(",");
modName = this.modifiers[i].substring(0, modName.indexOf("("));
}
}
var mod = this.grammar.modifiers[modName];
if (!mod) {
this.errors.push("Missing modifier " + modName);
this.finishedText += "((." + modName + "))";
}
else {
this.finishedText = mod(this.finishedText, modParams);
}
}
for (var i = 0; i < this.postactions.length; i++) {
this.postactions[i].activate();
}
}
else {
this.finishedText = "";
}
break;
}
break;
}
}
else {
}
};
this.clearEscapeChars = () => {
this.finishedText = this.finishedText.replace(/\\\\/g, "DOUBLEBACKSLASH").replace(/\\/g, "").replace(/DOUBLEBACKSLASH/g, "\\");
};
this.errors = [];
this.depth = 0;
this.childIndex = 0;
this.scratch = settings.scratch;
this.grammar = grammar;
this.parent = null;
this.children = [];
this.finishedText = "";
if (settings.raw === undefined) {
this.errors.push("Empty input for node");
settings.raw = "";
}
if (parent) {
this.parent = parent;
this.depth = parent.depth + 1;
this.childIndex = childIndex;
this.scratch = parent.scratch;
}
this.raw = settings.raw;
this.type = settings.type;
this.isExpanded = false;
if (!this.grammar) {
console.warn("No grammar specified for this node", this);
}
}
;
}
;
function NodeVariableOp(node, raw) {
this.node = node;
this.scratch = this.node.scratch;
var comparision = raw.split("==");
if (comparision.length == 2) {
this.type = 2;
this.target = comparision[0];
var conditional = comparision[1].split("?");
if (conditional.length == 2) {
this.value = conditional[0];
this.conditional = conditional[1];
}
return;
}
var sections = raw.split("=");
this.target = sections[0];
if (sections.length === 1) {
this.type = 0;
}
else {
this.type = 1;
this.value = sections[1];
}
}
NodeVariableOp.prototype.activate = function () {
switch (this.type) {
case 0:
break;
case 1:
if (this.scratch && this.value)
this.scratch[this.target] = this.value;
break;
}
};
function NodeAction(node, raw) {
this.raw = raw;
this.node = node;
var sections = raw.split(":");
this.target = sections[0];
if (sections.length === 1) {
this.type = 2;
}
else {
this.rule = sections[1];
if (this.rule === "POP") {
this.type = 1;
}
else {
this.type = 0;
}
}
}
NodeAction.prototype.createUndo = function () {
if (this.type === 0) {
return new NodeAction(this.node, this.target + ":POP");
}
return null;
};
NodeAction.prototype.activate = function () {
var grammar = this.node.grammar;
switch (this.type) {
case 0:
this.ruleSections = this.rule?.split(",");
this.finishedRules = [];
if (this.ruleSections) {
for (var i = 0; i < this.ruleSections.length; i++) {
var n = new EphemeralNode(null, grammar, 0, {
type: -1,
raw: this.ruleSections[i],
scratch: this.node.scratch,
});
n.expand(false);
this.finishedRules.push(n.finishedText);
}
}
grammar.pushRules(this.target, this.finishedRules, this);
break;
case 1:
grammar.popRules(this.target);
break;
case 2:
grammar.flatten(this.target, true, this.node.scratch);
break;
case 3:
console.log("Should set variable value?");
break;
}
};
NodeAction.prototype.toText = function () {
switch (this.type) {
case 0:
return this.target + ":" + this.rule;
case 1:
return this.target + ":POP";
case 2:
return "((some function))";
default:
return "((unknown action))";
}
};
function RuleSet(grammar, raw) {
this.raw = raw;
this.grammar = grammar;
this.falloff = 1;
this.defaultRules = [];
this.conditionalRule = undefined;
this.lastIndex = -1;
if (Array.isArray(raw)) {
this.defaultRules = raw;
}
else if (typeof raw === 'string') {
this.defaultRules = [raw];
}
}
;
RuleSet.prototype.selectRule = function (errors) {
if (this.defaultRules !== undefined) {
var index = Math.floor(Math.random() * this.defaultRules.length);
while (this.defaultRules.length > 1 && index == this.lastIndex) {
++index;
index %= this.defaultRules.length;
}
this.lastIndex = index;
return this.defaultRules[index];
}
errors.push("No default rules defined for " + this);
return null;
};
RuleSet.prototype.clearState = function () {
this.lastIndex = -1;
};
class EphemeralSymbol {
constructor(grammar, key, rawRules) {
this.clearState = () => {
this.stack = [this.baseRules];
this.uses = [];
this.baseRules.clearState();
};
this.pushRules = (rawRules) => {
var rules = new RuleSet(this.grammar, rawRules);
this.stack.push(rules);
};
this.popRules = () => {
this.stack.pop();
};
this.selectRule = (node, errors) => {
this.uses.push({
node: node
});
if (this.stack.length === 0) {
errors.push("The rule stack for '" + this.key + "' is empty, too many pops?");
return "((empty stack: " + this.key + "))";
}
return this.stack[this.stack.length - 1].selectRule(errors);
};
this.getActiveRules = () => {
if (this.stack.length === 0) {
return null;
}
return this.stack[this.stack.length - 1].selectRule([]);
};
this.key = key;
this.grammar = grammar;
this.rawRules = rawRules;
this.baseRules = new RuleSet(this.grammar, rawRules);
this.stack = [this.baseRules];
this.uses = [];
this.isDynamic = false;
this.clearState();
}
;
}
;
class Grammar {
constructor(raw) {
this.clearState = () => {
var keys = Object.keys(this.symbols);
for (var i = 0; i < keys.length; i++) {
if (this.symbols[keys[i]])
this.symbols[keys[i]].clearState();
}
};
this.addModifiers = (mods) => {
for (var key in mods) {
if (mods.hasOwnProperty(key)) {
this.modifiers[key] = mods[key];
}
}
;
};
this.withModifiers = (mods) => {
for (var key in mods) {
if (mods.hasOwnProperty(key)) {
this.modifiers[key] = mods[key];
}
}
;
return this;
};
this.createRoot = (rule, scratch) => {
var root = new EphemeralNode(null, this, 0, {
type: -1,
raw: rule,
scratch: scratch,
});
return root;
};
this.expandChildren = (rule, allowEscapeChars, scratch) => {
var root = this.createRoot(rule, scratch);
root.expandChildren(rule, false);
if (!allowEscapeChars)
root.clearEscapeChars();
if (root.errors && root.errors.length > 0)
console.error(root.errors);
var finishedTexts = [];
root.children.forEach((child) => finishedTexts.push(child.finishedText));
return finishedTexts;
};
this.expand = (rule, allowEscapeChars, scratch) => {
var root = this.createRoot(rule, scratch);
root.expand(false);
if (!allowEscapeChars)
root.clearEscapeChars();
if (root.errors && root.errors.length > 0)
console.error(root.errors);
return root;
};
this.flatten = (rule, allowEscapeChars, scratch) => {
var root = this.expand(rule, allowEscapeChars, scratch);
return root.finishedText;
};
this.toJSON = () => {
var keys = Object.keys(this.symbols);
var symbolJSON = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (this.symbols && this.symbols[key])
symbolJSON.push(' "' + key + '" : ' + JSON.stringify(this.symbols[key].rawRules));
}
return "{\n" + symbolJSON.join(",\n") + "\n}";
};
this.pushRules = (key, rawRules, sourceAction) => {
if (this.symbols === undefined)
this.symbols = {};
if (this.symbols[key] === undefined) {
this.symbols[key] = new EphemeralSymbol(this, key, rawRules);
if (sourceAction)
this.symbols[key].isDynamic = true;
}
else {
this.symbols[key].pushRules(rawRules);
}
};
this.popRules = (key) => {
if (!this.symbols[key])
this.errors.push("Can't pop: no symbol for key " + key);
this.symbols[key].popRules();
};
this.selectRule = (key, node, errors) => {
if (this.symbols[key]) {
var rule = this.symbols[key].selectRule(node, errors);
return rule;
}
for (var i = 0; i < this.subgrammars.length; i++) {
if (this.subgrammars[i].symbols[key])
return this.subgrammars[i].symbols[key].selectRule(node, errors);
}
errors.push("No symbol for '" + key + "'");
return "(( no symbol " + key + "))";
};
this.modifiers = {};
this.depth = -1;
this.symbols = {};
this.subgrammars = [];
this.errors = [];
this.raw = raw;
if (this.raw) {
for (var key in this.raw) {
if (this.raw.hasOwnProperty(key)) {
this.symbols[key] = new EphemeralSymbol(this, key, this.raw[key]);
}
}
}
}
;
}
exports.Grammar = Grammar;
;
function createGrammar(raw) {
var jsonData = raw.startsWith("{") ? JSON.parse(raw) : string2json(raw);
return new Grammar(jsonData);
}
exports.createGrammar = createGrammar;
function parseTag(tagContents) {
var parsed = {
symbol: undefined,
preactions: [],
postactions: [],
modifiers: []
};
var { sections, errors } = parse(tagContents);
var symbolSection = undefined;
for (var i = 0; i < sections.length; i++) {
if (sections[i].type === 0) {
if (symbolSection === undefined) {
symbolSection = sections[i].raw;
}
else {
throw ("multiple main sections in " + tagContents);
}
}
else {
parsed.preactions.push(sections[i].raw);
}
}
if (symbolSection != undefined) {
var components = symbolSection.split(".");
parsed.symbol = components[0];
parsed.modifiers = components.slice(1);
}
return parsed;
}
function parse(rule) {
var depth = 0;
var inTag = false;
var inVar = false;
var sections = [];
var escaped = false;
var errors = [];
var start = 0;
var escapedSubstring = "";
var lastEscapedChar = undefined;
if (rule === null) {
return { sections: sections, errors: errors };
}
function createSection(start, end, type) {
if (end - start < 1) {
if (type === 1)
errors.push(start + ": empty tag");
if (type === 2)
errors.push(start + ": empty action");
}
var rawSubstring = "";
if (rule) {
if (lastEscapedChar !== undefined) {
rawSubstring = escapedSubstring + "\\" + rule.substring(lastEscapedChar + 1, end);
}
else {
rawSubstring = rule.substring(start, end);
}
}
sections.push({
type: type,
raw: rawSubstring
});
lastEscapedChar = undefined;
escapedSubstring = "";
}
;
for (var i = 0; i < rule.length; i++) {
if (!escaped) {
var c = rule.charAt(i);
switch (c) {
case '[':
if (depth === 0 && !inTag) {
if (start < i)
createSection(start, i, 0);
start = i + 1;
}
depth++;
break;
case ']':
depth--;
if (depth === 0 && !inTag) {
createSection(start, i, 2);
start = i + 1;
}
break;
case '#':
if (depth === 0) {
if (inTag) {
createSection(start, i, 1);
start = i + 1;
}
else {
if (start < i)
createSection(start, i, 0);
start = i + 1;
}
inTag = !inTag;
}
break;
case '@':
if (depth === 0 || inVar) {
if (inVar) {
createSection(start, i, 3);
start = i + 1;
depth--;
}
else {
if (start < i)
createSection(start, i, 0);
start = i + 1;
depth++;
}
inVar = !inVar;
}
break;
case '\\':
escaped = true;
escapedSubstring = escapedSubstring + rule.substring(start, i);
start = i + 1;
lastEscapedChar = i;
break;
}
}
else {
escaped = false;
}
}
if (start < rule.length)
createSection(start, rule.length, 0);
if (inTag) {
errors.push("Unclosed tag");
}
if (depth > 0) {
errors.push("Too many [");
}
if (depth < 0) {
errors.push("Too many ]");
}
sections = sections.filter(function (section) {
if (section.type === 0 && section.raw.length === 0)
return false;
return true;
});
return { sections: sections, errors: errors };
}
const string2json = (text) => {
var rules = {};
var symbol = "";
var expansions = new Array();
var asterisk = false;
let lines = text.split('\n');
lines.forEach(line => {
let trimmed = line.trim();
if (trimmed.startsWith("\\\\"))
return;
if (trimmed.length == 0) {
if (asterisk && expansions.length > 0)
expansions[expansions.length - 1] += "\n";
return;
}
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
if (symbol.length > 0) {
if (expansions[expansions.length - 1].endsWith("\n")) {
expansions[expansions.length - 1] = expansions[expansions.length - 1].substring(0, expansions[expansions.length - 1].length - 1);
}
rules[symbol] = expansions;
expansions = new Array();
asterisk = false;
}
symbol = trimmed.substring(1, trimmed.length - 1);
}
else {
if (trimmed.startsWith("*")) {
expansions.push(trimmed.substring(1).trim());
asterisk = true;
}
else {
if (asterisk)
expansions[expansions.length - 1] += "\n" + trimmed;
else
expansions.push(trimmed);
}
}
if (symbol.length > 0) {
rules[symbol] = expansions;
}
});
return rules;
};
var modifiers_js_1 = require("./modifiers.js");
Object.defineProperty(exports, "modifiers", { enumerable: true, get: function () { return modifiers_js_1.modifiers; } });