Difference between revisions of "Spectranet: Tutorial 6"

From Spectrum
Jump to navigation Jump to search
(New page: The Spectranet ROM provides some facilities for extending the ZX BASIC interpreter. At present, there is an interpreter extension mechanism, and there are plans to add support for channel...)
 
Line 11: Line 11:
When the new command is being executed, the Spectranet ROM is paged in. However, there's a simple mechanism to make calls to the BASIC ROM which will automatically page the Spectranet RON back in on return. This has been modeled on the way the ZX Interface 1 dispatches calls to the ZX BASIC ROM, to allow commands written to work with the Interface 1 to run on the Spectranet. It also means that the examples in the Complete Shadow ROM disassembly will work with very few modifications.
When the new command is being executed, the Spectranet ROM is paged in. However, there's a simple mechanism to make calls to the BASIC ROM which will automatically page the Spectranet RON back in on return. This has been modeled on the way the ZX Interface 1 dispatches calls to the ZX BASIC ROM, to allow commands written to work with the Interface 1 to run on the Spectranet. It also means that the examples in the Complete Shadow ROM disassembly will work with very few modifications.


New commands can be written either in assembly language or C (or a mixture of C and assembly language). Commands can run from within the lower 16K of address space (for example, as part of a Spectranet ROM module or from static RAM), or in the Spectrum's main RAM. Code that's to run from Spectranet memory should be assembled into the area between 0x2000 and 0x2FFF (paging area B).
New commands can be written either in assembly language or C (or a mixture of C and assembly language). The C functions are found in the libspectranet library (which is distinct from the socket library), and is a wrapper around the ROM entry points. Commands can run from within the lower 16K of address space (for example, as part of a Spectranet ROM module or from static RAM), or in the Spectrum's main RAM. Code that's to run from Spectranet memory should be assembled into the area between 0x2000 and 0x2FFF (paging area B).


== Adding a simple extension ==
== Adding a simple extension ==


(todo)
In this context, a simple BASIC extension is just a very simple command with no arguments. It's the sort of thing you might write if you wanted to simply launch a routine that you had written that takes no arguments and returns nothing. You may use it, for example, to start an IRC client in ROM, or perhaps do something simple like display the IP, netmask, gateway and DNS servers on screen.
 
The code to make it happen is very simple for both C and assembly language programmers.
 
Firstly, a structure must be filled which contains the command, the error code that triggers this command (which in the case of a simple command, is always C - Nonsense in BASIC - error code 0x0B), the address of the routine that must be called and what page (if any) should be paged into paging area B for the duration of the command. This is passed to a Spectranet ROM routine which adds the command to the extensions table. In this example, the code for the new command will be in the Spectrum's main RAM.
 
Below is the example code for adding the extension in C.
 
#include <stdio.h>
#include <spectranet.h>
// The string that is to be interpreted must live somewhere. We declare it here.
// Typing '''*simple''' in BASIC will result in our new command being run.
char *token="*simple";
// Prototype for the call to the command. It'll always be void something()
void simpleCmd();
main()
{
        // This structure is used to install the new command into the Spectranet ROM
        // extension interpreter.
        struct basic_cmd bc;
 
        bc.errorcode=TRAP_NONSENSE; // Trap nonsense in basic
        bc.command=token;              // A pointer to the string above
        bc.rompage=0;         // don't do paging - running from main RAM
        bc.function=simpleCmd;          // Pointer to the function that gets run when *simple is entered
 
        if(addbasicext(&bc) < 0)        // The addbasicext() call in libspectranet adds the new command.
        {
                printk("Failed to add extension\n");
                return;
        }
        printk("Added basic extension.\n");
}
 
Once this code has successfully executed, then typing '''*simple''' will execute whatever's in the function void simpleCmd(). The information we suppled to the addbasicext routine all lives in the basic_cmd structure. This consists of the error code when the ZX BASIC ROM executes RST 8 (since it knows nothing of the *simple command, it will trigger RST 8 with a C Nonsense in BASIC error), a pointer to the string for this command, the page in ROM (if any) that the routine lives in - here, it's set to zero as we're running from main RAM, and a pointer to the function to call.
 
There is some special structure to the simpleCmd function. Unfortunately, it´s not quite as easy as just depositing your code there, you also have to interact a little with the ZX ROM. In the case of a complex command (with parameters) you may need to make several calls to the ZX ROM to verify and fetch these parameters. For a simple command like this, which has no parameters, you don't need to do very much. The other thing to note is when your function finally exits, it must exit via a jump to a ROM routine that arranges everything correctly for the return to BASIC, or the computer will crash.
 
Here is the simpleCmd function. It will just print "Statement executed" on the screen, and that's it:
 
void simpleCmd()
{
        statement_end();        // Check for statement end - an error will be returned
                                // to BASIC if any parameters follow, because this command
                                // has none.
        printk("Statement executed.\n");
        // This exits after the command was successfully executed.
#asm
        jp 0x3E99
#endasm
}
 
There's not much to it, but there are some interesting things to note which will affect how you write the functions that handle your new command.
 
None of the code that actually does the work of the command should appear ''before'' the call to statement_end(). If you put some code before statement_end() it'll get executed twice because the simpleCmd() routine above gets called twice! This is because for each C Nonsense in BASIC error which we handle, the ZX ROM will execute RST 8 twice. The code after statement_end(), however, only gets run once - after all of the command's arguments have been successfully processed into something usable.
 
The function finishes with a single instruction enclosed in #asm directives. The indirect call via 0x3E99 handles the proper return of control back to ZX BASIC after your function has completed.
 
(todo asm lang example)


== Further reading ==
== Further reading ==

Revision as of 21:52, 1 October 2008

The Spectranet ROM provides some facilities for extending the ZX BASIC interpreter.

At present, there is an interpreter extension mechanism, and there are plans to add support for channels and streams at a later date. This tutorial will introduce BASIC extension methods.

How BASIC extensions work

When the ZX BASIC interpreter encounters something it considers an error, it executes the RST 8 instruction with the error code as the byte following the RST 8 instruction. The CPU calls the address at 0x0008, where the error routine examines the stack, and picks off the byte at the return address (the error code). It then processes it in the appropriate way.

The Spectranet traps execution at this address (unless this has been disabled by a hardware jumper or in software). This means the Spectranet ROM code runs at this point, and this gives an opportunity to look at some alternate syntax for new commands. If there's no command to run, control gets passed back to the ZX ROM to handle the error as it would have, had the trap not been present. The Spectranet ROM examines a table of commands, and if the command that the ZX ROM threw out matches one in the Spectranet's table, it calls a routine for the new command. For simple commands, this routine will simply execute the action intended. For more complex commands, there may be further parsing to be done.

When the new command is being executed, the Spectranet ROM is paged in. However, there's a simple mechanism to make calls to the BASIC ROM which will automatically page the Spectranet RON back in on return. This has been modeled on the way the ZX Interface 1 dispatches calls to the ZX BASIC ROM, to allow commands written to work with the Interface 1 to run on the Spectranet. It also means that the examples in the Complete Shadow ROM disassembly will work with very few modifications.

New commands can be written either in assembly language or C (or a mixture of C and assembly language). The C functions are found in the libspectranet library (which is distinct from the socket library), and is a wrapper around the ROM entry points. Commands can run from within the lower 16K of address space (for example, as part of a Spectranet ROM module or from static RAM), or in the Spectrum's main RAM. Code that's to run from Spectranet memory should be assembled into the area between 0x2000 and 0x2FFF (paging area B).

Adding a simple extension

In this context, a simple BASIC extension is just a very simple command with no arguments. It's the sort of thing you might write if you wanted to simply launch a routine that you had written that takes no arguments and returns nothing. You may use it, for example, to start an IRC client in ROM, or perhaps do something simple like display the IP, netmask, gateway and DNS servers on screen.

The code to make it happen is very simple for both C and assembly language programmers.

Firstly, a structure must be filled which contains the command, the error code that triggers this command (which in the case of a simple command, is always C - Nonsense in BASIC - error code 0x0B), the address of the routine that must be called and what page (if any) should be paged into paging area B for the duration of the command. This is passed to a Spectranet ROM routine which adds the command to the extensions table. In this example, the code for the new command will be in the Spectrum's main RAM.

Below is the example code for adding the extension in C.

#include <stdio.h>
#include <spectranet.h>

// The string that is to be interpreted must live somewhere. We declare it here.
// Typing *simple in BASIC will result in our new command being run.
char *token="*simple";

// Prototype for the call to the command. It'll always be void something()
void simpleCmd();

main()
{
       // This structure is used to install the new command into the Spectranet ROM
       // extension interpreter.
       struct basic_cmd bc;
 
       bc.errorcode=TRAP_NONSENSE;	// Trap nonsense in basic
       bc.command=token;               // A pointer to the string above
       bc.rompage=0;		        // don't do paging - running from main RAM
       bc.function=simpleCmd;          // Pointer to the function that gets run when *simple is entered
 
       if(addbasicext(&bc) < 0)        // The addbasicext() call in libspectranet adds the new command.
       {
               printk("Failed to add extension\n");
               return;
       }
       printk("Added basic extension.\n");
}

Once this code has successfully executed, then typing *simple will execute whatever's in the function void simpleCmd(). The information we suppled to the addbasicext routine all lives in the basic_cmd structure. This consists of the error code when the ZX BASIC ROM executes RST 8 (since it knows nothing of the *simple command, it will trigger RST 8 with a C Nonsense in BASIC error), a pointer to the string for this command, the page in ROM (if any) that the routine lives in - here, it's set to zero as we're running from main RAM, and a pointer to the function to call.

There is some special structure to the simpleCmd function. Unfortunately, it´s not quite as easy as just depositing your code there, you also have to interact a little with the ZX ROM. In the case of a complex command (with parameters) you may need to make several calls to the ZX ROM to verify and fetch these parameters. For a simple command like this, which has no parameters, you don't need to do very much. The other thing to note is when your function finally exits, it must exit via a jump to a ROM routine that arranges everything correctly for the return to BASIC, or the computer will crash.

Here is the simpleCmd function. It will just print "Statement executed" on the screen, and that's it:

void simpleCmd()
{
       statement_end();         // Check for statement end - an error will be returned
                                // to BASIC if any parameters follow, because this command
                                // has none.
       printk("Statement executed.\n");

       // This exits after the command was successfully executed.
#asm
       jp 0x3E99
#endasm
}

There's not much to it, but there are some interesting things to note which will affect how you write the functions that handle your new command.

None of the code that actually does the work of the command should appear before the call to statement_end(). If you put some code before statement_end() it'll get executed twice because the simpleCmd() routine above gets called twice! This is because for each C Nonsense in BASIC error which we handle, the ZX ROM will execute RST 8 twice. The code after statement_end(), however, only gets run once - after all of the command's arguments have been successfully processed into something usable.

The function finishes with a single instruction enclosed in #asm directives. The indirect call via 0x3E99 handles the proper return of control back to ZX BASIC after your function has completed.

(todo asm lang example)

Further reading

(todo)