diff --git a/examples/hexpressions/main.hank b/examples/hexpressions/main.hank index beb8189..e11ad89 100644 --- a/examples/hexpressions/main.hank +++ b/examples/hexpressions/main.hank @@ -2,7 +2,8 @@ Have you tried inserting {test} in your Hank stories? ~ var good = true; Rumor has it, it can be {if (good) { - 'rewarding' + 'rewarding'; } else { - 'obnoxious' -}} to write a story with {test}. \ No newline at end of file + 'obnoxious'; +}} to write a story with {test}. +Null values will simply not appear.{if (!good) 'Invisible.'} \ No newline at end of file diff --git a/examples/hexpressions/test1.hlog b/examples/hexpressions/test1.hlog index 2104639..a23ac5e 100644 --- a/examples/hexpressions/test1.hlog +++ b/examples/hexpressions/test1.hlog @@ -1,2 +1,3 @@ Have you tried inserting dynamic content in your Hank stories? -Rumor has it, it can be rewarding to write a story with dynamic content. \ No newline at end of file +Rumor has it, it can be rewarding to write a story with dynamic content. +Null values will simply not appear. \ No newline at end of file diff --git a/hank/HInterface.hx b/hank/HInterface.hx index d0ceab1..3671696 100644 --- a/hank/HInterface.hx +++ b/hank/HInterface.hx @@ -13,38 +13,73 @@ class HInterface { var parser: Parser = new Parser(); var interp: Interp = new Interp(); + var viewCounts: ViewCounts; - public function new(?variables: Map) { - if (variables != null) { - interp.variables = variables; + public function new(viewCounts: ViewCounts) { + this.viewCounts = viewCounts; + + this.interp.variables['_isTruthy'] = isTruthy; + } + + static function isTruthy(v: Dynamic) { + switch (Type.typeof(v)) { + case TBool: + return v; + case TInt | TFloat: + return v > 0; + default: + throw '$v cannot be coerced to a boolean'; } } - public function getVariable(v: String) { - return interp.variables[v]; + public function addVariable(identifier: String, value: Dynamic) { + this.interp.variables[identifier] = value; } /** Run a pre-processed block of Haxe embedded in a Hank story. **/ - public function runEmbeddedHaxe(haxe: String) { - var expr = parser.parseString(haxe); + public function runEmbeddedHaxe(h: String) { + trace(h); + var expr = parser.parseString(h); expr = transmute(expr); interp.execute(expr); } + public function evaluateExpr(h: String): String { + var expr = parser.parseString(h); + trace(expr); + expr = transmute(expr); + var val = interp.expr(expr); + if (val == null) { + return ''; + } else { + return Std.string(val); + } + } + + public function resolve(identifier: String, scope: String): Dynamic { + if (interp.variables.exists(identifier)) { + return interp.variables[identifier]; + } else { + return viewCounts.resolve(identifier, scope); + } + } + /** Convert numerical value expressions to booleans for binary operations **/ function boolify(expr: Expr): Expr { - // TODO this won't work in cases where the expression is already a bool - return EBinop('>', expr, EConst(CInt(0))); + return ECall(EIdent('_isTruthy'), [expr]); } /** Adapt an expression for the embedded context **/ function transmute(expr: Expr) { + if (expr == null) { + return null; + } return switch (expr) { case EIdent(name): // TODO if the name is a root-level view count, return EArray(view_counts, ...) diff --git a/hank/Output.hx b/hank/Output.hx index c134f3b..abb0a89 100644 --- a/hank/Output.hx +++ b/hank/Output.hx @@ -151,6 +151,7 @@ class Output { fullOutput += t; case AltExpression(a): case HExpression(h): + fullOutput += hInterface.evaluateExpr(h); case InlineDivert(t): case ToggleOutput(o, b): if (b != displayToggles) { diff --git a/hank/Story.hx b/hank/Story.hx index 34407b5..b71b126 100644 --- a/hank/Story.hx +++ b/hank/Story.hx @@ -30,14 +30,11 @@ class Story { parser = new Parser(); ast = parser.parseFile(script); - // viewCounts = new ViewCounts(ast); - var variables = [ - 'story' => this/*, - 'viewCounts' => viewCounts - */ - ]; - hInterface = new HInterface(variables); + var viewCounts = new ViewCounts(ast); + + hInterface = new HInterface(viewCounts); + hInterface.addVariable('story', this); exprIndex = ast.findFile(script); } @@ -51,6 +48,11 @@ class Story { case EOutput(output): exprIndex += 1; return HasText(output.format(hInterface, false)); + case EHaxeLine(h): + exprIndex += 1; + + hInterface.runEmbeddedHaxe(h); + return nextFrame(); default: return Finished; } diff --git a/hank/StoryTestCase.hx b/hank/StoryTestCase.hx index 167cc46..b91ebf6 100644 --- a/hank/StoryTestCase.hx +++ b/hank/StoryTestCase.hx @@ -23,7 +23,7 @@ class StoryTestCase extends utest.Test { // Allow white-box story testing through variable checks prefixed with # if (StringTools.startsWith(line, "#")) { var parts = StringTools.trim(line.substr(1)).split(':'); - HankAssert.equals(StringTools.trim(parts[1]), Std.string(story.hInterface.getVariable(parts[0]))); + HankAssert.equals(StringTools.trim(parts[1]), Std.string(story.hInterface.resolve(parts[0], ''))); } if (StringTools.startsWith(line, "*")) { // Collect the expected set of choices from the transcript. diff --git a/hank/ViewCounts.hx b/hank/ViewCounts.hx new file mode 100644 index 0000000..0d1432f --- /dev/null +++ b/hank/ViewCounts.hx @@ -0,0 +1,11 @@ +package hank; + +class ViewCounts { + public function new(ast: HankAST) { + + } + + public function resolve(identifier: String, scope: String): Int { + return 0; + } +} \ No newline at end of file diff --git a/tests/HInterfaceTest.hx b/tests/HInterfaceTest.hx index 860d905..1e0f389 100644 --- a/tests/HInterfaceTest.hx +++ b/tests/HInterfaceTest.hx @@ -4,13 +4,14 @@ import utest.Test; import utest.Assert; import hank.HInterface; +import hank.ViewCounts; class HInterfaceTest extends utest.Test { var hInterface: HInterface; public function setup() { - hInterface = new HInterface(); + hInterface = new HInterface(new ViewCounts([])); } function assertVar(name: String, value: Dynamic) { @@ -20,6 +21,8 @@ class HInterfaceTest extends utest.Test { public function testVarDeclaration() { hInterface.runEmbeddedHaxe('var test = "str"'); assertVar('test', 'str'); + hInterface.runEmbeddedHaxe('var test2 = 2'); + assertVar('test2', 2); } public function testBoolification() {