EgonOlsen71 / basicv2

A Commodore (CBM) BASIC V2 interpreter/compiler written in Java
https://egonolsen71.github.io/basicv2/
The Unlicense
84 stars 15 forks source link

Fixes GameTests #10

Closed ciplogic closed 5 years ago

ciplogic commented 5 years ago

This should fix the game tests. I also fix a missing "Logger.log"

EgonOlsen71 commented 5 years ago

Merged it. As a sidenote: If possible, please avoid omitting the curly braces on ifs. I'm not so much about code style, I don't care where the braces are placed and if one indents using tabs or spaces, because you can always reformat this to your liking in the IDE. But I really can't stand

if (blah) blubb();

I don't see the point...

if (blah) { blubb(); }

is so much more readble to me and I would like the keep the source code that way.

EgonOlsen71 commented 5 years ago

Hi,

thinking of what you want to achieve and how BASIC actually works on these old machines, two possible problems that you most likely have to deal with came into my mind. Maybe you are already aware of them, but in case you don't (because they don't feel natural to people who are used to modern languages), here they are:

A RETURN ends all FOR-loops started after its corresponding GOSUB. In that regard, it's similar to something like:

for (int i=0; i<10; i++) {
    if (i==4) {
        return;
    }
}

in a modern language. But then again, it's not. Take a look at this (pretty pointless code, but anyway...):

10 FOR I=0 TO 10
20 GOSUB 100
30 NEXT
40 END
100 FOR X=0 TO 10
110 IF X=4 THEN RETURN
120 NEXT
130 RETURN

The RETURN in line 110 will end the loop started in line 100. So after returning, the next NEXT in line 30 refers to the FOR-loop in line 10 and all is well and works as expected. But if you replace the GOSUB/RETURN with GOTOs (from what I got, this is what you want to do, or isn't it?), the FOR-stack won't be cleared of the loop on line 100 when "returning" with GOTO instead of return. So two things will happen:

  1. The next NEXT in like 30 will still refer to the FOR-loop in line 100, because it's still on the FOR-stack
  2. The FOR-stack will overflow if you do things like this too often

As far as I can see, you can do two things (assuming that you can detect these cases):

  1. don't do it in these cases and leave them untouched
  2. replace line 100 with something like (with T being some otherwise unused temp. var): IF X=4 THEN T=X:X=10:NEXT:X=T:GOTO 30

Solution 2.) will clear the FOR-stack and retain the actual value of X. However, you might have started more than one loop at that time, so it can get even more complicated than that to close them all.

The next problem is how to detect this case. As you might have guessed (or know) already, FOR and NEXT in this BASIC are independant commands. They don't really require each other at least not in a strict sense. It's perfectly fine to have 10 FOR-loops but just one NEXT or 10 NEXTs but just one FOR-loop. Sure, it's bad style...but that's what 8-bit BASIC is most known for, so people did it. For example, you could use one NEXT for two FORs:

10 FOR I=0 TO 5
20 PRINT I:GOSUB 100
30 NEXT
40 END
100 FOR X=0 TO 10:PRINTTAB(10);X
110 IF X=4 THEN RETURN
120 GOTO 30

this does the exact same thing as:

10 FOR I=0 TO 5
20 PRINT I:GOSUB 100
30 NEXT
40 END
100 FOR X=0 TO 10:PRINTTAB(10);X
110 IF X=4 THEN RETURN
120 NEXT
130 RETURN

Sure, it's bad style and prone to errors...but it happens in actual code more often then you would think.

How to deal with that...I'm not sure. I just wanted to point it out in case you weren't aware of it. Because I for sure wasn't when I started this project...:-)

ciplogic commented 5 years ago

Thank you for heads up!

I will try to get up-to speed around this kind of code. So it is great that the flag is "off" by default.

To say you frankly, I had only programmed on a ZX Spectrum clone and I never knew about GOSUB till after I jumped to Pascal bandwagon.

Though I wanted to add "GOSUB" removal as the "grand plan" in my mind is the following: (this will be probably the next PR, hopefully I will have something at the end of the week the number I, and over another week the inliner, and over another one or two week the simple block propagator)

I. Consolidating statements What I mean: if there are 3 lines as following:

10 PRINT "A"
20 GOTO 50
50 ....

150 GOTO 10

There should be a "merge" of lines 10 and 20:

10 PRINT "A": GOTO 50
50 ...
150 GOTO 10

II. One line inliner

And if it is done correctly (I hope it will 🗡 ) I can do both "basic block optimisations" (probably the initial constant propagation over the Line) and inlining of simple 1 row statements which always end with a GOTO

For the scope of illustration after "1 line inliner" code will look like following:

10 PRINT "A": GOTO 50
50 ...
150 PRINT "A": GOTO 50

(GOTO 10 is replaced with statements till the ending GOTO)

III. "Basic block propagation" Every constant defined with LET, will be saved in a map, and it will be replaced to all subsequent statements over the line if there no other jumping statement but the final GOTO or "conditional IF".

Contrived example:

A = 2: B = A+3: IF A > 3 THEN GOTO 150

I hope to have as you can assume:

A = 2: B = 2+3: IF 2 > 3 THEN GOTO 150