Author Topic: Understanding how goto and else work  (Read 3704 times)

0 Members and 1 Guest are viewing this topic.

Offline cosmicr

Understanding how goto and else work
« on: May 03, 2022, 06:17:59 PM »
Working on my decompiler a bit more, I originally had it just use goto everywhere, but reading WinAGI's docs, specifically:

Quote
Else and Goto Statements
Code 0xFE is a bit unusual in that it can act as a goto statement, but is also used in conjunction with the if statement to act as an else statement. The operation that AGI performs when it encounters the 0xFE value is to read the next two bytes as an offset, then jump to that new position. This is why this code is referred to as the goto statement.

When used in conjunction with an if statement, this code acts as an else statement. To create an if ... else construct, the programmer includes a goto statement at the end of the commands to be evaluated if the condition is true. The offset value of this goto statement is the length of commands that are executed if the condition is false (i.e., the else block). An example illustrates this concept best:
Code: [Select]
FF 07 E7 FF        if(isset(f231))
05 00                {
65 0F                print("The door is already open.");
FE 11 00             goto END
                     }
0C 24               set(f36);
77                  prevent.input();
3B 05               start.update(o5);
03 98 03            assignn(v152, 3);
4C 05 98            cycle.time(o5, v152);
49 05 E8            end.of.loop(o5, f232);
63 46 9A            sound(70, f154);
END:
This is functionally equivalent to:
Code: [Select]

FF 07 E7 FF         if(isset(f231))
05 00                 {
65 0F                 print("The door is already open.");
                      }
FE                  else
11 00                 {
0C 24                 set(f36);
77                    prevent.input();
3B 05                 start.update(o5);
03 98 03              assignn(v152, 3);
4C 05 98              cycle.time(o5, v152);
49 05 E8              end.of.loop(o5, f232);
63 46 9A              sound(70, f154);
END:                  }

This makes perfect sense, but going through Gold Rush code I found this:

Code: [Select]
    if (isset(f233))
      {
      print("You are already fishing.");
      goto(Label2);
      }

How does WinAGI's decompiler know that this isn't an 'else' block? The bytecode for this is:

Code: [Select]
ff 07 e9 ff  if (isset(f233)
05 00        (block is 5 bytes long)
65 7e        print message 126 ("You are already fishing.")
fe 5d 07     else/goto 1885 bytes (blocksize/offset)



Offline troflip

Re: Understanding how goto and else work
« Reply #1 on: May 03, 2022, 08:12:24 PM »
Is WinAGI's decompiler authoritative? Maybe it just isn't advanced enough to recognize the higher level logic at play, so it resorted to a goto?
Check out my website: http://icefallgames.com
Groundhog Day Competition

Offline cosmicr

Re: Understanding how goto and else work
« Reply #2 on: May 03, 2022, 10:13:03 PM »
Is WinAGI's decompiler authoritative? Maybe it just isn't advanced enough to recognize the higher level logic at play, so it resorted to a goto?

I'm not sure that it is - I don't know whether original AGI game logic codes used else statements - maybe only goto?

That said, I wonder if it's as simple as winagi decides if the "else block" is larger than a certain size it treats it as a goto, since it seems to have plenty of else statements in other places.

Offline AGKorson

Re: Understanding how goto and else work
« Reply #3 on: May 03, 2022, 11:17:10 PM »
That particular 'goto' does not create a well formed 'else' block. If you tried to make it an 'else', the code between it and the 'end-if' would not align.

By 'well-formed', I mean a block of code that immediately follows the end of the 'if' block it's associated with, and that ends at the same block level as the original 'if' statement. An 'else' block can't 'jump' levels. A 'goto' can.

You are talking about logic 42 (rm.ShipSternInterior). Here's the fully decompiled section that contains that 'goto' statement:
Code: [Select]
[ go fishing
if ((said("go", "fish") ||
    said("go", "fishing") ||
    said("fish") ||
    said("acquire", "fish") ||
    said("catch", "fish")))
  {
  [ Jerrod has to be on the aft deck
  if (posn(ego, 40, 30, 65, 52))
    {
    [ if already fishing
    if (isset(fFishing))
      {
      print("You are already fishing.");
      goto(Done);
      }
     
    [ if sick
    if (isset(SickAtSea))
      {
      [ too weak to fish
      print("That is a wonderful idea, but you just can't muster the strength "
            "to do it!");
      }
    else
      {
      [ check for all the required pieces needed to fish
     
      [ paperclip for a hook
      if (PaperclipStatus != 1)
        {
        print("This is the place to fish, but how can you fish without "
              "something to use as a hook?");
        goto(Continue);
        }
      [ string for line
      if (StringStatus != 1)
        {
        print("This is the place to fish, but you are going to need some "
              "fishing line!");
        goto(Continue);
        }
      [ metal for weights
      if (MetalStatus != 1)
        {
        print("This is the place to fish, but you need some weight to attach to "
              "the fishing line!");
        goto(Continue);
        }
      [ ham for bait
      if (!isset(HasHam))
        {
        print("This is the place to fish, but the fish are going to want to see "
              "some bait at the end of that line!");
        goto(Continue);
        }
      [ shovel handle for pole
      if (HandleStatus != 1)
        {
        print("This is the place to fish, all you need now is something to use "
              "as a fishing pole!");
        goto(Continue);
        }
     
      [ ready to go fishing!
      print("Well you've done it!");
      print("You have found the perfect place to fish, and you have everything "
            "you need to do it!");
      [ move into position on the stern deck
      move.obj(ego, 56, 52, 1, fFishMoved);
      [ initiate fishing status
      vFishingStatus = 1;
     
      [ score 8 points
      if (!isset(ScoreFishing))
        {
        sound(s.AddToScore, fSoundDone);
        set(ScoreFishing);
        currentScore  += 8;
        }
      }
    }
  else
    {
    print("You're getting close to the ideal fishing spot, but you're not quite "
          "there!");
    }
  }
 
Continue:
.
.
.
.
Done:
[ call the main timing logic
call(lgc.CapeTripTiming);

return();


You will notice that the 'goto' statement jumps past the end of the if-block that it is inside of. So it can't be an 'else'. To reduce it to a simpler set of lines that are easier to see, this is what is happening:

Code: [Select]
if (testA)
  {
  if (testB)
    {
    statement1;
    goto(Label1);
    }  [endif testB
  statement2;
  } [ endif testA

Label1:
statement3;

If you try to make that an 'else' block, you would get this:
Code: [Select]
if (testA)
  {
  if (testB)
    {
    statement1;
    } else 
    {
******   
*  statement2;
*  } [ endif testA
******
    }  [ endif testB
statement3;

You can see how the block levels don't work. The testA block can't end inside the testB block. Blocks have to be fully nested.

For a more technical definition of 'else' vs. 'goto' in terms of actual code, you could take a look at the WinAGI source, in the 'LogDecode.bas' module.  Basically, decompiling is a two-pass process. The first time through, all jump points (labels), code blocks and their levels are determined, and stored in an array. Then the decompiler goes through the logic code a second time, identifying all commands and if tests. Using the information gathered during the first pass, labels are added as needed, and blocks are confirmed to be either 'else' or 'goto' depending on where they jump to.

WinAGI may not be authoritative, but it's pretty damn smart, and has a very good pedigree. It's based on original code from Peter Kelly's first AGI Studio. His decompiler code was pretty good, and since I did not know much about AGI at the time, I just copied what he had, porting it to VB but mostly following the same approach. I've made a few tweaks to it over the years, but the basic decompilation algorithm is pretty much the same as Peter's original.

And size doesn't matter (yeah, that's what she said...). Unless it's larger than an Integer. Then it would be a Long, and clearly, that would matter.

Offline cosmicr

Re: Understanding how goto and else work
« Reply #4 on: May 04, 2022, 12:10:45 AM »
Great response, thanks for taking the time! I have been considering doing a 2-pass method as you say, but it "appeared" to be working without it. Obviously it wasn't :)

I'm gonna have to re-write my code from scratch almost I think.

Offline troflip

Re: Understanding how goto and else work
« Reply #5 on: May 04, 2022, 05:48:15 PM »
You will notice that the 'goto' statement jumps past the end of the if-block that it is inside of. So it can't be an 'else'. To reduce it to a simpler set of lines that are easier to see, this is what is happening:

Code: [Select]
if (testA)
  {
  if (testB)
    {
    statement1;
    goto(Label1);
    }  [endif testB
  statement2;
  } [ endif testA

Label1:
statement3;



Hmm... isn't that just an if/else inside an if?

Code: [Select]

if (a)
{
if (b)
{
statement1;
}
else
{
statement2;
}
}

statement3;

Yes, it jumps past the testB if block, but that's just because there isn't any code to jump to inside the testA block.
Check out my website: http://icefallgames.com
Groundhog Day Competition

Offline Collector

Re: Understanding how goto and else work
« Reply #6 on: May 04, 2022, 06:17:13 PM »
Looking through the leaked lsl source I don't notice any GoTos, but there are if/else statements

From RM0.CG:
Code: [Select]
if (Controller( cAbout))
{
if (gameHours)
{
Print( 18);
}
else
{
Print( 19);
}
}

It would be nice to have scripting for the modern AGI IDEs/compilers use the same scripting as the original Sierra used, like Phil did with SCI.
KQII Remake Pic

Offline troflip

Re: Understanding how goto and else work
« Reply #7 on: May 04, 2022, 07:02:37 PM »
I forgot I had written some pretty detailed blog posts on decompilation. I dunno if they might be useful? There are some links to white papers on control flow analysis in part 2.

https://mtnphil.wordpress.com/2016/04/09/decompiling-sci-byte-code-part-1/
Check out my website: http://icefallgames.com
Groundhog Day Competition

Offline AGKorson

Re: Understanding how goto and else work
« Reply #8 on: May 05, 2022, 12:27:58 AM »
Hmm... isn't that just an if/else inside an if?

Sorry, my example was a bit too simple. I didn't specify that there are additional statements between the end of testA block and the destination for the goto statement. The code block should actually be
Code: [Select]
if (A)
{
  if (B)
  {
    statement1;
    goto(Label1);
  }  [endif B
  statement2;
} [ endif A
statement4;
Label1:
statement3;

Now you can see how it fails:
Code: [Select]
if (A)
{
  if (B)
  {
    statement1;
  }
  else
  {
  statement2;
} [ endif A
statement4;
  }  [endif B
statement3;
The closing curly brace for ending block A happens BEFORE the end of block B. If you tried to compile this, the compiler would think that the 'endif A' curly brace is 'endif B', and vice versa. Blocks have to be fully nested.

Offline AGKorson

Re: Understanding how goto and else work
« Reply #9 on: May 05, 2022, 12:38:19 AM »
It would be nice to have scripting for the modern AGI IDEs/compilers use the same scripting as the original Sierra used, like Phil did with SCI.

Have you seen this post?

The original Sierra scripting syntax is completely known. 'else' and 'goto' are both supported. There are some significant differences between 'fan syntax' and 'Sierra syntax'.

And there are some really good reasons for not going back to 'Sierra syntax' for serious programming. Having that as an option for nostalgia and completeness might be worth doing at some point, but it's not a priority for me.

Offline troflip

Re: Understanding how goto and else work
« Reply #10 on: May 05, 2022, 01:09:31 AM »

The closing curly brace for ending block A happens BEFORE the end of block B. If you tried to compile this, the compiler would think that the 'endif A' curly brace is 'endif B', and vice versa. Blocks have to be fully nested.

Ok, yep. Makes sense in that context!
Check out my website: http://icefallgames.com
Groundhog Day Competition


SMF 2.0.19 | SMF © 2021, Simple Machines
Simple Audio Video Embedder

Page created in 0.077 seconds with 23 queries.