title logo

back contents forward

Chapter 10

blocks

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.

software interrupts

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.

reading the screen size

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.
example: window

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.

back contents forward