ZXSimulator

Anything QL Software or Programming Related.
Post Reply
User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

Another new version:
zx.zip
(22.36 KiB) Downloaded 104 times
This one adds both the PAUSE and the INKEY$ command. The latter is kind of important for writing anything interactive. Had to use a trap call for make that work but it does. Also fixed a few small bugs.

Also, I figured out how ZX81 does its PLOT and UNPLOT commands. It basically scans screen memory before plotting a 4x4 pixel block and if any of the 4x4 pixel blocks (so 16 bits) in the 8x8 pixel block are neither all 0's or all 1's then it clears the entire 8x8 pixel block as it has "detected" a character. However, this causes weird behavior when plotting over character graphics. If it's one of the all black graphics, it leaves is alone since those graphics pass its detection test (i.e. it doesn't detected characters) but if its one of the grey or partial-grey ones it clears them out (so it thinks those are characters).

If I want the same behavior I will have to get to the QL's screen memory to figure out what is already there using a similar algorithm (except that it won't be 16 bits for a 4x4 pixel block since the QL adds color). Or, I could keep track of all 8x8 blocks to see if they contain a character. With 22 rows by 32 columns of characters, that would lead to 704 possible characters or 704 bytes to fix that problem. That's likely too big of a memory hit to just fix that problem (seeing I want it to work on an unexpanded QL).

BTW, all these small incremental changes are me just pushing off finally adding floating point. That's really the final hurdle. To that point I also added the PI function except if you try it you'll see that for now it prints 3 instead of 3.1415927. I have some other inefficiencies in the interpreter that I may remove eventually. One example, instead of doing this: str = ch, I instead write a function setchp(str, i, ch) and I'm not quite sure why I did that. It adds an extra function call where none is needed.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

A bug fix and minor changes...this should be the last of this batch.
zx.zip
(22.58 KiB) Downloaded 102 times
The change is in editing, when doing "EDIT 200" it won't clear the screen but instead leave what's there already. Not sure if I like that. What really should happen is that when editing a specific line the listing should refresh with that line in the center. Problem is that it slows done editing since the listing takes time on an unexpanded QL, though not super-slow. (Again, I always keep the focus on how it runs on an unexpanded QL since that is the point of this pet project).

However, once you enter an edited line, it does, from thereon, center the line and refresh the listing, so it doesn't really save too much time if you are editing more than one line. Still, I like that I could list the code and edit a specific line a little quicker than having to wait. I will have to use it to see if it's worth keeping. If not I may get rid of it and just refresh the listing and live with the little extra wait time. Note that editing does not, and will not, match the ZX81 since I like it being a bit quicker, less cumbersome than the ZX81, and have more of a QL feel. The fix was a bit dirty where in ZXgets() , my main input function, at the end I just check, after skipping leading spaces, if my first character is an 'E' and if so I assume EDIT (it's the only command that starts with 'E" so it works). If I type crap instead, it still works since it won't parse and flag as an error.

Other changes are also subtle. For instance, now Ctrl-S kills the current edit buffer and when you type a non-existing line number with EDIT it finds the next available line unless the number is greater than the last line number. But no more errors when using EDIT. I also changed the included BALL_BAS program to utilize the new INKEY$ function to let you make the ball bounce 'S'lower or 'F'aster. My next sample ZXSimulator BASIC program will be an Elite demo utilizing the PLOT and UNPLOT commands to fire a laser at a moving ship. That ought to be a good stress test for it. Been trying to reclaim old tape programs but man, getting EightyOne to read sampled WAV files thus far has been futile. I just don't know what the audio profile needs to look like (in audacity). EightyOne saves audio is super clean digital to analog conversion and looks nothing like what you get from a real cassette tape.

Also, I'm thinking of eventually getting rid of error messages overall and instead trying to match the ZX81 error codes. Don't know if I should. Is it valuable? The ZXSimulator only tries to run ZX81 BASIC code the same way (i.e. that is what it's simulating) but not pretend to be a fully emulated ZX81. Should it give error messages the same way using those funky numeric codes at the bottom or be more descriptive by having human text? Runtime and syntax error handling in programming languages is not easy and I just quit after the first error and say "good luck programmer, you fix it."


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

I lied...this is the final one in the batch:
zx.zip
(22.44 KiB) Downloaded 91 times
I was writing my Elite demo and ran into some problems. I had broken PRINT as it no longer worked properly due to trying to fix a weird case with SCROLL. Currently SCROLL does not quite work properly:

Code: Select all

5 SCROLL
10 PRINT "HELLO"
20 GOTO 10
Giving an output of:

HELLOHELLO

It should just print one HELLO and then quit.

Still, not a biggie and I'm going to ignore it for now. In addition I found more unused code and got it down an additional 1 1/2 K in size for the executable (at 42,170 bytes). I found a weird Digital 'C' SE behavior that I will need to explore as it seems that:

Code: Select all

for (i=0; i<10; i++)
{
   /* code in here */
}
...takes up 4 more bytes than this version:

Code: Select all

for (i=0; i<10;  )
{
   /* code in here */
   i++;
}
So no idea why moving the i++ increment out of the header reduces it by 4 bytes...should be the same. If that's the case I will replace it all with: i=0; while (i<10) { i++; }.

Additional fixes/changes include the main loop being cleaned up and now looks a little better and I got rid of those goto's in it where I was hitting the switch statement for errors. Also PLOT and UNPLOT now will no longer plot out of range. Tweaked character plotting to get rid of a few more block calls combining +,-,* and 5,6,S and 1,I to save some code size. And I fixed variable names where they work now like the ZX81:

Code: Select all

10 LET MY    COUNTER=10
20 PRINT MYCOUNTER
I basically allow for spaces when parsing a variable name but throw it out when I store it so a name such as "A B C" is stored as "ABC" which is exactly the way the XZ81 does it. You can then access it in any combination of "AB C" or "A BC" or "A B C" or "ABC" with as many spaces as you want. Weird but oh well.

One other thing that needs fixing is line-number lookup. It's a linear search and when a goto goes to a high number when there are a lot of lines in front of it, the time can grow to slow a program down. You can see the problem if you add the following code to the end of BALL_BAS which will take 3 times as long to run. I'll have to make that a binary search to speed it up to normal.

Code: Select all

2000 PRINT "HELLO"
2005 GOTO 2000
So back to writing my Elite demo. I only got as far as plotting the bottom portion. Also realize that as of now I only allow for 7 variable names and it is hardcoded. Will need to make it dynamic eventually. Plus my string variable names still allow for more than single letter names which I may leave in.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

So Digital 'C' SE just doesn't do a great job. I had a bug of this nature:

Code: Select all

   if ((err=linenum(&num)) == NOERR)
      if (str[sp] != ' ' || insertline(num) != NOERR)
         err = BADARG;
      else
         { findline(num); listpgm(1); }
I needed to not only check for space (' ') but also EOL ('\n') and so changed it to this:

Code: Select all

   if ((err=linenum(&num)) == NOERR)
      if ((str[sp] != ' ' && str[sp] != '\n') || insertline(num) != NOERR)
         err = BADARG;
      else
         { findline(num); listpgm(1); }
So simply added an && with that condition. This added a total of 62 bytes to the executable (no other changes). I was a bit surprised by this.

I then tried the following:

Code: Select all

   if ((err=linenum(&num)) == NOERR)
   {
      if (str[sp] != ' ' && str[sp] != EOL)
         return BADARG;
      if ((err=insertline(num)) == NOERR)
         { findline(num); listpgm(1); }
   }
And this only added 38 bytes which is still sizeable for basically wanting to check for '\n' in addition to space.


I then tried another exercise and changed the following code:

Code: Select all

         if (((inc > 0) && (val_i > end)) || ((inc < 0) && (val_i < end)))
            { clearlp(i); }
...

   if (((inc > 0) && (bgn > end)) || ((inc < 0) && (bgn < end)))
      return BADARG;
To this:

Code: Select all

        if ((inc > 0) && (val_i > end)) 
           { clearlp(i); }
        else if ((inc < 0) && (val_i < end))
           { clearlp(i); }
...

   if ((inc > 0) && (bgn > end)) 
      return BADARG;
   else if ((inc < 0) && (bgn < end))
      return BADARG;
But it only saved me 4 bytes. So unrolling &&'s with ||'s doen't fix the first problem. No idea but 62 bytes is expensive for adding one little check.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

Ok, this cut it down to only adding 26 bytes:

Code: Select all

   if ((err=linenum(&num)) == NOERR)
      if (strchr(" \n", str[sp]) == NULL || insertline(num) != NOERR)
         err = BADARG;
      else
         { findline(num); listpgm(1); }
Then tried this and it added 44 bytes:

Code: Select all

   if ((err=linenum(&num)) == NOERR)
      if (((ch=str[sp]) != ' ' && ch != EOL) || insertline(num) != NOERR)
         err = BADARG;
      else
         { findline(num); listpgm(1); }
So one thing I've learned is that doing string array indexing seems to be costly So the code above, though only saving 18 bytes from the original example, that's still significant. I went through and got rid of all instances where I was doing stuff like this:

Code: Select all

while (str[sp] != EOL) putc(str[sp++]);
To

Code: Select all

while ((ch=str[sp]) != EOL) { putc(ch); sp++; }
And that saved a significant amount of code size and likely sped things up.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

New version that fixes a few bugs, including the SCROLL problem. Plus the last version was not able to delete lines which is fixed here. It's slightly slower (3%), likely because of fix in PRINT, which had an issue not catching stuff like PRINT "HELLO" "WORLD" so that is fixed now. Will see if I can get the speed back.
zx.zip
(22.46 KiB) Downloaded 94 times


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

Another new version. Spent time speeding it up a bit (about 2%) and fixed PRINT with all its formatting. Works mostly though tabs (i.e. ,) still give me a little trouble. Also reduced code to 41,766 so that's pretty small from where it was at almost 44,000 without any feature difference.
zx.zip
(22.44 KiB) Downloaded 98 times
But these work now:

Code: Select all

10 PRINT AT 21,31;"P"
20 PRINT AT 10,10;"HELLO"
And this weird one that broke before:

Code: Select all

5 SCROLL
10 PRINT "HELLO"
20 GOTO 10
And a variant that ran for ever now fixed:

Code: Select all

5 SCROLL
10 PRINT
20 GOTO 10
It all has to deal with out-of-bounds issues and I got most of them.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

New banner basic program to run on ZXSimulator (and any ZX81 emulator that can read .p files -- just convert it using zxedit2p.exe).

Code: Select all

10 REM SPIRAL BANNER
20 LET A$="QL ZX-SIMULATOR"
30 FOR K=0 TO 7
40 FOR J=K+1 TO 15-K
50 PRINT AT K,J*2-1;A$(J);A$(3)
60 PRINT AT 21-K,31-J*2-1;A$(3);A$(16-J)
70 NEXT J
80 IF K=0 THEN PRINT AT 0,30;" "
90 IF K=0 THEN PRINT AT 21,0;" "
100 FOR I=K+1 TO 20-K
110 PRINT AT I,31-(K+1)*2-1;A$(3);A$(15-K)
120 PRINT AT 21-I,(K+1)*2-1;A$(K+1);A$(3)
130 NEXT I
140 NEXT K
150 IF A$(1)="%Q" THEN GOTO 20
160 LET A$="%Q%L% %Z%X%-%S%I%M%U%L%A%T%O%R"
170 GOTO 30


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

So there is a problem with not using tokenized keywords. This is actually a legal ZX81 BASIC line:

Code: Select all

10 LET ATO10=5
20 FOR I=A TO 10 TO A TO 10
30 PRINT I
40 NEXT I
It will print 5 once. A more accurate look at what it does is:

Code: Select all

10 LET ATO10=5
20 FOR I= ATO10 TO ATO10
30 PRINT I
40 NEXT I
Of course if I'm just parsing legal identifiers in ZX81 BASIC then I could grab the entire rest of line for a legal variable name:

ATO10TOATO10

And I could write it as:

A T O 1 0 T O A T O 1 0

If it were that. Or I could write it as:

A T O 1 0 TO A T O 1 0

How do I know where to stop? It could be:

ATO10 TO ATO10

Or it could be:

ATO10TOA TO 10

And you can make this as outrageous as possible.

In ZX81 BASIC the TO belonging to the FOR is a token so ZX81 BASIC sees:

A T O 1 0 ** A T O 1 0

Where ** is some number representing the token for TO.

Since I'm dealing in only text I don't get this and just get the letter T and O and I can't really determine what each thing is.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1202
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Post by bwinkel67 »

Came up with a fix, sort of. Now ZXSimulator is only 99.9% compatible with the ZX81...
zx.zip
(22.86 KiB) Downloaded 88 times
The zip file now contains three demo programs, the newest is spiral_bas which is another cool banner demo.

So the fix is in the getvar() function. I simply look for the TO keyword after seeing a space and if I find it I'm done building the variable name. So no names with the word TO in them that has a space right before and after. You can have THISTOTHAT but you can't have THIS TO THAT as it assumes TO is a keyword and gives you the variable THIS. In ZX81 BASIC lingo, THIS TO THAT is allowed, hence the 1% loss of ZX81 BASIC compatibility.

Note that it will allow THIS TOTHAT as a variable so the TO needs to be isolated with spaces before and after, so again, only a 0.1% loss me things. It should impact speed since it only adds a bit of a slowdown if you put a space in the name. If you have it in front of a TO then the getKey() call just eats blank space and checks the first character so that should be quick.

It was a simple fix

From this:

Code: Select all

   for (i=0; i<VARLEN; sp++)
      if (isalpha(ch=str[sp]) || isdigit(ch) || (ch == ' '))
         { if (ch != ' ')  var[i++] = toupper(ch);  }
      ...
To this:

Code: Select all

   for (i=0; i<VARLEN; sp++)
      if (isalpha(ch=str[sp]) || isdigit(ch) || (ch == ' '))
         { if (ch != ' ')
              var[i++] = toupper(ch);
           else 
              if (getkey("TO") == NOERR) { sp = sp - 2; goto end_var; } }
      ...
 


Post Reply