Search code examples
exceptionf#compiler-constructioninterpretercontrol-flow

How implement Try/Except/Finally in a made-up language interpreter with goto or similar?


How can be implemented the try/catch/finally functionality on a interpreter (currently I'm on F#)?

I suspect that GOTOs can be used (yet, is also necessary to provide the functionality for it in the interpreter, not much idea of how), but I have never used GOTO in my life (only know is evil!) and neither understand how preserve the environment.

P.D: I already know that CPS (Continuations) can be made to emulate exceptions and any other control flow. However it complicate the rest of the language implementation and requiere optimization pass for eliminate the overhead of it, and for this question I wish to understand alternatives ways of do that

P.D.2: Exist another alternative to CPS that lead to easy of implementation of custom control-flows? Or a way to generalize GOTOs for this?


Solution

  • Control can escape a "try" block many ways:

    • Goto a label in a surrounding block (note: goto X and goto Y are 2 different escapes!)
    • Return from the function containing the try
    • Throw an exception in the try
    • Propagate an exception from a function called by the try body
    • (detectably) call exit() in the try body

    You might have more, depending on your language.

    What your try-finally block must do is catch all of these, execute the finally part, and then continue the intended action.

    One way to accomplish this is to create a transfer island for each block escape. The transfer island acts as a target for each of these actions; if control arrives at transfer island K then block escape K has occurred. What the transfer island does is call a (parameterless) subroutine that contains the finally clause, and then executes an action to continue escape K.

    Imagine the following try-finally block:

       try
          ...goto X...  // ... means some control structure wrapped around this
          ...raise Z...
          ...call q()... // throws exception
          ...goto Y...
          ...return 5...
       finally
           <some actions>
       end
       ...return <exp>
    

    This code might be compiled as:

       // try
          ... goto  TIX...  // TIk ==> "tranfer island k"
          ...exception=Z; goto TIE ...
          ...try call q()
             catch exception; goto TIE
             end try 
          ...result=5; goto TIR...
        // finally
          local subroutine finally()
              { <some actions> }
          TIX: call finally();
               goto X;
          TIY: call finally();
               goto Y;
          TIR: call finally();
               goto RETURN;
          TIE: call finally();
               propagate exception;  // means "pass control to containing exception handler"
          ...
          // end try
    
               result=<exp>;
          RETURN:
               return result;
    

    WIth this background, you have to make the interpreter carry out actions as if this code existed. Obviously the interpreter doesn't need to instantiate the transfer islands; it knows which type of block escape has occurred, can execute the finally action, and then carry out continuing the block escape.