Glue (at the expense of a few regressions). Closed #37

This commit is contained in:
2019-06-26 18:58:52 -06:00
parent 06d7405641
commit a967c5f3ef
5 changed files with 124 additions and 39 deletions

14
examples/glue/main.hank Normal file
View File

@@ -0,0 +1,14 @@
// This example is from https://github.com/inkle/ink/blob/91bbf0bcfd32e2cefa8561eb83d581444900dcf0/Documentation/WritingWithInk.md#glue
-> hurry_home
=== hurry_home ===
We hurried home <>
-> to_savile_row
=== to_savile_row ===
to Savile Row
-> as_fast_as_we_could
=== as_fast_as_we_could ===
<> as fast as we could.

1
examples/glue/test1.hlog Normal file
View File

@@ -0,0 +1 @@
We hurried home to Savile Row as fast as we could.

View File

@@ -97,6 +97,7 @@ class ASTExtension {
public static function findNextGather(ast:HankAST, path:String, startingIndex:Int, maxDepth:Int):Int {
for (i in startingIndex...findEOF(ast, path)) {
switch (ast[i].expr) {
// TODO what about tagged gathers?
case EGather(_, depth, _) if (depth <= maxDepth):
return i;
default:

View File

@@ -17,12 +17,14 @@ enum OutputType {
InlineDivert(t:String); // A divert statement on the same line as an output sequence.
ToggleOutput(o:Output,
invert:Bool); // Output that will sometimes be displayed (i.e. [bracketed] section in a choice text or the section following the bracketed section)
Glue;
}
@:allow(tests.ParserTest, hank.ChoiceExtension)
class Output {
var parts:Array<OutputType> = [];
var diverted:Bool = false;
public static var GLUE_ERROR = 'Error! You cannot glue anything other than more text output to the end of an output line.';
public function new(?parts:Array<OutputType>) {
this.parts = if (parts != null) {
@@ -67,6 +69,7 @@ class Output {
// This is an individual Output to parse
}
// After toggle outputs are handled, we can begin parsing pieces from start to finish
while (!buffer.isEmpty()) {
var endSegment = buffer.length();
var findBraceExpression = buffer.findNestedExpression('{', '}');
@@ -75,24 +78,18 @@ class Output {
endSegment = slice.start;
default:
}
// If the next piece is not a brace expression
if (endSegment == buffer.length() || endSegment != 0) {
var peekLine = buffer.peekLine().unwrap();
if (peekLine.length < endSegment) {
var text = buffer.takeLine().unwrap();
if (text.length > 0) {
if (text.ltrim().startsWith('~'))
parts.push(HCall(text.ltrim().substr(1)));
else
parts.push(Text(text));
parseText(parts, text);
}
break;
} else {
var text = buffer.take(endSegment);
// If text starts with ~, it's actually a silent HCall.
if (text.ltrim().startsWith('~'))
parts.push(HCall(text.ltrim().substr(1)));
else
parts.push(Text(text));
parseText(parts, text);
}
} else {
parts.push(parseBraceExpression(buffer));
@@ -104,6 +101,29 @@ class Output {
return new Output(parts);
}
private static function parseText(parts:Array<OutputType>, text:String) {
var trimmed = text.trim();
// If text starts with ~, it's actually a silent HCall.
if (trimmed.startsWith('~')) {
parts.push(HCall(trimmed.substr(1)));
}
else {
var endsWithGlue = false;
if (trimmed.endsWith("<>")) {
endsWithGlue = true;
text = text.rtrim().substr(0, text.rtrim().length - 2);
}
if (trimmed.startsWith("<>")) {
parts.push(Glue);
text = text.ltrim().substr(2);
}
parts.push(Text(text));
if (endsWithGlue) {
parts.push(Glue);
}
}
}
private static function updateLastPart(parts:Array<OutputType>, isPartOfAlt:Bool) {
// If the last output is Text, it could contain optional text or an inline divert. Or just need rtrimming.
if (parts.length > 0) {
@@ -178,9 +198,13 @@ class Output {
diverted = false; // Clear the diverted flag
// trace(parts);
var i = 0;
for (part in parts) {
i++;
switch (part) {
case Glue:
if (i > 1) // Glue at the end of a line forces another frame to be concatenated
fullOutput = appendNextText(story, fullOutput, GLUE_ERROR);
case Text(t):
fullOutput += t;
case AltExpression(a):
@@ -214,11 +238,7 @@ class Output {
story.divertTo(t);
// Track whether the last call to format() resulted in a divert, so nested Outputs can interrupt the flow of their parents
diverted = true;
switch (story.nextFrame()) {
case HasText(text):
fullOutput += text;
default:
}
fullOutput = appendNextText(story, fullOutput);
break; // Don't format the rest of this Output's parts
case ToggleOutput(o, b):
@@ -230,4 +250,20 @@ class Output {
return fullOutput;
}
public function startsWithGlue() {
return parts[0] == Glue;
}
public static function appendNextText(story: Story, fullOutput: String, throwOnFail: String = ''): String {
switch (story.nextFrame()) {
case HasText(text):
fullOutput += text;
default:
if (throwOnFail.length > 0) {
throw throwOnFail;
}
}
return fullOutput;
}
}

View File

@@ -218,6 +218,31 @@ class Story {
case EOutput(output):
exprIndex += 1;
var text = output.format(this, hInterface, random, altInstances, nodeScopes, false).trim();
// We need to check if the next expr is a divert or thread because it might lead to a pre-glued output
if (exprIndex < ast.length) {
var nextExpr = ast[exprIndex].expr;
switch (nextExpr) {
case EDivert(targets):
nextExpr = ast[indexOf(targets[0])].expr;
case EThread(target):
nextExpr = ast[indexOf(target)].expr;
default:
}
trace(nextExpr);
switch (nextExpr) {
case EGather(_, _, exp):
nextExpr = exp;
default:
}
switch (nextExpr) {
case EOutput(output) | ETagged(EOutput(output), _):
if (output.startsWithGlue())
text = Output.appendNextText(this, text + " ", Output.GLUE_ERROR);
default:
}
}
return finalTextProcessing(text);
case EHaxeLine(h):
exprIndex += 1;
@@ -428,6 +453,37 @@ class Story {
return newScopes;
}
private function resolveScopes(target:String) {
var newScopes = if (target.startsWith("@")) {
var parts = target.split('.');
var root:Array<StoryNode> = hInterface.getVariable(parts[0].substr(1));
if (parts.length > 1) {
var subTarget = parts.slice(1).join('.');
// trace(subTarget);
resolveNodeInScope(subTarget, root);
} else {
root;
};
} else { resolveNodeInScope(target); };
// trace('$target is $newScopes');
if (newScopes == null // happens when a divert target variable doesn't exist
|| newScopes.length == 0) // happens when target can't be resolved
throw 'Divert target not found: $target';
return newScopes;
}
/** Get the AST index of the given divert target, without diverting **/
private function indexOf(target:String) {
var disposableFork = storyFork(target);
// TODO this probably causes substantial wasting of memory (could lead to garbage collector problems?)
trace('index of $target is ${disposableFork.exprIndex}');
return disposableFork.exprIndex;
}
public function divertTo(target:String) {
// Don't try to divert to a fallback target
if (target.length == 0) {
@@ -444,32 +500,9 @@ class Story {
}
// trace('diverting to $target');
var newScopes = if (target.startsWith("@")) {
var parts = target.split('.');
var root:Array<StoryNode> = hInterface.getVariable(parts[0].substr(1));
if (parts.length > 1) {
var subTarget = parts.slice(1).join('.');
// trace(subTarget);
resolveNodeInScope(subTarget, root);
} else {
root;
};
} else resolveNodeInScope(target);
// trace('$target is $newScopes');
if (newScopes == null // happens when a divert target variable doesn't exist
|| newScopes.length == 0) // happens when target can't be resolved
throw 'Divert target not found: $target';
var newScopes = resolveScopes(target);
var targetIdx = newScopes[0].astIndex;
if (targetIdx == null) {
throw 'Divert target not found: $target';
}
// trace(targetIdx);
// update the expression index
exprIndex = targetIdx;
var target = newScopes[0];