_Basil

[Introduction

 Do you use BASIC libraries? Maybe you write your own or use one of the systems such as "DrWimp" or "EventShell". They are a great saving of programming effort.

 The library code is loaded into an application's workspace so that it is accessible to the program, but this uses memory. Moreover, the same code is loaded into each application that uses the library. Many programs do not use the whole, or even a substantial part, of the library, so that more memory than neccessary is used. Does this waste of memory concern you?

 One way to help is to link the library into your program, transferring only the used code, but this must be done after every major change. Another is to break the library into sections and only load those required. A further saving is to make the sections into overlays, but this needs very careful management.

 Perhaps you have noticed that 'C' programmers have a shared library. Maybe you have tried a support module such "WimpExt", but found that it it is quite large and that you are no longer just programming in BASIC.

 If you go back before RiscOS you may have used BASIC's immediate command 'INSTALL' which puts a library permanently in memory so that any program loaded can use it. Of course, only one program at a time could be loaded, so it is redundant in an age of multi-tasking.

 I have been through all these permutatations, searching for my Holy Grail, true BASIC shared libraries. Basil is my answer, and it turns out to be surprisingly simple after all.
]
[Getting started with Basil

 The Basil module is loaded in the usual way in a !Run file, eg:

> RMEnsure Basil 1.00 RMLoad System:Modules.Basil

 At the start of the program, link to the library list and ensure the required libraries with, eg:

> REM>!RunImage
> SYS"Basil_Link"
> SYS"Basil_Find","MyLibrary","Basil:Libraries.MyLib"
> SYS"Basil_Find" ...

 Use the library routines as usual.

 Before ending, declare that the program no longer needs the libraries with, eg:

> SYS"Basil_Lose","MyLibrary"
> SYS"Basil_Lose" ...
> END

 It is not essential for your program to lose libraries, but it is the friendly thing to do. Basil will free the memory when a library is not being used.
]
[How Basil works

 My BASIC Reference Manual says: "Basic libraries are stored as a word, which is a pointer to the next library (0 denoting the end of the list). The word is followed immediately by the BASIC program which forms the library."

 There are two library lists maintained by BASIC, with pointers in the workspace designated LIBRARYLIST and INSTALLLIST. The former is for libraries loaded into the variable heap with the keyword 'LIBRARY', and is the one normally used. The latter is for libraries loaded at the top of memory using the immediate command 'INSTALL'. 'INSTALL' cannot be used inside a program and is is effectively redundant, leaving INSTALLLIST to be used by Basil.

 The Basil module puts an empty BASIC program into its workspace to act as a link library and links it to another that is permanently within the module and acts as an end marker. (I know this is slightly redundant, but it is flexible, and is retained from earlier versions)

 A BASIC program calling SYS"Basil_Link" causes the address of the link library to be put into INSTALLLIST, faking an 'INSTALL' command.

 SYS"Basil_Find" looks for a library in the list. If it is not found it loads a default file into the relocatable memory area (RMA), checks that the it contains the required library, and links it into the list. A counter is incremented to record that the library is in use.

 The keywords 'LIBRARY' and 'OVERLAY' are unaffected.

 SYS"Basil_Lose" decrements the counter and if the count is zero the memory in RMA is released and the links in the list are adjusted.

 To see what libraries are in the list use *BasilList, which acts a bit like the immediate command 'LVAR'.

 Because BASIC uses only the memory addresses of routines when they have been used once, the Basil module and its found libraries cannot be moved or replaced without crashing a client program. If Find and Lose pairs are used consistently there will be no problems, but Basil will not die while it is in use. In case of emergency *BasilReset will remove all libraries, but it should be used with care.

 Basil also provides two SWIs and two service calls that module libraries and my RFSFiles module use to link into the list. They should not be used in a program.
 
 SWI"Basil_Library" duplicates the LIBRARY statement but puts the code into RMA. SWI "Basil_Release" must be used to free the memory used. These call are independant of the rest of Basil.

 Details of the commands are in the Appendices.
]
[Using libraries with Basil

{Format of library headers

 The BASIC code inside a Basil library should conform to the normal recommendations for libraries; Basil is not concerned with this.

 However, Basil needs to find the name of a library, and it uses the line number of the first line to count the number of finds.

 So the first line must be line 0 [1] and be formatted this way:

> 0 REM> {sp} <name> [{sp}+ <version>] {sp} [<other>]

. {sp}  	= 0 or more spaces
. {sp}+ 	= 1 or more spaces
. <name>	= unique name
. <version>	= version number - optional
. <other>	= any other information, such as a date string

 For example:

> 0 REM> MyLibrary 1.34 (29 Feb 2000)

 This is quite a common way of doing things anyway.

 At the moment Basil does not use the version number but may in future, so it should be included for compatibility.
 
 [1] At the moment Basil forces line number 0, but in the future this may be use to discriminate libraries from general programs. Line 0 can be entered in any editor showing line numbers. StrongED can be set up to save with lines numbered from 0. Zap can be set up with physical line numbers starting at 0. The BASIC direct commands AUTO and RENUMBER accept 0 as their first parameter.
}
{Conflicting versions of libraries

 Because Basil keeps libraries once they have been loaded and are in use, it may not be possible to install a required newer library version if an older with one with the same title is already installed.

 A similar problem existed when, for example, the SharedCLibrary was not in ROM but was RMEnsured by applications. It was not uncommon for a newer version to replace an older one and cause C programs to crash when they could no longer find their pointers.

 The solution was to make sure that only the latest version of the module was visible to all applications, in the !System.Modules directory. !SysMerge, or on RiscPCs the System configuration, is used to make sure that this is so.

 I recommend that this common-use practice is used with Basil libraries, and the '!Basil' application is one way of holding libraries to do this. Basil modules can be put in the System as normal.
 
 When developing libraries, SYS"Basil_Find" can be configured to force a new version of a found library to the beginning of the list, leaving the old version resident. This should not be used in a finished program, as it leads to a memory leakage, and there is ambiguity when libraries are lost.
}
{Names of library routines

 With several libraries installed and each containing many procedures and functions there is great scope for the existence of duplicates. A program will use the first routine it finds in the list, and the order of libraries is undefined.

 To avoid these conflicts I recommend a naming convention identical to that used for SWIs. The first part of a name should be the library name, followed by '_' and then the name of the routine.

 For example, if the library title is 'MyLibrary' then the procedure to make a sound could be PROCMyLibrary_MakeSound().

 See 'Feedback' for more about this.
}
{Basil module libraries

 From version 1.10, Basil provides for library modules to install and remove themselves. The advantage is that the libraries remain at the same place in memory if not killed or reinitialised; the disadvantage is that they have an overhead of about 200 bytes and they do not free memory when not in use. The modules will be created by the !Basilica application. [not yet implemented]

. They are shown after *BasilList with a "*" suffix.

. They always have a count of 0.

. They are found by SYS"Basil_Find" but otherwise unaffected.

. They are not removed by SYS"Basil_Lose".

. They are removed from the list by *BasilReset, but of course the modules remain in memory.

. They re-install when Basil is loaded or re-initialised.

 See 'Installing libraries from memory' for details and warnings about creating module libraries.

 My RFSFiles module allows BASIC files to be added to ResourceFS, and they may install and remove themselves in the same way as library modules. The overhead is slightly less, and it is more flexible.
}
{Basilica modules

 !Basilica is a separate application that produces modules containing a single BASIC library, as well as Basil library modules. In early versions these modules only linked to INSTALLLIST directly, and could not be used in the same program as Basil libraries. They now link to the start of the Basil list as well, so any installed Basil libraries can be used in a program using one of them. The Basilica library does not appear in the Basil list and is not available generally. The link is maintained as long as a Basil module is loaded.
}
{Overlay libraries

 If reducing memory usage is important, Basil can be used to implement overlays, independent of the OVERLAY statement. This is done by calling Basil_Find just before using a library, and Basil_Lose just after. This can be nested, and even recursive I think, but like all such calls, they must be in pairs.
}
]
[Application workspace

 When most of a program is contained in Basil libraries, the application workspace claimed with *WimpSlot can be much reduced.

 The BASIC language workspace is just under 4k. The pagesize on a RiscPC is 4k and on a 1M Archimedes 8k. So the smallest wimpslot must be 8k. Many simple utilities can run in this if the variables and code take less than 4k.

 Unfortunately a 2M Archimedes takes 16k and a 4M takes 32k. This additional space is inevitably wasted without some tricky program combining.

 However, if memory is to be claimed for data, say sprites, windows, messages or drawfiles, then the space can be used.
 
 Using SWI"Basil_Library" even the main code can be in RMA. The advantage of this is that less memory has to be paged in and out, which may improve the response of the desktop.
]
[Installing module libraries

 Basil module libraries and my RFSFiles module use the facility to install BASIC libraries held in memory, the RMA in these cases.

 The libraries must conform to the Basil requirements and there must also be one writeable word immediately before the BASIC code. The address of this word is passed in SWIs "Basil_Install" and "Basil_Remove".

 To allow for operations on the Basil module, the controlling program must be able to respond to service calls, and will therefore be a module too.

 It is essential to respond to "Service_BasilResetting" by removing all linked libraries, so that Basil can reset. It is likely that removed libraries will be relinked in response to a following "Service_
BasilStarting". (I know that BasilReset could just remove module libraries, but I have at all stages guarded against arbitrary changes to libraries).

 NOTE: BASIC routines are only looked for by name when first used. Thereafter only the address of the routine is used. This is the reason for not allowing libraries to be arbitrarily removed from memory. A benefit of this when installing from memory is that routines that have already been used continue to be available to a program, even if the library has been unlinked, although the use of new routines will produce error 30, 'No such function/procedure'. When the library is relinked it will be useable as before.
]
[Copyright

 Basil and its documentation are  Steve Drain, Kappa.

. mailto:basil.program@kappa.zetnet.co.uk
. http://www.users.zetnet.co.uk/kappa/
. snailto:Steve Drain, 161a Pymore Road, Bridport, Dorset DT6 3AW

 This version maybe freely distributed and included unchanged in disc libraries and at ftp sites. It is not in the public domain.

 The source code is not included in this package but I can sent it to anyone who is interested.
]
[Appendix 1 - SWIs

{Basil_Link

(SWI &50400)

 Links a BASIC program to the Basil library list.

-On Entry

. R0 = INSTALLLIST or 0 if this is not specified

-On exit

. R0 = the value of INSTALLLIST used

-Use

 This call checks that it has been made from a BASIC program and puts the address of the library list into INSTALLLIST. It should usually be used once only at the start of a program.

 Possible error is 'Not in BASIC'

 Note: This SWI only has meaning when called from a BASIC program; the results are otherwise unpredictable. The BASIC check will not work after an error has occured in a program.

 Addendum: Basilica modules now use the 'X' form of this SWI to link the Basil list after their own library.

-Related SWIs

 None
}
{Basil_Find

 (SWI &50401)

 Ensures a library is available to a BASIC program.

-On Entry

. R0 = pointer to the name of the library to find
. R1 = pointer to full filename of the default file to load
. R2 = version required * 100 [not implemented]
. R3 = 0, or any other value to force default library to load

-On exit

. R0 = current count of the library
. R1 = address of the library block

-Use

 The list of libraries is searched for the name in R0. If it is found its count is incremented. Otherwise the file named in R1 is loaded into RMA. The name is checked again and if not found the memory is freed and an error returned. If found, it is linked to the list.

 Version number 0 means any version, and is the default.

 The flag in R3 can be used to load a new version of a library while retaining the old one in the list. In a development environment, this allows programs to continue to find routines they are already using. However, more memory is used for each new version, and SWI Basil_Lose will lose the latest library first, which could be ambiguous. This should not be used in completed programs.

 This call has no effect on module libraries and the count returned in R0 is 0.

 Possible error is 'Library not found'.

-Related SWIs

 Basil_Lose
}
{Basil_Lose

 (SWI &50402)

 Releases a library that is no longer reqired by a BASIC program.

-On Entry

. R0 = pointer to the name of the library to lose

-On exit

. R0 = current count of the library

-Use

 The list of libraries is searched for the name in R0. If it is found its count is decremented and if it is then zero the library is removed and memory is freed. Otherwise an error is returned.

 This call has no effect on module libraries and the count returned in R0 is 0.

 Possible error is 'Library not found'.

-Related SWIs

 Basil_Find
}
{Basil_Install

 (SWI &50403)

 Installs a library in memory to the Basil list.

-On Entry

. R0 = pointer to address of the library to install

-On exit

. R0 preserved

-Use

 This call may be used by other modules to link their libraries to Basil.

-Related SWIs

 Basil_Remove
 
-Related Service calls

 Basil_Starting
}
{Basil_Remove

 (SWI &50404)

 Removes a library in memory from the Basil list .

-On Entry

. R0 = pointer to address of the library to remove

-On exit

. R0 preserved

-Use

 This call may be used by other modules to unlink their libraries from Basil.

 Possible error is 'Library not found'.

-Related SWIs

 Basil_Install
 
-Related Service calls

 Basil_Resetting

 (SWI &50403)

 Installs a library in memory to the Basil list.

-On Entry

. R0 = pointer to address of the library to install

-On exit

. R0 preserved

-Use

 This call may be used by other modules to link their libraries to Basil.

-Related SWIs

 Basil_Remove
 
-Related Service calls

 Basil_Starting
}
{Basil_Library

 (SWI &50405)

 Loads a library into RMA and links it to LIBRARYLIST.

-On Entry

. R0 = pointer to full filename of library to load

-On exit

. R0 = address of the library block

-Use

 This duplicates the function of the LIBRARY statement, but the library does not occupy application workspace and is not therefore paged in and out. This might improve the responsiveness of the desktop.

 The LIBRARY statement is not affected.

 Because libraries are not in application workspace the memory is not freed when a program quits. It is essential to use SWI Basil_Release before quitting.

 NOTE: The use of this call is likely to produce more fragmentation of RMA.

 Possible error is 'Library not found' if the filename is not a BASIC file.

-Related SWIs

 Basil_Release
 
-Example

 Put all the code of the '!RunImage' file into a 'Main' file, enclosing the main program in a procedure called 'PROCMain. Then use this as a '!RunImage':

> SYS"Basil_Library","<App$Dir>.Main"
> PROCMain
> SYS"Basil_Release"
> END

 WARNINGS: Make a backup of !RunImage before modifying it. Crunched programs may not be easily edited.
}
{Basil_Release

 (SWI &50406)

 Releases memory claimed by Basil_Library.

-On Entry

. None

-On exit

. None

-Use

 This routine must always be called before quitting if SWI Basil_Library has been called, in order to free memory.

 The action is to remove any libraries that are in RMA from the LIBRARYLIST and release the memory. Libraries loaded with the LIBRARY statement are unaffected.

 WARNING: Although the memory is released, addresses of routines that BASIC has already used are remembered and may be used. Until the memory is re-allocated they may still work, but cannot be relied on. It is highly rcommended that this SWI is only called immediatedly before END.

-Related SWIs

 Basil_Library 

-Examples

 See Basil_Library 
}
]
[Appendix 2 - Commands

{*BasilList

 Lists BASIC libraries linked to Basil

-Syntax

 *BasilList

-Parameters

 None

-Use

 Prints the first lines of the BASIC libraries installed. Libraries installed by other modules are suffixed with "*".

-Example

 *BasilList

-Related commands

 None

-Related SWIs

 None
}
{*BasilReset

 Empties the linked list and clears memory

-Syntax

 *BasilReset

-Parameters

 None

-Use

 If a BASIC program has found a library, but has not lost it, perhaps after a crash, Basil assumes that it is still in use and refuses to die.

 If it is certain that no programs are using Basil this command will empty the list of linked libraries and free all the RMA used by found libraries.

 Although libraries that have been installed by other modules will be
unlinked, they remain in memory and will reinstall when Basil is
reloaded or reinitialised.

-Example

 *BasilReset

-Related commands

 None

-Related SWIs

 None
 
-Related Service calls

 Basil_Resetting
}
]
[Appendix 3 - Service calls

{Service_BasilStarting

 (Service Call &80880)

 The Basil module has been loaded or re-initialised.

-On Entry

. None

-On exit

. None

-Use

 Modules that have libraries to link to Basil's list should install
them. This will usually follow their removal after a reset.
 
-Related SWIs

 Basil_Install
 
-Related Service calls

 Basil_Resetting
}
{Service_BasilResetting

 (Service Call &80801)

 The Basil module is being reset.

-On Entry

. None

-On exit

. None

-Use

 Modules with libraries linked to Basil's list with SWI "Basil_Install" must remove them. They may also take other action, as no new routines can be found.
 
-Related SWIs

 Basil_Remove
 
-Related Service calls

 Basil_Starting
}
]
[Appendix 4 - Errors

{&814900, "Basil is in use"

 Attempt to kill or replace Basil while a BASIC program is using a library.
}
{&814901, "Not in BASIC

 Use of SWI Basil_Link outside a BASIC environment.
}
{&814902, "Library not found"

 An attempt to find a library in the list has failed and the default file did not exist, was not type FFB or did not contain the correct library.

 An attempt has been made to remove a library using an incorrect address. This is only used internally.
}
]
[Appendix 5 - History

 The early history of how Basil came about is in the file 'History'; this is from the first release version on.

{Version 1.01 (20 Jan 1998)

 This has been thoroughly tested by me, but beta-testers have given very little feedback, so either it works or the program is no use!

 I have reduced the core library to a pair of useful procedures and put the rest in libraries; I could be persuaded to change that.

 This release arises from a change of ISP and website.
}
{Version 1.02 (26 Jan 1998)

 On futher consideration I have removed the core library.

 The included documentation has been revised and reduced.
}
{Version 1.12 (14 Feb 1998)

 Included SWI"Basil_Install" and SWI"Basil_Remove" to be used by library modules. Other code adjusted to allow for modules.

 Defined and used "Service_BasilStarting" and "Service_BasilResetting", so that modules can remove and re-install libraries.

 Added a flag to SWI"Basil_Find" to force the loading of the default file. This can reduce the need for *BasilReset while developing libraries.

 Some internal tidying of search code.
}
{Version 1.15 (25 Jun 1998)

 A little code tidying and a moderate updating of the documentation.

 My module 'RFSFiles' has been extended to integrate closely with 'Basil' so that BASIC files added to ResourceFS can link to the list as module libraries. This echoes a much earlier version of 'Basil', and is now my own prefered method. My 'Basilisk' resource application, in development, uses both modules to provide an object-oriented way of programming in BASIC.
}
{Version 1.16 (25 Jan 1999)

 Bug: Basil_Find no longer returned count and address of a library since the previous code tidying!
}
{Version 1.20 (26 Jan 1999)

 Prompted by discussing of 'lazy task swapping' and desktop reponsiveness, I have added SWIs Basil_Library and Basil_Release, primarily to allow the main code of a program to be outside application workspace.

 RMA is used, but a dynamic area for all libraries is next step.

 NOTE: The experimental v1.20 with BASIC code has not been pursued.
}
{Version 1.21 (26 Sep 1999)

 This only fixes a nasty bug introduced into the code for version 1.20.
}
{Future

 Include version checking, but how this would operate is not clear.
}
]
[Appendix 6 - Feedback

 I would welcome comments on some different aspects of implementing Basil, as well as the documentation.

{Using ResourceFS

 It is quite feasible for the BASIC libraries to be registered with ResourceFS and to appear in Resources. From there a user could inspect the code and maybe run it to produce some action - I have started a StrongHelp manual in this way. The penalties are that the code /is/ accessible and that each library would be a bit larger. I decided not to do it this way.

 My module RFSFiles is developed from earlier versions of Basil which did use ResourcesFS. It now integrates closely with Basil.
}
{The SWI interface

 It is usually neater to use SWIs to interface with a module, and they can also return values, which might be useful. However, *commands are  more transparent to a user. I have in mind a shell system in which SYS need never be used.

 The current version provides only SWIs, but these could be *commands instead, which I have coded but not implemented. *Commands do make a module significantly larger.
}
{The value of INSTALLLIST

 The value of INSTALLLIST (&86A0) has been the same in all versions of BASIC V and VI (BASIC64) up to 1.16. Basil assumes this value by default, but allows a different one to be used if this becomes necessary.

 I know how to get at this value dynamically in User mode, say in a utility, but not in Service mode in a module. Does anyone know how to step reliably through BASIC workspace from &8000 to get this value?

 If a future version of BASIC uses a different value it can be found with this function:

> DEF FN_INSTALLLIST
>  P%=&4000
>  [OPT 0
>  LDR r0,[r14,#36] \get offset of TIMEOF
>  ADD r0,r0,r8     \add offset to ARGP
>  ADD r0,r0,#8     \get INSTALLLIST
>  MOV PC,r14
>  ]
> =USR(&4000)

 Now, Jun 98, I have good idea how to do what I want, but it would take a moderately large chunk of quite awkward code. I could then also detect a BASIC environment reliably by checking for the word &BAS1Cxxx in the rom. I do not think it is worthwhile.
}
{Using Basil outside BASIC

 Since Basil_Link writes directly to application workspace it would probably be disastrous to use it excecpt in a BASIC program. To try to protect the user from this possibility, Basil detects a BASIC program by looking at the error buffer at the start of the workspace, which contains a copyright message before an error occurs. This is fine if Basil_Link is called at the start of a program, but might be less successful otherwise. See fold above about using the rom identifying word.

 Libraries could be successfully manipulated with Basil_Find and Basil_Lose outside BASIC, but this might lead to problems and why would anyone do it?

 Now, Jun 98, I have the answer 'yes', and my module RFSFiles does just that, to good effect.
}
{Library routine names

 If a unique naming convention is used, as suggested above, the routine names get quite large and they should not be crunched. SWIs get around this by having fixed length numbers that are only referenced by their names.

 What is needed is for library routine names to be compact but unique (and probably cryptic!) and for a separate library to translate from readable names while writing and debugging. Then a crunch program could take a translation file and produce the compact form.

 At the extreme, the names could be digital eg PROC_50405 with blocks of numbers allocated like SWI numbers!

 I have more detailed ideas on this and a specification for an application to do some of the work. Is it worthwhile?
}
{Naming conventions

 You may notice that the utilities library use the character '@' (ASC64) as a separator in routine names. That this is legal is not well known, and I hope thereby to keep the names out of conflict with any other routines you may come across. The character '`' (ASC96) is also legal in routine names, and variables.

 I have prefixed the names of library files that have Basil headers with '' so that they are recognizeable. My Basilisk system uses '' (ALT-s) as a prefix.
}
]
