UNPKG

ephemeral-writer

Version:
716 lines (715 loc) 27.8 kB
"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; } });