From a967c5f3ef8d82ca07b269d895c1b0280946596d Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Wed, 26 Jun 2019 18:58:52 -0600 Subject: [PATCH] Glue (at the expense of a few regressions). Closed #37 --- examples/glue/main.hank | 14 +++++++ examples/glue/test1.hlog | 1 + hank/HankAST.hx | 1 + hank/Output.hx | 66 ++++++++++++++++++++++++-------- hank/Story.hx | 81 ++++++++++++++++++++++++++++------------ 5 files changed, 124 insertions(+), 39 deletions(-) create mode 100644 examples/glue/main.hank create mode 100644 examples/glue/test1.hlog diff --git a/examples/glue/main.hank b/examples/glue/main.hank new file mode 100644 index 0000000..8f28738 --- /dev/null +++ b/examples/glue/main.hank @@ -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. \ No newline at end of file diff --git a/examples/glue/test1.hlog b/examples/glue/test1.hlog new file mode 100644 index 0000000..07226ae --- /dev/null +++ b/examples/glue/test1.hlog @@ -0,0 +1 @@ +We hurried home to Savile Row as fast as we could. \ No newline at end of file diff --git a/hank/HankAST.hx b/hank/HankAST.hx index ee6c486..eb19c36 100644 --- a/hank/HankAST.hx +++ b/hank/HankAST.hx @@ -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: diff --git a/hank/Output.hx b/hank/Output.hx index fd99354..8944472 100644 --- a/hank/Output.hx +++ b/hank/Output.hx @@ -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 = []; 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) { 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, 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, 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; } } diff --git a/hank/Story.hx b/hank/Story.hx index 4f7c5d9..d26bc1f 100644 --- a/hank/Story.hx +++ b/hank/Story.hx @@ -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 = 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 = 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];