Refactoring to allow tags

This commit is contained in:
2019-06-15 20:44:14 -06:00
parent e01b20b1c1
commit 06d7405641
5 changed files with 77 additions and 35 deletions

View File

@@ -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 {

View File

@@ -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])];
}
}

View File

@@ -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:
}
}

View File

@@ -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;

View File

@@ -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 = [];