inkle / ink

inkle's open source scripting language for writing interactive narrative.
http://www.inklestudios.com/ink
MIT License
3.97k stars 481 forks source link

Compiler crash when tunnel stitch accesses knot-level temp variable #186

Open AlexRComer opened 7 years ago

AlexRComer commented 7 years ago

The compiler crashes when a temp variable created within a knot is accessed from a stitch in that knot, only if the stitch is a tunnel.

The following script results in "System.Exception: RUNTIME ERROR: Variable 'solution' could not be found in context '-1'. This shouldn't be possible so is a bug in the ink engine."

=== Ink_Compiler_Crash_Course ===

    With gouts of plasma spewing from the left thruster, the starship Ink Compiler was hurled into the planet's atmosphere. Flames ripped across the hull as the surface came closer.

    ~ temp solution = "Activate the weft drive"  

    -> save_the_ship -> END

    = save_the_ship

        Captain Loom knew there was just one chance to save the ship.

        "{ solution }," he cried! // knot level temp variable should be accessible from the stitch

        ->->
joethephish commented 7 years ago

Hrm. I remember we had some conversations about exactly how temporary the temp variables should be, and I think we decided they should be "very" temporary, so shouldn't be accessible after any kind of divert, even to stitches within the knot. Part of that decision is technical - internally it's tricky to ensure that temp variables are available in exactly the "correct" scope.

So, probably the fix will actually be to tighten it up to give you a compile time error rather than a runtime error. Probably not what you'd be looking for, sorry!

AlexRComer commented 7 years ago

I thought that might be the case! A more accurate runtime error message would be fine – my code accessing an undefined variable doesn't mean there's a bug in the compiler! Note that this crash also happens if you do the following.

    {define_me}
    ~ temp define_me = "selfish"

I guess my 'tunnel' problem is that going into a tunnel creates a clean slate in order to handle recursion, whereas regular diverts retain variable values and only worry about lexical scope (it seems stitches can access temps defined in the knot, but not in other stitches, which is sensible). You'd have to pass knot-level variables by reference into the new scope when tunnelling to a stitch, which can be done explicitly with parameters in any case. It would be great if an error message had pointed this out to me though!

=== Ink_Compiler_Crash_Averted ===

    With gouts of plasma spewing from the left thruster, the starship Ink Compiler was hurled into the planet's atmosphere. Flames ripped across the hull as the surface came closer.

    ~ temp solution 

    -> save_the_ship(solution) ->

    "{ solution }," he cried!

    -> END

    = save_the_ship(ref how)

        Captain Loom knew there was just one chance to save the ship.

        ~ how = "Activate the weft drive" 

        ->->
Stratege commented 7 years ago

If the decision to make temporary variables should be changed, from a technical aspect it shouldn't be too difficult if one uses dynamic scope instead of lexical scope. A very simple way of using dynamic scope is to pass a association list along (var_name,value). The downside there is in the difficulty of statically ensuring that no variable gets used that isn't in scope in that particular place. (Though another solution with automatically generated constraints similar to the implicit vars extension of Haskell could likely provide both)