Glue (at the expense of a few regressions). Closed #37
This commit is contained in:
14
examples/glue/main.hank
Normal file
14
examples/glue/main.hank
Normal 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
1
examples/glue/test1.hlog
Normal file
@@ -0,0 +1 @@
|
||||
We hurried home to Savile Row as fast as we could.
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
@@ -227,7 +247,23 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user