Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1602 lines (1252 sloc) 37.2 KB
:: twinecery version [annotation]
v.2016-09-05
:: traceryInit
<<if !tale.grammar>>
<<if tracery>>
<<set tale.grammar = tracery.createGrammar(tale.story.data)>>
<<print console.log("grammar: ", tale.grammar)>>
<<else>>
<<print console.log("grammar instantiation failed")>>
<<endif>>
<<endif>>
:: tracery.js (no require) [script]
window.tracery = {
utilities : {}
};
(function () {/**
* @author Kate Compton
*/
function inQuotes(s) {
return '"' + s + '"';
};
function parseAction(action) {
return action;
};
// tag format
// a thing to expand, plus actions
function parseTag(tag) {
var errors = [];
var prefxns = [];
var postfxns = [];
var lvl = 0;
var start = 0;
var inPre = true;
var symbol,
mods;
function nonAction(end) {
if (start !== end) {
var section = tag.substring(start, end);
if (!inPre) {
errors.push("multiple possible expansion symbols in tag!" + tag);
} else {
inPre = false;
var split = section.split(".");
symbol = split[0];
mods = split.slice(1, split.length);
}
}
start = end;
};
for (var i = 0; i < tag.length; i++) {
var c = tag.charAt(i);
switch(c) {
case '[':
if (lvl === 0) {
nonAction(i);
}
lvl++;
break;
case ']':
lvl--;
if (lvl === 0) {
var section = tag.substring(start + 1, i);
if (inPre)
prefxns.push(parseAction(section));
else
postfxns.push(parseAction(section));
start = i + 1;
}
break;
default:
if (lvl === 0) {
}
break;
}
}
nonAction(i);
if (lvl > 0) {
var error = "Too many '[' in rule " + inQuotes(tag);
errors.push(error);
}
if (lvl < 0) {
var error = "Too many ']' in rule " + inQuotes(tag);
errors.push(error);
}
return {
preActions : prefxns,
postActions : postfxns,
symbol : symbol,
mods : mods,
raw : tag,
errors : errors,
};
};
// Split a rule into sections
function parseRule(rule) {
var sections = [];
var errors = [];
if (!( typeof rule == 'string' || rule instanceof String)) {
errors.push("Cannot parse non-string rule " + rule);
sections.errors = errors;
return sections;
}
if (rule.length === 0) {
return [];
}
var lvl = 0;
var start = 0;
var inTag = false;
function createSection(end) {
var section = rule.substring(start, end);
if (section.length > 0) {
if (inTag)
sections.push(parseTag(section));
else
sections.push(section);
}
inTag = !inTag;
start = end + 1;
}
for (var i = 0; i < rule.length; i++) {
var c = rule.charAt(i);
switch(c) {
case '[':
lvl++;
break;
case ']':
lvl--;
break;
case '#':
if (lvl === 0) {
createSection(i);
}
break;
default:
break;
}
}
if (lvl > 0) {
var error = "Too many '[' in rule " + inQuotes(rule);
errors.push(error);
}
if (lvl < 0) {
var error = "Too many ']' in rule " + inQuotes(rule);
errors.push(error);
}
if (inTag) {
var error = "Odd number of '#' in rule " + inQuotes(rule);
errors.push(error);
}
createSection(rule.length);
sections.errors = errors;
return sections;
};
function testParse(rule, shouldFail) {
console.log("-------");
console.log("Test parse rule: " + inQuotes(rule) + " " + shouldFail);
var parsed = parseRule(rule);
if (parsed.errors && parsed.errors.length > 0) {
for (var i = 0; i < parsed.errors.length; i++) {
console.log(parsed.errors[i]);
}
}
}
function testParseTag(tag, shouldFail) {
console.log("-------");
console.log("Test parse tag: " + inQuotes(tag) + " " + shouldFail);
var parsed = parseTag(tag);
if (parsed.errors && parsed.errors.length > 0) {
for (var i = 0; i < parsed.errors.length; i++) {
console.log(parsed.errors[i]);
}
}
}
tracery.testParse = testParse;
tracery.testParseTag = testParseTag;
tracery.parseRule = parseRule;
tracery.parseTag = parseTag;
function spacer(size) {
var s = "";
for (var i = 0; i < size * 3; i++) {
s += " ";
}
return s;
}
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
function extend(destination, source) {
for (var k in source) {
if (source.hasOwnProperty(k)) {
destination[k] = source[k];
}
}
return destination;
}
// Inspired by base2 and Prototype
(function() {
var initializing = false,
fnTest = /xyz/.test(function() { xyz;
}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function() {
};
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn) {
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) : prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if (!initializing && this.init)
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
/**
* @author Kate
*/
var Rule = function(raw) {
this.raw = raw;
this.sections = parseRule(raw);
};
Rule.prototype.getParsed = function() {
if (!this.sections)
this.sections = parseRule(raw);
return this.sections;
};
Rule.prototype.toString = function() {
return this.raw;
};
Rule.prototype.toJSONString = function() {
return this.raw;
};
/**
* @author Kate
*/
var RuleWeighting = Object.freeze({
RED : 0,
GREEN : 1,
BLUE : 2
});
var RuleSet = function(rules) {
// is the rules obj an array? A RuleSet, or a string?
if (rules.constructor === Array) {
// make a copy
rules = rules.slice(0, rules.length);
} else if (rules.prototype === RuleSet) {
// clone
} else if ( typeof rules == 'string' || rules instanceof String) {
var args = Array.prototype.slice.call(arguments);
rules = args;
} else {
console.log(rules);
throw ("creating ruleset with unknown object type!");
}
// create rules and their use counts
this.rules = rules;
this.parseAll();
this.uses = [];
this.startUses = [];
this.totalUses = 0;
for (var i = 0; i < this.rules.length; i++) {
this.uses[i] = 0;
this.startUses[i] = this.uses[i];
this.totalUses += this.uses[i];
}
};
//========================================================
// Iterating over rules
RuleSet.prototype.parseAll = function(fxn) {
for (var i = 0; i < this.rules.length; i++) {
if (this.rules[i].prototype !== Rule)
this.rules[i] = new Rule(this.rules[i]);
}
};
//========================================================
// Iterating over rules
RuleSet.prototype.mapRules = function(fxn) {
return this.rules.map(function(rule, index) {
return fxn(rule, index);
});
};
RuleSet.prototype.applyToRules = function(fxn) {
for (var i = 0; i < this.rules.length; i++) {
fxn(this.rules[i], i);
}
};
//========================================================
RuleSet.prototype.get = function() {
var index = this.getIndex();
return this.rules[index];
};
RuleSet.prototype.getRandomIndex = function() {
return Math.floor(this.uses.length * Math.random());
};
RuleSet.prototype.getIndex = function() {
// Weighted distribution
// Imagine a bar of length 1, how to divide the length
// s.t. a random dist will result in the dist we want?
var index = this.getRandomIndex();
// What if the uses determine the chance of rerolling?
var median = this.totalUses / this.uses.length;
var count = 0;
while (this.uses[index] > median && count < 20) {
index = this.getRandomIndex();
count++;
}
// reroll more likely if index is too much higher
return index;
};
RuleSet.prototype.decayUses = function(pct) {
this.totalUses = 0;
for (var i = 0; i < this.uses; i++) {
this.uses[index] *= 1 - pct;
this.totalUses += this.uses[index];
}
};
RuleSet.prototype.testRandom = function() {
console.log("Test random");
var counts = [];
for (var i = 0; i < this.uses.length; i++) {
counts[i] = 0;
}
var testCount = 10 * this.uses.length;
for (var i = 0; i < testCount; i++) {
var index = this.getIndex();
this.uses[index] += 1;
counts[index]++;
this.decayUses(.1);
}
for (var i = 0; i < this.uses.length; i++) {
console.log(i + ":\t" + counts[i] + " \t" + this.uses[i]);
}
};
RuleSet.prototype.getSaveRules = function() {
var jsonRules = this.rules.map(function(rule) {
return rule.toJSONString();
});
return jsonRules;
};
/**
* @author Kate Compton
*/
var Action = function(node, raw) {
this.node = node;
this.grammar = node.grammar;
this.raw = raw;
};
Action.prototype.activate = function() {
var node = this.node;
node.actions.push(this);
// replace any hashtags
this.amended = this.grammar.flatten(this.raw);
var parsed = parseTag(this.amended);
var subActionRaw = parsed.preActions;
if (subActionRaw && subActionRaw.length > 0) {
this.subactions = subActionRaw.map(function(action) {
return new Action(node, action);
});
}
if (parsed.symbol) {
var split = parsed.symbol.split(":");
if (split.length === 2) {
this.push = {
symbol : split[0],
// split into multiple rules
rules : split[1].split(","),
};
// push
node.grammar.pushRules(this.push.symbol, this.push.rules);
} else
throw ("Unknown action: " + parsed.symbol);
}
if (this.subactions) {
for (var i = 0; i < this.subactions.length; i++) {
this.subactions[i].activate();
}
}
};
Action.prototype.deactivate = function() {
if (this.subactions) {
for (var i = 0; i < this.subactions.length; i++) {
this.subactions[i].deactivate();
}
}
if (this.push) {
this.node.grammar.popRules(this.push.symbol, this.push.rules);
}
};
/**
* @author Kate Compton
*/
var isConsonant = function(c) {
c = c.toLowerCase();
switch(c) {
case 'a':
return false;
case 'e':
return false;
case 'i':
return false;
case 'o':
return false;
case 'u':
return false;
}
return true;
};
function endsWithConY(s) {
if (s.charAt(s.length - 1) === 'y') {
return isConsonant(s.charAt(s.length - 2));
}
return false;
};
var universalModifiers = {
capitalizeAll : function(s) {
return s.replace(/(?:^|\s)\S/g, function(a) {
return a.toUpperCase();
});
},
capitalize : function(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
},
inQuotes : function(s) {
return '"' + s + '"';
},
comma : function(s) {
var last = s.charAt(s.length - 1);
if (last === ",")
return s;
if (last === ".")
return s;
if (last === "?")
return s;
if (last === "!")
return s;
return s + ",";
},
beeSpeak : function(s) {
// s = s.replace("s", "zzz");
s = s.replace(/s/, 'zzz');
return s;
},
a : function(s) {
if (!isConsonant(s.charAt()))
return "an " + s;
return "a " + s;
},
s : function(s) {
var last = s.charAt(s.length - 1);
switch(last) {
case 'y':
// rays, convoys
if (!isConsonant(s.charAt(s.length - 2))) {
return s + "s";
}
// harpies, cries
else {
return s.slice(0, s.length - 1) + "ies";
}
break;
// oxen, boxen, foxen
case 'x':
return s.slice(0, s.length - 1) + "en";
case 'z':
return s.slice(0, s.length - 1) + "es";
case 'h':
return s.slice(0, s.length - 1) + "es";
default:
return s + "s";
};
},
ed : function(s) {
var index = s.indexOf(" ");
var s = s;
var rest = "";
if (index > 0) {
rest = s.substring(index, s.length);
s = s.substring(0, index);
}
var last = s.charAt(s.length - 1);
switch(last) {
case 'y':
// rays, convoys
if (isConsonant(s.charAt(s.length - 2))) {
return s.slice(0, s.length - 1) + "ied" + rest;
}
// harpies, cries
else {
return s + "ed" + rest;
}
break;
case 'e':
return s + "d" + rest;
break;
default:
return s + "ed" + rest;
};
}
};
/**
* @author Kate Compton
*/
// A tracery expansion node
var nodeCount = 0;
var ExpansionNode = Class.extend({
init : function() {
this.depth = 0;
this.id = nodeCount;
nodeCount++;
this.childText = "[[UNEXPANDED]]";
},
setParent : function(parent) {
if (parent) {
this.depth = parent.depth + 1;
this.parent = parent;
this.grammar = parent.grammar;
}
},
expand : function() {
// do nothing
return "???";
},
expandChildren : function() {
if (this.children) {
this.childText = "";
for (var i = 0; i < this.children.length; i++) {
this.children[i].expand();
this.childText += this.children[i].finalText;
}
this.finalText = this.childText;
}
},
createChildrenFromSections : function(sections) {
var root = this;
this.children = sections.map(function(section) {
if ( typeof section == 'string' || section instanceof String) {
// Plaintext
return new TextNode(root, section);
} else {
return new TagNode(root, section);
}
});
}
});
var RootNode = ExpansionNode.extend({
init : function(grammar, rawRule) {
this._super();
this.grammar = grammar;
this.parsedRule = parseRule(rawRule);
},
expand : function() {
var root = this;
this.createChildrenFromSections(this.parsedRule);
// expand the children
this.expandChildren();
},
});
var TagNode = ExpansionNode.extend({
init : function(parent, parsedTag) {
this._super();
if (!(parsedTag !== null && typeof parsedTag === 'object')) {
if ( typeof parsedTag == 'string' || parsedTag instanceof String) {
console.warn("Can't make tagNode from unparsed string!");
parsedTag = parseTag(parsedTag);
} else {
console.log("Unknown tagNode input: ", parsedTag);
throw ("Can't make tagNode from strange tag!");
}
}
this.setParent(parent);
$.extend(this, parsedTag);
},
expand : function() {
if (tracery.outputExpansionTrace)
console.log(r.sections);
this.rule = this.grammar.getRule(this.symbol);
this.actions = [];
// Parse the rule if it hasn't been already
this.createChildrenFromSections(this.rule.getParsed());
// Do any pre-expansion actions!
for (var i = 0; i < this.preActions.length; i++) {
var action = new Action(this, this.preActions[i]);
action.activate();
}
// Map each child section to a node
if (!this.rule.sections)
console.log(this.rule);
this.expandChildren();
for (var i = 0; i < this.actions.length; i++) {
this.actions[i].deactivate();
}
this.finalText = this.childText;
for (var i = 0; i < this.mods.length; i++) {
this.finalText = this.grammar.applyMod(this.mods[i], this.finalText);
}
},
toLabel : function() {
return this.symbol;
},
toString : function() {
return "TagNode '" + this.symbol + "' mods:" + this.mods + ", preactions:" + this.preActions + ", postactions" + this.postActions;
}
});
var TextNode = ExpansionNode.extend({
isLeaf : true,
init : function(parent, text) {
this._super();
this.setParent(parent);
this.text = text;
this.finalText = text;
},
expand : function() {
// do nothing
},
toLabel : function() {
return this.text;
}
});
/**
* @author Kate Compton
*/
function Symbol(grammar, key) {
this.grammar = grammar;
this.key = key;
this.currentRules = undefined;
this.ruleSets = [];
};
Symbol.prototype.loadFrom = function(rules) {
rules = this.wrapRules(rules);
this.baseRules = rules;
this.ruleSets.push(rules);
this.currentRules = this.ruleSets[this.ruleSets.length - 1];
};
//========================================================
// Iterating over rules
Symbol.prototype.mapRules = function(fxn) {
return this.currentRules.mapRules(fxn);
};
Symbol.prototype.applyToRules = function(fxn) {
this.currentRules.applyToRules(fxn);
};
//==================================================
// Rule pushpops
Symbol.prototype.wrapRules = function(rules) {
if (rules.prototype !== RuleSet) {
if (Array.isArray(rules)) {
return new RuleSet(rules);
} else if ( typeof rules == 'string' || rules instanceof String) {
return new RuleSet(rules);
} else {
throw ("Unknown rules type: " + rules);
}
}
// already a ruleset
return rules;
};
Symbol.prototype.pushRules = function(rules) {
rules = this.wrapRules(rules);
this.ruleSets.push(rules);
this.currentRules = this.ruleSets[this.ruleSets.length - 1];
};
Symbol.prototype.popRules = function() {
var exRules = this.ruleSets.pop();
if (this.ruleSets.length === 0) {
//console.warn("No more rules for " + this + "!");
}
this.currentRules = this.ruleSets[this.ruleSets.length - 1];
};
// Clear everything and set the rules
Symbol.prototype.setRules = function(rules) {
rules = this.wrapRules(rules);
this.ruleSets = [rules];
this.currentRules = rules;
};
Symbol.prototype.addRule = function(rule) {
this.currentRules.addRule(seed);
};
//========================================================
// selection
Symbol.prototype.select = function() {
this.isSelected = true;
};
Symbol.prototype.deselect = function() {
this.isSelected = false;
};
//==================================================
// Getters
Symbol.prototype.getRule = function(seed) {
return this.currentRules.get(seed);
};
//==================================================
Symbol.prototype.toString = function() {
return this.key + ": " + this.currentRules + "(overlaying " + (this.ruleSets.length - 1) + ")";
};
Symbol.prototype.toJSON = function() {
var rules = this.baseRules.rules.map(function(rule) {
return '"' + rule.raw + '"';
});
return '"' + this.key + '"' + ": [" + rules.join(", ") + "]";
};
Symbol.prototype.toHTML = function(useSpans) {
var keySpan = '"' + this.key + '"';
if (useSpans)
keySpan = "<span class='symbol symbol_" + this.key + "'>" + keySpan + "</span>";
var rules = this.baseRules.rules.map(function(rule) {
var s = '"' + rule.raw + '"';
if (useSpans)
s = "<span class='rule'>" + s + "</span>";
return s;
});
return keySpan + ": [" + rules.join(", ") + "]";
};
/**
* @author Kate Compton
*/
function Grammar() {
this.clear();
};
Grammar.prototype.clear = function() {
// Symbol library
this.symbols = {};
this.errors = [];
// Modifier library
this.modifiers = {};
// add the universal mods
for (var mod in universalModifiers) {
if (universalModifiers.hasOwnProperty(mod))
this.modifiers[mod] = universalModifiers[mod];
}
};
//========================================================
// Loading
Grammar.prototype.loadFrom = function(obj) {
var symbolSrc;
this.clear();
if (obj.symbols !== undefined) {
symbolSrc = obj.symbols;
} else {
symbolSrc = obj;
}
// get all json keys
var keys = Object.keys(symbolSrc);
this.symbolNames = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
this.symbolNames.push(key);
this.symbols[key] = new Symbol(this, key);
this.symbols[key].loadFrom(symbolSrc[key]);
}
};
Grammar.prototype.toHTML = function(useSpans) {
// get all json keys
var keys = Object.keys(this.symbols);
this.symbolNames = [];
var lines = [];
var count = 0;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var symbol = this.symbols[key];
if (symbol && symbol.baseRules) {
lines.push(" " + this.symbols[key].toHTML(useSpans));
}
};
var s;
s = lines.join(",</p><p>");
s = "{<p>" + s + "</p>}";
return s;
};
Grammar.prototype.toJSON = function() {
// get all json keys
var keys = Object.keys(this.symbols);
this.symbolNames = [];
var lines = [];
var count = 0;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var symbol = this.symbols[key];
if (symbol && symbol.baseRules) {
lines.push(" " + this.symbols[key].toJSON());
}
};
var s;
s = lines.join(",\n");
s = "{\n" + s + "\n}";
return s;
};
//========================================================
// selection
Grammar.prototype.select = function() {
this.isSelected = true;
};
Grammar.prototype.deselect = function() {
this.isSelected = false;
};
//========================================================
// Iterating over symbols
Grammar.prototype.mapSymbols = function(fxn) {
var symbols = this.symbols;
return this.symbolNames.map(function(name) {
return fxn(symbols[name], name);
});
};
Grammar.prototype.applyToSymbols = function(fxn) {
for (var i = 0; i < this.symbolNames.length; i++) {
var key = this.symbolNames[i];
fxn(this.symbols[key], key);
}
};
//========================================================
Grammar.prototype.addOrGetSymbol = function(key) {
if (this.symbols[key] === undefined)
this.symbols[key] = new Symbol(key);
return this.symbols[key];
};
Grammar.prototype.pushRules = function(key, rules) {
var symbol = this.addOrGetSymbol(key);
symbol.pushRules(rules);
};
Grammar.prototype.popRules = function(key, rules) {
var symbol = this.addOrGetSymbol(key);
var popped = symbol.popRules();
if (symbol.ruleSets.length === 0) {
// remove symbol
this.symbols[key] = undefined;
}
};
Grammar.prototype.applyMod = function(modName, text) {
if (!this.modifiers[modName]) {
console.log(this.modifiers);
throw ("Unknown mod: " + modName);
}
return this.modifiers[modName](text);
};
//============================================================
Grammar.prototype.getRule = function(key, seed) {
var symbol = this.symbols[key];
if (symbol === undefined) {
var r = new Rule("{{" + key + "}}");
r.error = "Missing symbol " + key;
return r;
}
var rule = symbol.getRule();
if (rule === undefined) {
var r = new Rule("[" + key + "]");
console.log(r.sections);
r.error = "Symbol " + key + " has no rule";
return r;
}
return rule;
};
//============================================================
// Expansions
Grammar.prototype.expand = function(raw) {
// Start a new tree
var root = new RootNode(this, raw);
root.expand();
return root;
};
Grammar.prototype.flatten = function(raw) {
// Start a new tree
var root = new RootNode(this, raw);
root.expand();
return root.childText;
};
//===============
Grammar.prototype.analyze = function() {
this.symbolNames = [];
for (var name in this.symbols) {
if (this.symbols.hasOwnProperty(name)) {
this.symbolNames.push(name);
}
}
// parse every rule
for (var i = 0; i < this.symbolNames.length; i++) {
var key = this.symbolNames[i];
var symbol = this.symbols[key];
// parse all
for (var j = 0; j < symbol.baseRules.length; j++) {
var rule = symbol.baseRules[j];
rule.parsed = tracery.parse(rule.raw);
// console.log(rule);
}
}
};
Grammar.prototype.selectSymbol = function(key) {
console.log(this);
var symbol = this.get(key);
};
/**
* @author Kate Compton
*/
tracery.createGrammar = function(obj) {
var grammar = new Grammar();
grammar.loadFrom(obj);
return grammar;
};
tracery.test = function() {
console.log("==========================================");
console.log("test tracery");
// good
tracery.testParse("", false);
tracery.testParse("fooo", false);
tracery.testParse("####", false);
tracery.testParse("#[]#[]##", false);
tracery.testParse("#someSymbol# and #someOtherSymbol#", false);
tracery.testParse("#someOtherSymbol.cap.pluralize#", false);
tracery.testParse("#[#do some things#]symbol.mod[someotherthings[and a function]]#", false);
tracery.testParse("#[fxn][fxn][fxn[subfxn]]symbol[[fxn]]#", false);
tracery.testParse("#[fxn][#fxn#][fxn[#subfxn#]]symbol[[fxn]]#", false);
tracery.testParse("#hero# ate some #color# #animal.s#", false);
tracery.testParseTag("[action]symbol.mod1.mod2[postAction]", false);
// bad
tracery.testParse("#someSymbol# and #someOtherSymbol", true);
tracery.testParse("#[fxn][fxn][fxn[subfxn]]symbol[fxn]]#", true);
// bad
tracery.testParseTag("stuff[action]symbol.mod1.mod2[postAction]", true);
tracery.testParseTag("[action]symbol.mod1.mod2[postAction]stuff", true);
tracery.testParse("#hero# ate some #color# #animal.s#", true);
tracery.testParse("#[#setPronouns#][#setOccupation#][hero:#name#]story#", true);
};
})();
:: Story [script]
//requires jquery
// input: an array of objects
// a property that each of those object have
// output: an array of the properties of all the objects
window.skimObjectArray = function(objectArray, property){
console.log("skimObjectArray(", "objectArray", objectArray, "property", property, ")")
var values = [];
for (var i = 0; i < objectArray.length; i++) {
var thingToAdd = objectArray[i][property];
if(typeof thingToAdd === "object"){
// i hope to god this works
thingToAdd = objectArray[i][property].join("\n")
}
values.push( thingToAdd );
};
values = values.join("\n");
values = values.split("\n")
return values;
}
Story.prototype.appendCorpora = function(){
var corporaToAppend = tale.lookup("tags", "corpus");
if(!corporaToAppend.length) return;
for(var i in corporaToAppend){
var currentPassage = corporaToAppend[i].title;
// the rules are the concatenation of each symbol in this passage
var rules = [];
var lines = tale.passages[currentPassage].text.split("\n")
for(var j in lines){
var line = lines[j];
var location = line.split("#");
console.log("location: ", location);
var corpusLocation = location[0];
// fetch me that sweet sweet boy
var corpus = $.ajax({
dataType: "json",
url: corpusLocation,
async: false
});
corpus = corpus.responseJSON;
// drill down to the array we want
for (var i = 1; i < location.length; i++) {
console.log("corpus: ", corpus);
// if there's a ! at the beginning of a location, skim the objArray for that property
if(location[i][0] === "!"){
corpus = skimObjectArray(corpus, location[i].substring(1));
continue;
}
corpus = corpus[ location[i] ]
};
console.log("corpus: ", corpus);
// add this into the symbol-in-progress
rules = rules.concat(corpus);
console.log("rules: ", rules)
}
// the name of this symbol is the name of the passage
var finalJSON = "{ \"" + currentPassage + "\": " + JSON.stringify(rules) + " }";
// save our dark deeds to the passage
tale.passages[currentPassage].text = finalJSON;
//tag this as JSON so it gets appended in the next step
tale.passages[currentPassage].tags.push("JSON")
}
console.log("corpora loaded")
}
Story.prototype.appendJSON = function() {
var JSONtoAppend = tale.lookup("tags", "JSON");
if(!JSONtoAppend.length) return;
for(i in JSONtoAppend){
var newJSON = JSON.parse(JSONtoAppend[i].text);
$.extend(this.data, newJSON);
}
console.log("JSON appended");
}
function Story(){
var grammars = tale.lookup("tags", "grammar", "title");
this.data = {};
var links = /(\[\[\b)(.+?)(\b\]\])/g;
var sublinks = /([^\[\]]+)*(.+)/
function convertSyntax(match, p1, p2, p3){
// If a passage is invoked that's tagged as a grammar, change Twine links into Tracery symbols.
// e.g.: [[animal]] => #animal#
// e.g.: [[animal][capitalize]] => #animal.capitalize#
// p1 is left brackets, p3 is right brackets
var targetLink = p2.split("][")[0];
var modifiers = p2.split("][").slice(1, p2.length).join(".");
modifiers = modifiers?("." + modifiers):"";
var trace = "#" + targetLink + modifiers + "#";
var linkIsGrammar = false;
var tags = tale.get(targetLink).tags
for(var i = 0; i < tags.length; i++){
if(tags[i] == "grammar" || tags[i] == "corpus"){
linkIsGrammar = true;
break;
}
}
return linkIsGrammar?trace:match;
}
for(var i in grammars){
if(grammars[i] == undefined) continue;
// Passage names become grammar names, Passage text becomes grammar text.
var newSymbol = grammars[i].title
var newText = grammars[i].text
var link = /(\[\[\b)(.+?)(\b\]\])/g;
newText = newText.replace(link, convertSyntax);
// Get everything that's being linked to.
this.data[newSymbol] = newText.split('\n');
}
this.appendCorpora();
this.appendJSON();
console.log("Story: ", this);
}
Story.prototype.constructor = Story;
// Append this to the tale object because I don't know where else to put it.
Tale.prototype.story = new Story();
Story.prototype.toHTML = function() {
var output = [];
var tab = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
var beg = '\n' + tab + "\"<span class=\"grammarContents\">{{{"
var end = "}}}</span>\""
for(var i in this.data){
var gram = "<span class=\"grammarTitle\">\"" + i + "\"</span>: [";
gram += beg + this.data[i].join(end + ',' + beg) + end;
gram += "]";
output.push(gram);
}
return "{\n" + output.join(",\n") + "\n}";
}
Tale.prototype.JSONtoTwee = function() {
var JSONtoConvert = tale.lookup("tags", "JSON");
var combinedJSON = ""
for (var i in JSONtoConvert){
combinedJSON += JSONtoConvert[i].text;
}
// Note the {{{}}} delimiters in textPost. This is intended for display in Twine, so
// if you're just running these raw they aren't necessary.
var regex = {titlesPre: /\t"(.+)": \[/g, titlesPost: "<br>:: $1 [grammar]",
textPre: /\t*"(.+)",*(?:\n\t)?(?:\],)*\n/g, textPost: "{{{$1}}}<br>"}
var tweeOutput = combinedJSON.replace(regex.titlesPre, regex.titlesPost);
tweeOutput = tweeOutput.replace(regex.textPre, regex.textPost);
tweeOutput = tweeOutput.replace(/({\n)|(]\n})/g, "")
return tweeOutput;
}
:: window.grammar [script]
window.grammar = function(rule){
return tale.get(rule).text.split('\n');
}
:: trace
<<silently>>
Uses the parameter if one was passed. Then, checks for the $symbol variable. If neither is present, uses "origin". Clears $symbol at the end.
<<if parameter(0)>>
<<set $symbol to parameter(0)>>
<<else>><<if $symbol>>
No need to do anything.
<<else>>
<<set $symbol to "origin">>
<<endif>><<endif>>
<<endsilently>><<print console.log("trace " + $symbol)>><<print tale.grammar.flatten("#" + $symbol + "#")>><<forget $symbol>>
:: window.trace [script]
// Expands a symbol and returns the output.
window.trace = function(symbol){
if(symbol === undefined){
symbol = "origin";
}
if(tale.grammar === undefined){
console.log("Couldn't find the grammar object.");
return "ERROR: Grammar object not found.";
}
var output = tale.grammar.flatten("#" + symbol + "#")
// console.log(symbol + " expands to:\n" + output);
return output;
}
:: window.traceArray [script]
// Returns an array of traces, each different from the one preceding it unless retrace() maxes out attempts.
window.traceArray = function(symbol, num){
var output = [];
output.push( trace(symbol) );
for (var i = 1; i < num; i++) {
output.push( retrace(symbol, output[i-1]) );
};
return output;
}
:: String.contains [script]
String.prototype.contains = function(substring){
if (substring.constructor === Array){
for (var i = 0; i < substring.length; i++){
if(this.contains(substring[i])){
return substring[i]; // Non-empty string evaluates to true
}
}
return false;
} else {
return this.indexOf(substring) > 0;
}
}
:: window.retrace [script]
// Returns a new version of a given expansion.
window.retrace = function(symbol, old, maxAttempts){
if(maxAttempts === undefined){
maxAttempts = 32;
}
if(!(maxAttempts > 1)){
maxAttempts = 1;
}
var output = "", attempts = 0;
do{
output = trace(symbol);
attempts++;
}while(output == old && attempts < maxAttempts)
// console.log("retrace:"
// + "\n\told: " + old
// + "\n\tnew: " + output
// + "\n\tattempts: " + attempts)
return output;
}
:: tracelink [script]
// This is a slightly modified version of Leon Arnott's cyclinglink macro.
version.extensions.tracelinkMacro = {
major: 0,
minor: 1,
revision: 0
};
macros.tracelink = {
handler: function(a, b, c) {
var rl = "traceLink";
function toggleText(w) {
w.classList.remove("traceLinkInit");
w.classList.toggle(rl + "Enabled");
w.classList.toggle(rl + "Disabled");
w.style.display = ((w.style.display == "none") ? "inline" : "none")
}
switch (c[c.length - 1]) {
case "end":
var end = true;
c.pop();
break;
case "out":
var out = true;
c.pop();
break
}
var v = "";
if (c.length && c[0][0] == "$") {
v = c[0].slice(1);
c.shift()
}
var h = state.history[0].variables;
if (out && h[v] === "") {
return
}
var l = Wikifier.createInternalLink(a, null);
l.className = "internalLink cyclingLink";
l.setAttribute("data-cycle", 0);
// Prebake a bunch of traces and use those as our links to cycle through.
c = traceArray(c[0], 64);
for (var i = 0; i < c.length; i++) {
var on = (i == Math.max(c.indexOf(h[v]), 0));
var d = insertElement(null, "span", null, "traceLinkInit traceLink" + ((on) ? "En" : "Dis") + "abled");
if (on) {
h[v] = c[i];
l.setAttribute("data-cycle", i)
} else {
d.style.display = "none"
}
insertText(d, c[i]);
if (on && end && i == c.length - 1) {
l.parentNode.replaceChild(d, l)
} else {
l.appendChild(d)
}
}
l.onclick = function() {
var t = this.childNodes;
var u = this.getAttribute("data-cycle") - 0;
var m = t.length;
toggleText(t[u]);
u = (u + 1);
if (!(out && u == m)) {
u %= m;
if (v) {
h[v] = c[u]
}
} else {
h[v] = ""
}
if ((end || out) && u == m - (end ? 1 : 0)) {
if (end) {
var n = this.removeChild(t[u]);
n.className = rl + "End";
n.style.display = "inline";
this.parentNode.replaceChild(n, this)
} else {
this.parentNode.removeChild(this);
return
}
return
}
toggleText(t[u]);
this.setAttribute("data-cycle", u)
}
}
};