Refactoring to allow tags
This commit is contained in:
@@ -3,8 +3,9 @@ package hank;
|
||||
import haxe.ds.Option;
|
||||
|
||||
typedef Choice = {id:Int, onceOnly:Bool, label:Option<String>, condition:Option<String>, depth:Int, output:Output, divertTarget:Option<String>};
|
||||
typedef ChoicePointInfo = {choices:Array<Choice>, fallbackIndex:Int};
|
||||
typedef FallbackChoice = {choice:Choice, index:Int};
|
||||
typedef ChoiceInfo = {choice: Choice, tags: Array<String>};
|
||||
typedef ChoicePointInfo = {choices:Array<ChoiceInfo>, fallbackIndex:Int};
|
||||
typedef FallbackChoiceInfo = {choiceInfo:ChoiceInfo, index:Int};
|
||||
|
||||
class ChoiceExtension {
|
||||
public static function toString(choice:Choice):String {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package hank;
|
||||
|
||||
using StringTools;
|
||||
import haxe.ds.Option;
|
||||
import hank.HankBuffer;
|
||||
|
||||
class Extensions {
|
||||
public static function unwrap<T>(o:Option<T>):T {
|
||||
@@ -16,4 +18,27 @@ class Extensions {
|
||||
public static function toIterable<T>(i:Void->Iterator<T>): Iterable<T> {
|
||||
return { iterator: i };
|
||||
}
|
||||
|
||||
public static function tokenize(s: String): Array<String> {
|
||||
var tokenStartIndices = [ ];
|
||||
var whitespaceIndices = [ ];
|
||||
var tokens = [ ];
|
||||
var lastWasChar = false;
|
||||
s = s.trim();
|
||||
|
||||
for (i in 0...s.length) {
|
||||
if (s.isSpace(i)) {
|
||||
lastWasChar = false;
|
||||
if (lastWasChar) whitespaceIndices.push(i);
|
||||
}
|
||||
else {
|
||||
if (!lastWasChar) tokenStartIndices.push(i);
|
||||
lastWasChar = true;
|
||||
}
|
||||
}
|
||||
|
||||
whitespaceIndices.push(null); // This ensures the last token will substr() correctly
|
||||
|
||||
return [for (i in 0...tokenStartIndices.length) s.substr(tokenStartIndices[i], whitespaceIndices[i])];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package hank;
|
||||
|
||||
import haxe.ds.Option;
|
||||
import hank.Alt.AltInstance;
|
||||
import hank.Choice.ChoiceInfo;
|
||||
import hank.Choice.ChoicePointInfo;
|
||||
|
||||
enum ExprType {
|
||||
@@ -16,8 +17,10 @@ enum ExprType {
|
||||
EHaxeLine(haxe:String);
|
||||
EHaxeBlock(haxe:String);
|
||||
EGather(label:Option<String>, depth:Int, expr:ExprType);
|
||||
// Choices are the most complicated expressions
|
||||
// Hank pre-tag-implementation: Choices are the most complicated expressions
|
||||
EChoice(c:Choice);
|
||||
// Tags: Hold my beer
|
||||
ETagged(e: ExprType, tags:Array<String>);
|
||||
}
|
||||
|
||||
typedef HankExpr = {
|
||||
@@ -53,12 +56,19 @@ class ASTExtension {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static function tryAddFunc(choices: Array<ChoiceInfo>, expectedDepth: Int, c: Choice, tags: Array<String>) {
|
||||
var valid = (c.depth == expectedDepth);
|
||||
if (valid) choices.push({choice:c,tags:tags});
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
Collect every choice in the choice point starting at the given index.
|
||||
**/
|
||||
public static function collectChoices(ast:HankAST, startingIndex:Int, depth:Int):ChoicePointInfo {
|
||||
var choices = [];
|
||||
var choices = new Array<ChoiceInfo>();
|
||||
var lastChoiceIndex = 0;
|
||||
var tryAdd = tryAddFunc.bind(choices, depth);
|
||||
if (startingIndex > ast.length || startingIndex < 0) {
|
||||
throw 'Trying to collect choices starting from expr ${startingIndex + 1}/${ast.length}';
|
||||
}
|
||||
@@ -68,12 +78,9 @@ class ASTExtension {
|
||||
switch (ast[i].expr) {
|
||||
// Gather choices of the current depth
|
||||
case EChoice(choice):
|
||||
if (choice.depth != depth)
|
||||
continue;
|
||||
else {
|
||||
lastChoiceIndex = i;
|
||||
choices.push(choice);
|
||||
};
|
||||
if (tryAdd(choice, [])) lastChoiceIndex = i;
|
||||
case ETagged(EChoice(choice), tags):
|
||||
if (tryAdd(choice, tags)) lastChoiceIndex = 1;
|
||||
// Stop at the next gather of this depth
|
||||
case EGather(_, d, _) if (d == depth):
|
||||
break;
|
||||
@@ -103,8 +110,9 @@ class ASTExtension {
|
||||
for (i in 0...ast.length) {
|
||||
var expr = ast[i].expr;
|
||||
switch (expr) {
|
||||
case EChoice(c) if (c.id == id):
|
||||
return i;
|
||||
case EChoice(c) | ETagged(EChoice(c), _):
|
||||
if (c.id == id)
|
||||
return i;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ class Parser {
|
||||
['```' => haxeBlock],
|
||||
['-' => gather],
|
||||
['*' => choice],
|
||||
['+' => choice]];
|
||||
['+' => choice],
|
||||
['#' => tag]];
|
||||
|
||||
var buffers:Array<HankBuffer> = [];
|
||||
var ast:HankAST = [];
|
||||
@@ -223,6 +224,13 @@ class Parser {
|
||||
});
|
||||
}
|
||||
|
||||
static function tag(buffer:HankBuffer, position: HankBuffer.Position):ExprType {
|
||||
buffer.drop('#');
|
||||
var tagLine = buffer.takeLine('lr').unwrap();
|
||||
var tags = tagLine.tokenize();
|
||||
return ETagged(parseExpr(buffer, position), tags);
|
||||
}
|
||||
|
||||
static function haxeBlock(buffer:HankBuffer, position:HankBuffer.Position):ExprType {
|
||||
buffer.drop('```\n');
|
||||
var rawContents = buffer.takeUntil(['```'], false, true).unwrap().output;
|
||||
|
||||
@@ -12,10 +12,10 @@ using hank.Extensions;
|
||||
using HankAST.ASTExtension;
|
||||
|
||||
import hank.Choice;
|
||||
|
||||
import hank.Choice.ChoiceInfo;
|
||||
import hank.Choice.FallbackChoiceInfo;
|
||||
using Choice.ChoiceExtension;
|
||||
|
||||
import hank.Choice.FallbackChoice;
|
||||
import hank.HankAST.ExprType;
|
||||
import hank.StoryTree;
|
||||
import hank.Alt.AltInstance;
|
||||
@@ -307,37 +307,37 @@ class Story {
|
||||
|
||||
private function nextChoiceFrame() {
|
||||
var optionsText = [
|
||||
for (c in availableChoices())
|
||||
c.output.format(this, hInterface, random, altInstances, nodeScopes, false)
|
||||
for (choiceInfo in availableChoices())
|
||||
choiceInfo.choice.output.format(this, hInterface, random, altInstances, nodeScopes, false)
|
||||
];
|
||||
if (optionsText.length > 0) {
|
||||
return finalChoiceProcessing(optionsText);
|
||||
} else {
|
||||
var fallback = fallbackChoice();
|
||||
switch (fallback.choice.divertTarget) {
|
||||
switch (fallback.choiceInfo.choice.divertTarget) {
|
||||
case Some(t) if (t.length > 0):
|
||||
var fallbackText = evaluateChoice(fallback.choice);
|
||||
var fallbackText = evaluateChoice(fallback.choiceInfo.choice);
|
||||
if (fallbackText.length > 0) {
|
||||
throw 'For some reason a fallback choice evaluated to text!';
|
||||
}
|
||||
return nextFrame();
|
||||
default:
|
||||
exprIndex = fallback.index + 1;
|
||||
weaveDepth = fallback.choice.depth + 1;
|
||||
weaveDepth = fallback.choiceInfo.choice.depth + 1;
|
||||
return nextFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function traceChoiceArray(choices:Array<Choice>) {
|
||||
for (choice in choices) {
|
||||
trace(choice.toString());
|
||||
private function traceChoiceArray(choices:Array<ChoiceInfo>) {
|
||||
for (choiceInfo in choices) {
|
||||
trace('${choiceInfo.choice.toString()}: #${choiceInfo.tags.join(" #")}');
|
||||
}
|
||||
trace('---');
|
||||
}
|
||||
|
||||
private function availableChoices():Array<Choice> {
|
||||
var choices = [];
|
||||
private function availableChoices():Array<ChoiceInfo> {
|
||||
var choices = new Array<ChoiceInfo>();
|
||||
|
||||
// If we're threading, collect all the childrens' choices, too.
|
||||
if (embedMode == Thread) {
|
||||
@@ -351,18 +351,18 @@ class Story {
|
||||
}
|
||||
|
||||
if (exprIndex < ast.length && ast[exprIndex].expr.match(EChoice(_))) {
|
||||
var allChoices = ast.collectChoices(exprIndex, weaveDepth).choices;
|
||||
for (choice in allChoices) {
|
||||
if (choicesTaken.indexOf(choice.id) == -1 || !choice.onceOnly) {
|
||||
switch (choice.condition) {
|
||||
var allChoiceInfo = ast.collectChoices(exprIndex, weaveDepth).choices;
|
||||
for (choiceInfo in allChoiceInfo) {
|
||||
if (choicesTaken.indexOf(choiceInfo.choice.id) == -1 || !choiceInfo.choice.onceOnly) {
|
||||
switch (choiceInfo.choice.condition) {
|
||||
case Some(expr):
|
||||
if (!hInterface.cond(expr, nodeScopes)) {
|
||||
continue;
|
||||
}
|
||||
case None:
|
||||
}
|
||||
if (!choice.output.isEmpty()) {
|
||||
choices.push(choice);
|
||||
if (!choiceInfo.choice.output.isEmpty()) {
|
||||
choices.push(choiceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,11 +375,11 @@ class Story {
|
||||
return choices;
|
||||
}
|
||||
|
||||
private function fallbackChoice():FallbackChoice {
|
||||
private function fallbackChoice():FallbackChoiceInfo {
|
||||
var choiceInfo = ast.collectChoices(exprIndex, weaveDepth);
|
||||
var lastChoice = choiceInfo.choices[choiceInfo.choices.length - 1];
|
||||
if (lastChoice.output.isEmpty()) {
|
||||
return {choice: lastChoice, index: choiceInfo.fallbackIndex};
|
||||
if (lastChoice.choice.output.isEmpty()) {
|
||||
return {choiceInfo: lastChoice, index: choiceInfo.fallbackIndex};
|
||||
} else {
|
||||
throw 'there is no fallback choice!';
|
||||
}
|
||||
@@ -526,7 +526,7 @@ class Story {
|
||||
return embeddedBlocks[0].choose(choiceIndex);
|
||||
} else {
|
||||
// if not embedded, actually make the choice. avalaibleChoices() accounts for aggregating threaded choices
|
||||
var output = evaluateChoice(availableChoices()[choiceIndex]);
|
||||
var output = evaluateChoice(availableChoices()[choiceIndex].choice);
|
||||
if (embedMode == Thread) {
|
||||
embedMode = Tunnel;
|
||||
embeddedBlocks = [];
|
||||
|
||||
Reference in New Issue
Block a user