RISC World

Modules for Beginners

Modules, getting started

The First Module

In the last part I explained how to set up the BASIC Assembler to produce relocatable code and used it to produce a module header with title and help strings. In this part I will extend the ideas to produce some simple modules showing how to set up help strings and star commands with parameter values.

The first example called Module1 on the CD produces a module which does nothing! Well it initialises, and finalises correctly and has a title and help strings, so it will respond to *modules and *help Module1 commands. Here is the code:

     REM Basic Assembling template for Modules
     REM J.B.Pickard for RiscWorld
     DIM mcode% 1024:     REM reserve memory for assembling code
     FOR pass% = 4 TO 7 STEP 3:REM two pass offset assembling
     P% = 0:        REM offset zero all offsets are from start of module.
     O% = mcode%
     REM Now setup the Module Header
     [OPT pass%
     EQUD 0          ;application startcode
     EQUD init%      ;initialisation
     EQUD final%     ;finalisation
     EQUD 0          ;service start
     EQUD title%     ;Module title string
     EQUD help%      ;Module help string
     EQUD 0          ;command table offset
     .title%         ;start of title string
     EQUS ”Module1•
     EQUB 0          ;terminating zero byte
     ALIGN          ;align for start of help string on a word boundary
     .help%          ;start of help string
     EQUS ”Module1•+CHR$(9)+”1.00 (•+MID$(TIME$,5,11)+”)•+CHR$(13)
     EQUS ”All it does is respond to *module and *help Module1 commands•
     EQUB 0          ;terminating zero byte
     ALIGN           ;align for rest of module code
     .init%          ;start of module initialisation code
     STMFD R13!,{R7-R11 ,R14}
     LDMFD R13!,{R7-R11 ,PC}
     .final%          ;start of module finalisation code
     STMFD R13!,{R7-R11 ,R14}
     LDMFD R13!,{R7-R11 ,PC}
     REM Alter the pathname for the file so that it
     REM will save the code in a convenient place
     SYS ”OS_File•,10,”full pathname for module file•,&FFA,,mcode%,O%

A Few Notes

Do use the ON ERROR trap line, it makes debugging much simpler. Be careful with the SYS"OS_File",10 line. It must have a legal full path for the file and there are two commas between &FFA and mcode% parameters. You should find the module file appear in your chosen disc destination.

Double click on this module file, nothing should happen, your computer should work as before. Now open the Task Window (ctrl + F12). Type Modules [ret] at the star prompt. A list of the all the modules should appear with Module1 at the bottom. This shows your module has been successfully loaded and is residing in the RMA.

Now type Help Module1 [ret] The modules help message should appear with the date you assembled it. Now type *RMkill Module1 [ret] The prompt should return. Typing Modules [ret] will now show a new list which no longer contains Module1. N.B. [ret] stands for one press of the return key.

Module2 - a more useful example

This module demonstrates how easy it is to set up a star command. The following lines are added to module1 (the full code is in the file Mod2SRC):

     EQUS "Mod2on"  ;Name of star command (not case sensitive)
     EQUB 0         ;zero byte terminator
     ALIGN          ;align to start of next word
     EQUD oncom%    ;pointer to this commands routine
     EQUD &00000000 ;star command information word
     EQUD onsyntax% ;pointer to help syntax message
     EQUD onhelp%   ;pointer to help message for command
     EQUD "Mod2off" ;start of next command
     EQUB 0
     EQUD offcom%
     EQUD &00000000
     EQUD offsyntax%
     EQUD offhelp%

     EQUD 0         ;zero word denotes the end of the command table

The above is called the command table. It has a strict structure.

First comes the name of the command terminated by a zero byte. RISC OS uses this to produce a lookup table. Next come 4 words:

  • A pointer to the start of the routine for the command.
  • Then an information word. This sets the parameters for the command. How many, what type, how are they to be read etc. In our example nothing is set. I will explain this word in greater detail later.
  • The next word points to a string which is used if the user calls the command with the incorrect number of parameters.
  • Finally we have a word pointing to a more general help string which is used when * help commandname is typed.

The whole structure is then repeated for the next command. A zero word is then used to mark the end of the command table.

The next set of lines setup the syntax and help strings for the commands.

     .onsyntax%          ;syntax message for command
     EQUS ”Syntax: *Mod2on (no params)•
     EQUB 0
     .onhelp%          ;help message for command
     EQUS ”*Mod2on (no params) test help message•+CHR$(13)
      +CHR$(10)+”first line of text.•+CHR$(13)+CHR$(10)
      +”second line of text.•+CHR$(13)+
      CHR$(10)+”Third line of text.•
     EQUB 0
     EQUS ”Syntax: *Mod2off (no params)•
     EQUB 0
     EQUS ”Syntax: *Mod2off (no params) test help message•
     EQUB 0

The following lines set up the routines for each command:

     .oncom%            ;start of actual code executed when *Mod2on typed
     STMFD R13!,{R0-R11 ,R14}
     ADR R0,onhelp%     ;in this case just print out the help string
     SWI ”XOS_Write0•
     SWI ”XOS_NewLine•
     LDMFD R13!,{R0-R11 ,PC}
     .offcom%         ;start of actual code executed when *Mod2off typed
     STMFD R13!,{R0-R11 ,R14}
     ADR R0,offhelp%   ;in this case just print out the help string
     SWI ”XOS_Write0•
     SWI ”XOS_NewLine•
     LDMFD R13!,{R0-R11 ,PC}

The pushing and pulling of registers R0 to R11 is probably an overkill but sometimes SWI's do corrupt certain registers! All thats left to do is alter the module header so the command table pointing word is changed from zero to command%.

When this module is run you should be able to do the following in a Task Window: Type either Mod2on[ret] or Mod2off[ret] and get back the help string for the command. Mod2on[ret] will give the syntax message. (RISC OS has used this message as an error since these commands do not have any parameters).

Typing *help commands[ret], this gives a list of all the modules commands loaded. Ours should be the last one in the list. Typing *help Mod2on[ret], this should give the help message for this command.

Module3 Using parameters

The source code for this module is called Mod3SRC on the CD. This demo module is set up to accept 3 parameters for its *commands as shown below:

     *newuser "name chrs" age system$variable

Rather strange but it is only a demo! The 1st parameter must be placed in quotes and can be one word or more. The 2nd parameter is typed as an integer. The third is the name of a system variable like File$Type_FFF etc.

Once this command is executed the module stores the data and then prints the message Data Accepted. Its other * commands are as follows

     *modusers (no parameters)
     *userdetails "name  chrs"

The first one just prints out a list of the users. The second will find the data for matching the name.

The reason I chose such strange data is it shows the method of reading and transforming parameter data into a useable forms. The command block is set up as follows:

     EQUS ”newuser•  ;Name of star command (not case sensitive)
     EQUB 0
     EQUD nuser%     ;pointer to this commands routine
     EQUD 3+(3<<16)+(4<<8)     ;star commands information word
     EQUD nusyntax%  ;pointer to help syntax message
     EQUD nuhelp%    ;pointer to help message for command
     EQUS ”Modusers• ;next star command
     EQUB 0
     EQUD moduscom%
     EQUD &00000000  ;do not need any parameters
     EQUD modussyntax%
     EQUD modushelp%
     EQUS ”userdetails•     ;final star command
     EQUB 0
     EQUD fnduser%
     EQUD 1+(1<<16)     ;only need first parameter
     EQUD fndussyntax%
     EQUD fndushelp%
     EQUD 0          ;zero word denotes the end of command table

The Information word

This word holds the following:

Byte 0: The minimum number of parameters before an error will be reported. For the first command we need all 3. For the last command we only need one.

Byte 1: The way the first 8 parameters may be read by RiscOS. Each bit is used for each parameter. If set then RiscOS will try and expand the parameter as is if it is a system variable.In our example the third parameter is a system variable e.g. <File$Type_FFF> Note the use of < > to enclose the variable.

So to read this variable before it is passed to the module we need to set bit 2. Hence the (4<<8) value.

Byte 2:The maximum number of parameters before an error will occur. Hence for the first parameter (3<<16) and for the last parameter (1<<16).

Byte 3: This is rarely used, it contains flags which determine whether a string variable is a filing system command like *ADFS etc. or a star command related to *Status or *Configure.

Hence we do not use it.The full annotated BASIC source listing is on the CD called Mod3SRC.

On entering any star command routine RISCOS sets up the following registers:

  • R0 points to the command tail. That is the complete string of characters that make up the parameters.
  • R1 contains the number of spaces in the command tail which equals the number of parameters. NOTE: Any spaces contained in quotation marks are ignored.
  • R12 contains the memory location of the modules private word. This value is usually the pointer to the workspace.
  • R13 is the usual pointer to a full descending stack.
  • R14 holds the return address.

I use !Zap for grabbing workspace and module from the RMA for checking. This excellent routine is very powerful, I have never used all its features. You can download it from the web.

Well that is it for this time. Just for fun(!) try and add another star command to module3 called 'deluser ' which will take the name parameter only and will delete the record from the workspace, then move all records after it up by 512 bytes.

My solution next time

Brian Pickard