RISC OS was designed specifically for the ARM CPU.
The SWI instruction (
SoftWare Interrupt) is the means by which user software interacts
with RISC OS. In BBC Basic the SYS command is
provided to give this facility.
The SWI instruction has the effect of passing control
from the user's code to code in a module. If all goes
well, control will be returned back to the next
instruction of the user's code.
Data is passed to the module's code, and passed back to
the user's code, in the lower ARM registers. These
registers either contain data
directly, as flags or numbers or addresses, or
indirectly as pointers to
blocks; that is, arrays of consecutive bytes in
memory where data is to be found. BBC Basic's use of blocks
for strings, or blocks created by the DIM statement
(without parentheses), is an essential part of its ability
to interact with RISC OS.
For reasons of portability, standard Lua provides nothing
that lets you create, write to, or read from blocks of memory.
Writing to blocks of memory is a dangerous business anyway,
without proper safeguards, and safety is something standard Lua
insists on. However, it does provide the notion of
userdata at the API level. RiscLua takes advantage of
this to provide a library,
block , to make up for
the absence in standard Lua of the memory arrays that RISC OS
insists on for its communication with user programs.
You can create a block of n words with
b = block.new(n)
A
word is four consecutive bytes, with the lowest address
on a
word boundary i.e. a multiple of 4. The type of
a block is userdata.
print(type(b)) --> userdata
The value of
#b is the number of words in the block.
You can also create a block by giving as argument to
block.new a string rather than a number. Just enough
words will be used to accommodate the string, followed by an ASCII NUL,
and the string will be stored in the block.
For words, blocks have a notation somewhat like that of tables.
The individual words in the block can be read and written to as
b[0], b[1], . . . . , b[#b-1]
Note that the indices are word offsets, and start at 0, not 1.
You can assign to a word in a block either a number, or an address
(see below), or a block (its address is written), or a string (the string
is stored in a block and the block's address is written). If the i-th word
of a block
b holds a pointer to a NUL-terminated
string then the expression
b^i is the string.
An
address has the form
b + m where
b evaluates to a block, or an address, and
m evaluates to a number, giving the offset in bytes from the start of the block to the address.
A different notation is used for bytewise access to a block. The
expression
b(m) denotes the m-th byte of the block.
So
m runs from
0 to
4*#b - 1 . The expression
b/m gives the longest string in
the block, starting at the m-th byte, that contains no control codes.
An expression
b(m,x) writes data bytewise to the
block starting at the m-th byte. If
x is a number
its least significant byte is written. If it is a string, as much
of it as will fit is written consecutively.
The
swi library contains two objects: a
block
swi.r of 8 words, and a function
swi.! . This function takes as argument either an
SWI number or an SWI name. Calling the function has the effect
of calling the SWI with the values of the words
swi.r[0], swi.r[1], . . . in registers R0, R1, . . . .
The function returns
true if successful, with
the values in the block
swi.r holding
the returned register values. On failure it returns
nil followed by an error message; the register
block is not updated.
A convenient phrase is
assert(swi.!(swiname))
If you are translating a program from Basic to RiscLua be warned that
swi.! differs from Basic's SYS command in a subtle but
important way. For the SYS command in Basic, ARM register parameters which are not explicitly mentioned are set to zero. In RiscLua a component of
swi.r retains the value it had after the last call to
swi.! . Some Basic programs may make implicit use of this
aspect of SYS, which is consistent with zeroing uninitialized variables,
so you may have to be careful about this. Use of uninitialized variables
is bad programming practice, in my view.
See PRMs I-709, 5a-114
screensize = \()
local !,r in swi
r[0] = -1 -- current mode
local read = \(x)
r[1] = x
! "OS_ReadModeVariable"
=> r[2]
end -- function
local Xmax = (1+read(11))<<read(4)
local Ymax = (1+read(12))<<read(5)
=> Xmax,Ymax --[[
screen size in coordinates]]
end -- function
Note the use of the phrase
local !,r in swi which helps to make the code less verbose and more
efficient.
Here is a program to put a window on the screen. It is
really pretty much a transcription of a Basic program to do
the same thing. I put it here to show its tedious detail. In
the next chapter we will look at RiscLua's wimp library and
see how to avoid the detail.
#! lua
do
local r,! in swi
-- read screensize
r[0] = -1 -- current mode
local read = function (x)
r[1] = x
!(0x35)
return r[2]
end
local Xmax = (1+read(11))<<read(4)
local Ymax = (1+read(12))<<read(5)
local pad = \(s,n,c) --[[
pad string s to length n with code c]]
local len = #s
if (len > n) then
=> s:sub(1,n)
else
=> s .. (string.char(c)):rep(n-len)
end -- if
end -- function
local title = "Window"
local titleb = block.new(title)
local w,h = Xmax/2,Ymax/2
-- create wimp task
local version = 310
local TASK = 0x4b534154
local b = block.new(64)
r[0] = version;r[1] = TASK;r[2] = titleb;r[3] = 0
assert(! "Wimp_Initialise","initialisation") --[[
PRM 3-87]]
local task = r[1]
local mask = 0x3801
local closedown = \ ()
r[0] = task;
r[1] = TASK;
! "Wimp_CloseDown" -- PRM 3-175
end -- function
-- try
local try = newtry(closedown)
-- report
local report = \ (b,s)
b[0] = 1234; -- errnum
b(4,s)
r[0] = b;r[1] = 1;r[2] = titleb
try(!(0x400df)) -- PRM 3-179
end -- function
-- window
do
local winflags,titleflags = 0x87000002,0x2700603d
local workflags = 3<<12
b[1] = w/2;b[2] = h/2;b[3] = b[1]+w;b[4] = b[2]+h
b[5] = 0;b[6] = 0;b[7] = -1;b[8] = winflags
b[9] = 0x00070207;b[10] = 0xc0103
b[11] = 0;b[12] = -h;b[13] = w;b[14] = 0
b[15] = titleflags;b[16] = workflags
b[17] = 1;b[18] = 0x10001
b(76,pad(title,12,0))
b[22] = 0
r[0] = 0;r[1] = b+4;try(! "Wimp_CreateWindow") --[[
PRM 3-89]]
end -- do
local win = r[0]
b[0] = win;r[0] = 0;r[1] = b
try(! "Wimp_OpenWindow") -- PRM 3-112
r[0] = win;r[1] = 0;r[2] = -h;r[3] = w;r[4] = 0
try(!(0x400d1)) -- Force redraw later PRM 3-150
-- draw
local plot = \ (k,x,y)
r[0] = k;r[1] = x;r[2] = y;try(! "OS_Plot")
end -- function
local circle = \ (x,y,r)
plot(4,x,y);plot(153,r,0)
end -- function
local colour = \ (c)
r[0] = c; ! "Wimp_SetColour"
end -- function
local draw = \ (ox,oy)
local white,blue = 0,8
colour(blue)
circle(ox+w/2,oy-h/2,3*h/8)
circle(ox+13*w/16,oy-3*h/16,h/8)
colour(white)
circle(ox+10*w/16,oy-3*h/8,h/8)
end -- function
-- handlers
local handler = {}
handler[1] = \ (b) -- Redraw
local ox,oy
r[1] = b
try(!(0x400c8)) -- PRM 3-129
ox,oy = b[1]-b[5],b[4]-b[6]
while r[0] ~= 0 do
if draw then draw(ox,oy) end -- if
r[1] = b;!(0x400ca) -- PRM 3-133
end
end -- function
handler[2] = \ (b)
r[0] = 0;r[1] = b;try(! "Wimp_OpenWindow")
end -- function
handler[3] = \ (b)
r[0] = 0;r[1] = b;try(! "Wimp_CloseWindow")
=> true
end -- function -- quit
handler[6] = \ (b)
report (b,"Lua logo")
end -- function
local mesghandler = \ (b)
if b[4] == 0 then => true end -- if
end -- function
handler[17] = mesghandler
handler[18] = mesghandler
-- polling loop
repeat
r[0] = mask;r[1] = b;try(! "Wimp_Poll") --[[
PRM 3-115]]
local reason = r[0]
local action = handler[reason]
if action then
quit = action(b)
end -- if
until quit
closedown()
end -- do
The most interesting part is the last - the polling loop.
The action of the program is defined by a table of handler
functions, indexed by the reason code returned from
Wimp_Poll. No CASE statements are required. The polling
loop continues until a handler returns a non-nil result.