Page 1 of 1

Example on using EXEC

Posted: Thu Oct 10, 2024 5:56 am
by voidstar
The EXEC keyword is typically used as way to bring plain-text BASIC into the system, since the KERNAL ROM V2 BASIC expects to load BASIC in a tokenized form. BASLOAD can handle plain-text (ASCII) as input files, but the LOAD command does not. So EXEC exists as a utility to work around that (for when using the physical hardware; within the emulator you can just paste in the plain text).

But there are some limited ways that EXEC can be used to perform "self modifying" BASIC code. Because of the way the system works, you generally have to use the RUN command to then run the loaded program. And one of the first things RUN does is CLR (clear out BASIC variables from memory). We've been pondering/experimenting with other exotic ways to make use of EXEC. So, this example is doing a couple things: after 90 jiffies (~1.5 seconds) it makes a decision on whether to draw white solid character, or else draw a random color (for a random duration across the screen). Then EXEC is used to self-modify the code to alternate which side of the screen this "simulated cave growth" is drawn on (top vs bottom, the ol'e stalagmite vs stalactite). Can try running this with WARP mode in the emulator.

I've yet to find a way to hide/disable screen output of the EXEC call (which streams the designated plain-text). I suppose you could temporarily do COLOR 6,6 or something (same foreground on background, to effectively make it non-visible). But maybe this concept has uses in graphical modes, which would mask the text output? Just exploring ideas on ways to take advantage of the constantly-interpreted nature of BASIC.

My understanding is that BASIC is essentially dividing into a <code><data> arrangement. The <code> is your tokenized code stored in memory, done as you are typing your lines of BASIC. Then when you RUN, any variables you declare (assign values to) are appended at the end past your tokenized <code> region during your runtime. This keeps everything in LORAM, but makes "true" self-modifying code impractical - as adding any new line (or significantly expanding an existing line) is stomp over the <data> portion. So in this example, it has to go out of its way to modify the code to initialize variables to the values left-off during the previous runtime (two main variables being the OS "output state" and the TL "time last" to maintain the jiffies-based decision tempo). Variables like this could also be stored in other BANKs. Code is also applied to avoid re-clearing the screen during the next "re-RUN".

NOTE: It may be possible to also just block-copy the entire BASIC <data> space to another BANK, since it is possible to programmatically "find" the end of the <code> region (and then just block-copy a prior <data> state back in early on during the "re-RUN"). As I get time, I'll try to prepare an example on how to do that (offhand I've forgotten how to find that <code> region, IIRC the BASIC ROM code maintains a RAM pointer to that?).

You can press ESCAPE to stop the runtime of this example, where the code is then modified again to return it back into a freshly re-runnable state (so you can RUN, press ESCAPE, and RUN again).


cavemadness.jpg
cavemadness.jpg (470.54 KiB) Viewed 392 times

Re: Example on using EXEC

Posted: Thu Oct 10, 2024 6:18 am
by schristis
Never used EXEC before... Im going to have to experiment with that lol..... ;)

Re: Example on using EXEC

Posted: Thu Oct 10, 2024 3:03 pm
by MarkTheStrange
FWIW, if you wanted to create a "null device" on the X16, that wouldn't be too hard. Pick a device number that's not currently in use, like 7. Then you just have to write your own routines to handle OPEN, CLOSE, CHKIN, CHKOUT, CLRCHN, and BSOUT/CHROUT (if you want it to work for input as well as output, you'd need to implement CHKIN, BASIN/CHRIN, and GETIN as well). All of these routines have RAM vectors pointing to their implementations (IOPEN, ICLOSE, etc.), so all you have to do is change the vectors to point to your replacements. Now they'll get called every time the corresponding KERNAL routine is invoked, no matter what device is involved, so it's up to your code to check the current state of things and fall through to the original ROM routines when the current call is not aimed at your device.

But once that is in place, the code to handle NUL is pretty easy. Your OPEN needs to remember the lfn, so the easiest thing to do is only allow one channel at a time to be open to your device, but if you want to make room for more you can. Your CLOSE just clears out the remembered lfn. When CHKOUT is called with a target matching your saved lfn, it just sets a flag indicating that output is now going nowhere. Your CHROUT checks that flag and returns without doing anything when it's set. CHKOUT needs to reset that flag when called with a different lfn, and CLRCHN also needs to reset that flag. But that's really all the logic you need. (To handle input, the corresponding logic is very similar, but you probably also want to set EOF status after an attempted read via CHRIN or GETIN.)

Re: Example on using EXEC

Posted: Tue Oct 15, 2024 3:03 pm
by voidstar
Thanks Mark, I'd like to dig into that after I get through a few other projects. That seems worthwhile to understand, since it seems we could someday resurrect using OPEN to talk to XYZ-serial card (i.e. sort of drivers for whoever serial card comes along?).

Here is a TryItNow for the CaveMadness.

Recall, use CTRL+= to toggle WARP mode. For me, enabling warp mode makes it seem like it is running slower (but it actually is runner faster)

Try It Now!