Compilation - SamuraiCrow/Yab2Cpp GitHub Wiki

Compilation Strategy

BASIC doesn't require variables to be declared before they are used. This makes compilation trickier than usual because the number, size and types of variables used is generally not known until after the parsing is completed. Many BASIC compilers are forced to side-step the issue by parsing the program twice. This technique is called two-pass compilation.

By using C++ as a backend which has a preprocessor of its own, this opens up another possibility. By sending the results of the first pass into header files (.h files) that are imported into the .cpp source file, we can execute both passes concurrently. In the case of Yab2C++, there are headers for constants, globals and functions respectively written at the same time as the executable code is drafted into the single .cpp file. This is called a pass-and-a-half compilation strategy and is a quick and clever alternative to the two-pass compilers of yesteryear.

Unstructured Commands

Unstructured commands present a particularly difficult challenge for exporting to C++. There simply is no equivalent for some of them in the C++ syntax. As a remedy, Yab2C++ doesn't use structured commands from the backend language at all. Instead, the switch/case statement fall-through conditions provide a simple way to implement labels and jumps as a state machine. In a C++ state machine, the case statements are labels generated by the compiler and jumps are implemented by assigning a label value to the state variable and breaking out of the switch statement. A while loop around the switch statement checks if an exit condition is in the state variable or if it is a label. If it is determined to be a label, control flow loops around to the beginning of the switch statement again to direct to the destination state.

Here's an example:

For x=1 To 5

On x Gosub a,b,c,d,e

Next x

End

Label a

Print "one"

Return

Label b

Print "two"

Return

Label c

Print "3"

Return

Label d

Print 2+2

Return

Label e

Print "Done"

Return

The construct that is produced looks something like the following C code:

int x;

int forStart, forEnd;

int temp1;

enum label

{

Error,Exit,Start,a,b,c,d,e,InitFor,ExitFor,ReturnAddress

};

enum label state;

enum label stack[512];

enum label gosub[]={a,b,c,d,e};

int main()

{

state=Start;

while(state!=Exit && state!=Error)

{

switch(state)

{

case Start:

forStart=1;

forEnd=5;

x=forStart;

case InitFor:

if (x>forEnd || x<forStart)

{

state=ExitFor;

break;

}

*stack++ =ReturnAddress;

state=gosub[x-1];

break;

case ReturnAddress:

x++;

state=InitFor;

break;

case ExitFor:

state=Exit;

break;

case a:

printf("one\n");

state=*--stack;

break;

case b:

printf("two\n");

state=*--stack;

break;

case c:

printf("3\n");

state=*--stack;

break;

case d:

temp=2;

temp+=2;

printf("%d\n",temp);

state=*--stack;

break;

case e:

printf("done\n");

state=*--stack;

break;

default:

state=Error;

}

}

return (state==Exit?0:1};

}