diff --git a/old-examples/main/extra.hank b/examples/main/extra.hank similarity index 100% rename from old-examples/main/extra.hank rename to examples/main/extra.hank diff --git a/old-examples/main/main.hank b/examples/main/main.hank similarity index 89% rename from old-examples/main/main.hank rename to examples/main/main.hank index 7826bb1..8f6c846 100644 --- a/old-examples/main/main.hank +++ b/examples/main/main.hank @@ -8,10 +8,11 @@ Line breaks define the chunks of this section that will eventually get sent to y Your Hank scripts will contain the static content of your game, but they can also insert {demo_var}, even the result of complex {part1 + " " + part2}! ~ var multiline_logic = "Logic can happen on any line before a multiline comment."; +// TODO is the next line even true anymore? /* Multiline block have to start at the beginning of their own line. And can't contain any other logic on the same line as the start or end. Sorry! */ -~ multiline_logic_example = "Logic can happen on any line after a multiline comment."; +~ multiline_logic = "Logic can happen on any line after a multiline comment."; -> choice_example diff --git a/old-examples/main/test1.hlog b/examples/main/test1.hlog similarity index 100% rename from old-examples/main/test1.hlog rename to examples/main/test1.hlog diff --git a/old-examples/main/test2.hlog b/examples/main/test2.hlog similarity index 100% rename from old-examples/main/test2.hlog rename to examples/main/test2.hlog diff --git a/hank/HInterface.hx b/hank/HInterface.hx index b8eb9c4..0e79144 100644 --- a/hank/HInterface.hx +++ b/hank/HInterface.hx @@ -170,7 +170,10 @@ class HInterface { case EBinop(op, e1, e2): if (BOOLEAN_OPS.indexOf(op) != -1) { EBinop(op, boolify(e1), boolify(e2)); - } else { + } else if (op == '=') { + EBinop(op, e1, e2); + } + else { EBinop(op, valify(e1), valify(e2)); } case EUnop(op, prefix, e): diff --git a/hank/Output.hx b/hank/Output.hx index 2025549..ac2ed2a 100644 --- a/hank/Output.hx +++ b/hank/Output.hx @@ -93,7 +93,7 @@ class Output { switch(lastPart) { case Text(t): parts.remove(lastPart); - parts = parts.concat(parseLastText(t, isPartOfAlt)); + parts = parts.concat(parseLastText(t)); default: } } @@ -102,7 +102,7 @@ class Output { /** The last part of an output expression outside of braces can include an inline divert -> like_so **/ - public static function parseLastText(text: String, isPartOfAlt: Bool): Array { + public static function parseLastText(text: String): Array { var parts = []; var divertIndex = text.lastIndexOf('->'); @@ -113,9 +113,6 @@ class Output { var target = text.substr(divertIndex+2).trim(); parts.push(InlineDivert(target)); } else { - if (!isPartOfAlt) { - text = text.rtrim(); - } parts.push(Text(text)); } @@ -175,7 +172,6 @@ class Output { break; } case HExpression(h): - trace(h); var value = hInterface.evaluateExpr(h, scope); // HExpressions that start with ',' will be parsed and formatted as their own outputs if (value.startsWith(',')) { @@ -206,11 +202,6 @@ class Output { } } - // Remove double spaces from the output - while (fullOutput.indexOf(" ") != -1) { - fullOutput = fullOutput.replace(" ", " "); - } - return fullOutput; } } \ No newline at end of file diff --git a/hank/Parser.hx b/hank/Parser.hx index 25ffebb..ce5849e 100644 --- a/hank/Parser.hx +++ b/hank/Parser.hx @@ -120,11 +120,12 @@ class Parser { } /** Split the given line into n tokens, throwing an error if there are any number of tokens other than n **/ - static function lineTokens(buffer: HankBuffer, n: Int, position: HankBuffer.Position): Array { + static function lineTokens(buffer: HankBuffer, n: Int, position: HankBuffer.Position, rtrim: Bool = true): Array { var line = buffer.takeLine().unwrap(); + if (rtrim) line = line.rtrim(); var tokens = line.split(' '); if (tokens.length != n) { - throw 'Include file error at ${position}: ${tokens.length} tokens provided--should be ${n}.\nLine: `${line}`'; + throw 'Wrong number of tokens at ${position}: ${tokens.length} tokens provided--should be ${n}.\nLine: `${line}`\nTokens: ${tokens}'; } return tokens; } @@ -137,7 +138,7 @@ class Parser { static function divert(buffer: HankBuffer, position: HankBuffer.Position) : ExprType { buffer.drop('->'); buffer.skipWhitespace(); - var tokens = lineTokens(buffer, 1, position); + var tokens = lineTokens(buffer, 1, position, true); return EDivert(tokens[0]); } diff --git a/hank/Story.hx b/hank/Story.hx index b5671aa..2e2e549 100644 --- a/hank/Story.hx +++ b/hank/Story.hx @@ -84,10 +84,28 @@ class Story { var story = new Story(random, parser, ast, storyTree, nodeScopes, viewCounts, hInterface); hInterface.addVariable('story', story); + story.runRootIncludedHaxe(script); story.exprIndex = ast.findFile(script); return story; } + + /* Go through each included file executing all Haxe embedded at root level */ + private function runRootIncludedHaxe(rootFile: String) { + var i = 0; + while (i < ast.findFile(rootFile)) { + var file = ast[i].position.file; + switch (ast[i].expr) { + case EHaxeLine(h) | EHaxeBlock(h): + hInterface.runEmbeddedHaxe(h, nodeScopes); + i += 1; + default: + i = ast.findEOF(file) + 1; + } + } + // TODO when parsing an included file, make sure the first line that isn't embedded haxe (block or line form) is a Knot + } + public function nextFrame(): StoryFrame { switch (storedFrame) { case Some(f): @@ -114,11 +132,7 @@ class Story { case EOutput(output): exprIndex += 1; var text = output.format(this, hInterface, random, altInstances, nodeScopes, false).trim(); - if (text.length == 0) { - return nextFrame(); - } else { - return HasText(text); - } + return finalTextProcessing(text); case EHaxeLine(h): exprIndex += 1; @@ -157,7 +171,7 @@ class Story { var optionsText = [for(c in availableChoices()) c.output.format(this, hInterface, random, altInstances, nodeScopes, false)]; if (optionsText.length > 0) { - return HasChoices(optionsText); + return finalChoiceProcessing(optionsText); } else { var fallback = fallbackChoice(); switch (fallback.choice.divertTarget) { @@ -310,7 +324,7 @@ class Story { weaveDepth = 0; case EChoice(c): - storedFrame = Some(HasText(evaluateChoice(c))); + storedFrame = Some(finalTextProcessing(evaluateChoice(c))); return; // Choices and gathers update their own view counts @@ -352,11 +366,34 @@ class Story { } var output = choice.output.format(this, hInterface, random, altInstances, nodeScopes, true); - return output; + return finalChoiceOutputProcessing(output); } /** Parse and run embedded Hank script on the fly. **/ public function runEmbeddedHank(h: String) { embeddedBlocks.push(embeddedStory(h)); } + + private function removeDoubleSpaces(t: String) { + var intermediate = t; + while (intermediate.indexOf(' ') != -1) { + intermediate = intermediate.replace(' ', ' '); + } + return intermediate; + } + + private function finalTextProcessing(t: String) { + if (t.length > 0) + return HasText(removeDoubleSpaces(t).trim()); + else + return nextFrame(); + } + + private function finalChoiceOutputProcessing(t: String) { + return removeDoubleSpaces(t).trim(); + } + + private function finalChoiceProcessing(choices: Array) { + return HasChoices([for(c in choices) removeDoubleSpaces(c).trim()]); + } } \ No newline at end of file diff --git a/tests/ParserTest.hx b/tests/ParserTest.hx index 8360860..cdc678b 100644 --- a/tests/ParserTest.hx +++ b/tests/ParserTest.hx @@ -6,6 +6,9 @@ import hank.Output; import hank.Alt; import hank.HankAssert; +/** + These tests are hard to maintain, and may not be relevant now that parsing largely works +**/ class ParserTest extends utest.Test { var ast: HankAST; @@ -25,7 +28,7 @@ class ParserTest extends utest.Test { assertNextExpr(EOutput(new Output([Text("This file contains test cases for output expression parsing.")]))); assertNextExpr(EOutput(new Output([Text("A line won't be interrupted or anything.")]))); assertNextExpr(EOutput(new Output([Text("Multiline comments an output expression. This should parse as one line of output.")]))); - assertNextExpr(EOutput(new Output([Text("Comments at the end of lines won't parse as part of the Output.")]))); + assertNextExpr(EOutput(new Output([Text("Comments at the end of lines won't parse as part of the Output. ")]))); assertNextExpr(EOutput(new Output([Text("You can "), HExpression("insert"), Text(" the values of expressions.")]))); assertNextExpr(EOutput(new Output([HExpression("you"), Text(" can start an output line with an insert expression. "), HExpression("and_end_one")]))); @@ -117,7 +120,7 @@ class ParserTest extends utest.Test { assertNextExpr(EGather(Some('labeled'), 2, EOutput(new Output([Text("deep gather")])))); assertNextExpr(EChoice({id: 0, onceOnly: true, label: None, condition: None, depth: 1, output: new Output([Text("Simplest possible choice")]),divertTarget: None})); - assertNextExpr(EChoice({id: 1, onceOnly: false, label: None, condition: Some("condition"), depth: 2, output: new Output([Text("Choice that ends with a divert")]),divertTarget: Some("target")})); + assertNextExpr(EChoice({id: 1, onceOnly: false, label: None, condition: Some("condition"), depth: 2, output: new Output([Text("Choice that ends with a divert ")]),divertTarget: Some("target")})); assertNextExpr(EChoice({id: 2, onceOnly: true, label: None, condition: None, depth: 1, output: new Output([]), divertTarget: Some("fallback_choice")})); assertNextExpr(EChoice({id: 3, onceOnly: true, label: None, condition: None, depth: 1, output: new Output([]), divertTarget: Some("")}));