Plua 2.0

Back to the main Plua Revisited page

1. Introduction

Plua 2.0 is a port of Lua 5.0.3 (plus a small IDE) for the Palm Computing platform. Lua is a programming language designed at TeCGraf, the Computer Graphics Technology Group of PUC-Rio, Brazil.

Plua 2.0 requires PalmOS 3.5 or greater.

More information on Lua can be found here.
More information on Plua can be found here.
There is a Plua discussion group here (registration required).

THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY, SO USE IT AT YOUR OWN RISK.

Plua is Copyright (C) Marcio Migueletto de Andrade.

2. Operation

Plua can be used in two different modes. The first one provides a quick read-eval-print loop. In this mode you can type a program and have it evaluated immediatelly. You type a program in the lower half of the screen and the results are printed in the upper half. To evaluate the current program, tap the Run button. To clear the current program, tap the Clear button.

For example, if you type in the text field this program:

print("Result = ", 2*3)
Plua will print the following in the result area:
Result =   6
The second mode also allows you to run Lua programs, but with two differences:

Tap the File button to access the file selection screen. On the top right corner you can choose one of the four file types (Memo, Doc, Stream or Card). A list of current available programs of the selected type is displayed.

If you create Memo with Lua code, it must start with "-- " (without the quotes and with a trailing space) and then the program name (usually a single word) ended with ".lua", otherwise it will not be visible in Plua. Doc, Stream and VFS files must also end in ".lua".

Select one program on the list and tap the Run button to run it. Plua will switch to a full screen mode and run the program. When it is finished Plua will return to the program selection screen. If you want to stop a program before it is finished tap the Applications icon. Plua looks for VFS files in the /PALM/Programs/Plua/src directory on your expansion card.

The button Compile is used to compile a program into a PRC file. The generated PRC is a standalone application that can be accessed like any other one in the Application Launcher. Note however that Plua or PluaRT (the Plua runtime) needs to be installed. You do not need to install both if you just want to run Plua applications. If neither Plua nor the runtime is installed and a compiled program is run, an error message will be displayed and the program will be aborted. Generated PRCs have a fixed pre-defined icon. Note that it is not necessary to compile a program in order to run it. If you just to want to run a program from within Plua, you can use the Run button directly. The Compile button should be used only when you want to generate a standalone PRC with your program.

Tap the Main button to return to the main screen. For Memos you have two additional buttons: New and Edit, to create and edit Memos respectively. For Docs, there is also a Edit button. If you tap this button Plua will open the third-party application SrcEdit to edit your doc file. If this application is not installed nothing will happen.

Preferences are accessible via Menu/Preferences in the main screen. If "Read-only mode" is checked, all attempts to open a database for writing or removing a database will be aborted, and an error message will be displayed. If "Clear output" is checked, the Clear button also clears the output area.

The Plua online help system is accessible via the PalmOS standard "Find" button. If you tap Find while in the main window or editing a memo, Plua will open a dialog showing all function names. If you select one function name and tap Goto again Plua will show the function reference. If you tap the Index button, all function names will be shown again. Tap Done to close the help dialog. While in the main window or editing a memo, before tapping Find you may first select a word, in which case the help dialog will open directly in the function reference. In order to use the help system, you must first install pluahelp.prc.

3. Plua compared to Lua

A great effort was put on porting the complete Lua package. Some portions of the source code were kept almost intact, while others have been heavily modified in order to fit the PalmOS architecture. There are a few limitations when compared to the standard Lua distribution: Plua also offers many extensions to the standard Lua distribution. In addition to the functions of standard Lua libraries, Plua provides additional functions to access PalmOS specific features. In the following function prototypes, optional parameters are denoted by square brackets.

Database I/O functions:

Misc I/O functions: VFS directory functions: Resource functions: Screen functions: Offscreen buffer functions: Sprite functions: GUI functions: Sound functions: Bitwise operator functions: Binary data functions: Misc OS functions: Built-in constants:

4. A small tutorial

This section assumes that you are already familiar with the Lua language, and focuses on issues related to the Palm platform. If you don't know the language Lua, don't worry. There is a lot of documentation on its home page. If you really want to start programming right away without reading the docs, here are a few tips:

4.1. The display

Plua handles your device screen as a bitmapped display where individual pixels can be addressed. The "stdout" is also mapped to the display, so when you use print the characters are written to the display. Plua stores the current cursor position in a pair of numbers (x,y). This position is updated whenever you write to the display.

The statement

screen.line(0,0,19,19)
will draw a line from position (0,0) to (19,19) and will update the current cursor position to (19,19). If the next statement is
write("abc")
the string "abc" will be printed starting at position (19,19) and the cursor will be placed at (19,19+d), where d is the width in pixels of string "abc" written with the current font.

Besides the cursor position, Plua also stores the current foregroung color, the current background color and the current font. The following example writes the string "Plua" in red over black with the bold font:

screen.color(screen.rgb(255,0,0), screen.rgb(0,0,0))
screen.font(1) print("Plua")
Plua works with 1-bit monochromatic displays, 2-bit or 4-bit grayscale displays and with 8-bit color displays. The foreground and background colors are mapped to the closest grayscale tone in your device, if color is not available. You can find the display properties of your device by calling the screen.mode() function:
width, height, depth, hasColor = screen.mode()
print(width, height, depth, hasColor)
Width and height are the display dimensions in pixels. Depth is 1, 2, 4, or 8, and hasColor is 1 if the display supports color, or 0 otherwise. On color devices, you can use the screen.rgb() function to get the index of a color given its red, green and blue components. The example below will print 125 on 8-bit color device using the standard color pallete.
red = screen.rgb(255,0,0)
print(red)
Plua supports a Logo-like "turtle" pointer. Using screen.walk() and screen.turn() you can walk around the display drawing lines. The example below clears the display, positions the cursor at the middle of the display and draws a small expiral.
screen.clear() w,h = screen.mode() screen.moveto(w/2,h/2)
for d = 1,20,1 do
  screen.walk(d) screen.turn(math.rad(-40))
end
One final note on the display: writing at the end of a line does not make it to wrap, and writing at bottom line does not make the display to scroll up.

4.2. Bitmaps

Plua supports bitmaps in two different formats: PalmOS native Bitmap format and Windows BMP format. This section shows how you open and display a bitmap centered on the screen. In the following examples, suppose you have a Windows BMP file named "picture.bmp", your application source code is in the file "MyApp.lua" and you registered the Creator ID "MyCr" for your application.

4.2.1. Using Windows BMP format

This method uses the bitmap is its original Windows BMP format. PalmOS does not understand the BMP format, but Plua does. The only restrictions are: First you need to store the BMP file on your Palm, so that your application can read it. There are three possibilities:

Store the file on a memory card

Using a memory card reader, copy "picture.bmp" to some directory on the memory card, for example "/data/picture.bmp". Then your application can use a code like this to open and display the bitmap:

bmp,bmp_width,bmp_height = buffer.read("vfs0:/data/picture.bmp")
width,height = screen.mode()
buffer.put(bmp, (width-bmp_width)/2, (height-bmp_height)/2)
buffer.free(bmp)
Your application can be compiled onboard with Plua, or on the desktop with the Plua desktop compiler. If you want to deploy your application, you must distribute "MyApp.prc" and a memory card with the "picture.bmp" file, which is not very practical. Because of this, you can use the following variation of this method.

Store the file on a stream file

Using a memory card reader, copy "picture.bmp" to some directory on the memory card, for example "/data/picture.bmp". Then you need to copy the bitmap from the memory card to a stream file (you need to do this only once). In this example, the stream file is named "Picture":

bmp = buffer.read("vfs0:/data/picture.bmp")
buffer.write("Picture", bmp)
buffer.free(bmp)
You have just created a PRC file on your device named "Picture". After this you do not need the file stored on the memory card anymore. Then your application can use the same code as before to open and display the bitmap, except that now it reads the bitmap from the stream file "Picture":
bmp,bmp_width,bmp_height = buffer.read("Picture")
width,height = screen.mode()
buffer.put(bmp, (width-bmp_width)/2, (height-bmp_height)/2)
buffer.free(bmp)
Your application can be compiled onboard with Plua, or on the desktop with the Plua desktop compiler. If you want to deploy your application, you must distribute both "MyApp.prc" and "Picture.prc".

Store the file on a resource

This method stores the BMP file in the same PRC file of your application. You must use the Plua desktop compiler to compile your application. This method works only if the Windows bitmap does not exceed 64K.

On your desktop development environment, copy the "picture.bmp" file to a file named "Wbmp07d0.bin" ("Wbmp" is just an arbitrary resource type, 07d0 is 2000 in hexadecimal, which is the ID we chose for the bitmap resource). You must compile your application using a syntax like this:

plua2c -name MyApp -cid MyCr -o MyApp.prc MyApp.lua Wbmp07d0.bin
You can use this code to open and display the bitmap:
bmp,bmp_width,bmp_height = buffer.read("rsrc:/Wbmp/2000")
width,height = screen.mode()
buffer.put(bmp, (width-bmp_width)/2, (height-bmp_height)/2)
buffer.free(bmp)
If you want to deploy your application, you can distribute only "MyApp.prc".

4.2.2. Using PalmOS Bitmap format

This method converts the bitmap to the native PalmOS format at compile time. You need a third-party resource compiler, and in this example we use PilRC version 3.2. First you need to build a resource file named "Picture.rcp" with the following contents (this example works only for devices with high-density displays):
BITMAPFAMILYEX ID 2000
BEGIN
  BITMAP "Picture.bmp" BPP 8 DENSITY 144
END
You must compile the resource file with PilRC:
pilrc Picture.rcp
A file named "Tbmp07d0" will be created, containing the bitmap converted to PalmOS format. This method works only if the converted bitmap resource does not exceed 64K. You must compile your application using a syntax like this:
plua2c -name MyApp -cid MyCr -o MyApp.prc MyApp.lua Tbmp07d0.bin
You can use this code to open and display the bitmap:
bmp = resource.open("Tbmp", 2000)
bmp_size, bmp_width, bmp_height = resource.size(bmp)
width,height = screen.mode()
screen.moveto((width-bmp_width)/2, (height-bmp_height)/2)
resource.draw(bmp)
resource.close(bmp)

4.3. Sprites

Plua provides an easy to use sprite animation engine. This section describes the sprite definition table, which is the second argument passed to the sprite.add() function.

The sprite definition table has five mandatory fields:

Fields x and y must be updated by your program to move the sprite. Coordinates are relative to the background buffer. The bitmap field is usually set only once at the beginning, but can also be updated during the animation to change the sprite appearance. Since the sprite definition table is a regular Lua table, you can use it to store other fields belonging to your program logic.

4.4. User Interface

The standard PalmOS UI componentes can be easily created and interacted with. The usual way to do this is to create a few components and then enter a loop waiting for UI events. The following example does exactly this:
textField = gui.field(1,20,20)
lengthButton = gui.button("Length") gui.nl()
while true do
  ev,id = gui.event()
  if ev == ctlSelect and id == lengthButton then
    print(string.len(gui.gettext(textField)))
  elseif ev == appStop then
    break
  end
end
The functions gui.field() and gui.button() create a 1-line text field and a button, respectively. The gui.nl() function positions the cursor below the button. The infinite loop calls gui.event(), which makes the application block until an UI event is generated. In our example, the "Length" button will generate the an event (ctlSelect) when it is pressed. gui.event() returns this event ID and the component ID (in this case the ID of the button). The program uses gui.gettext() to retrieve the current contents of the text field and prints its length.

NOTE: since this program enters an infinite loop, the only way to stop it is to catch the appStop event and drop out of the loop.

The example below shows gui.input(), gui.alert() and gui.confirm().

s = gui.input("Write something")
if s ~= nil then
  gui.alert("You wrote "..s)
end

if gui.confirm("Are you tired ?") then
  print("Me too")
else
  print("Me neither")
end
In order to set a menu for your application, you can use gui.menu() as following.
gui.menu{"O:Open", "Preferences", "-", "Q:Quit"}
The menu will have four items: the fixed "About Plua" item, a fixed separator, an "Open" item with "O" as shortcut, a "Preferences" item with no shortcut, a separator, and a "Quit" item with "Q" as shortcut. Limitations: currently gui.menu() will work only on PalmOS 3.5 or greater (earlier versions do not allow dynamic menu configuration), it works only in full-screen mode, and it is not possible to define more than one menu in the same menu bar. gui.menu() can be called any time, and it will replace the current menu with the new one.

When one of the menu items is selected (except the About item), a menuSelect event is returned by gui.event(). In the example above, if "Preferences" was selected, gui.event() would return menuSelect and the number 2, meaning that the second user defined item was selected.

The following table shows all events returned by gui.event(). The general syntax is:

  event,arg1,arg2 = gui.event(timeout)
Note that depending on the event, there can be two arguments, one argument, or none. Note also that if the system event or control has an event handler, gui.event() will not return at all but will call the event handler instead.

Source Event Arguments
A menu item is selected menuSelect Menu item number (starting with 1)
A button is selected ctlSelect Control ID
Button state
A pushbutton is selected ctlSelect Control ID
Pushbutton state (1=selected, 0=unselected)
A checkbox is selected ctlSelect Control ID
Checkbox state (1=selected, 0=unselected)
A selector trigger is selected ctlSelect Control ID
Selector trigger state
A slider is moved ctlSelect Control ID
Slider position (starting with 1)
A repeating button is selected.
Multiple events are sent while the button remains selected
ctlRepeat Control ID
A list item is selected lstSelect Control ID
List item number (starting with 1)
A popup item is selected popSelect Control ID
Popup item number (starting with 1)
A key is pressed/written keyDown Key code
Pen touches the screen (outside of a UI control) penDown X coordinate
Y coordinate
Pen moves on the screen (outside of a UI control) penMove X coordinate
Y coordinate
Pen leaves the screen (outside of a UI control) penUp X coordinate
Y coordinate
gui.event(n) timeout expires nilEvent None
gui.event(n) exits because of pending I/O ioPending None
A sample started with sound.play() finishes sampleStop Slot number (1 to 3)
User exits the application using the "Home" button() appStop None

The following table lists the fields used to create UI controls with the gui.control() function. This single function can be used to create controls that would be created with gui.label, gui.button, gui.pbutton, gui.rbutton, gui.checkbox, gui.selector, gui.slider, gui.field, gui.list and gui.popup.

Type Field name Field type Meaning Default value
label text string label text empty string
x number top left x position current x position
y number top left y position current y position
font number text font current font
button, pbutton, rbutton, checkbox, selector text string control text empty string
bitmap number bitmap resource id use text if bitmap is not defined
group number group id for pushbuttons 0
state number 0=not selected, 1=selected 0
x number top left x position current x position
y number top left y position current y position
width number control width text/bitmap width plus some spacing
height number control height text/bitmap height plus some spacing
font number text font current font
handler function event handler function do not use an event handler
slider state number initial position (starts at 1) 1
limit number maximum position 10
x number top left x position current x position
y number top left y position current y position
width number slider width 80 pixels
height number slider height calculated slider height plus some spacing
handler function event handler function do not use an event handler
field text string initial text empty string
lines number number of lines, if > 1 field has a scroll bar 1
columns number number of columns 16
limit number maximum characters lines * columns
x number top left x position current x position
y number top left y position current y position
editable boolean if true user can edit text true
underlined boolean if true lines are underlined true
font number text font current font
list list table list items this field is mandatory
lines number number of lines number of items in the list
columns number number of columns 16
selected number initially selected item (starts at 1) 1
x number top left x position current x position
y number top left y position current y position
font number text font current font
handler function event handler function do not use an event handler
popup list table popup items this field is mandatory
selected number initially selected item (starts at 1) 1
x number top left x position current x position
y number top left y position current y position
font number text font current font
handler function event handler function do not use an event handler

The gui.control() function expects a a table as its single argument. The table has a mandatory field named "type", and its value must be one of the values listed in the first column on the table above. Other fields are optional (except where noted), and have default values as defined above. For example, these two forms are equivalent:

gui.button("OK")
gui.control{type="button", text="OK"}
Note that the second form uses the abbreviated way of passing a single table argument to a Lua function, that is, it uses brackets instead of parenthesis. The gui.control function can do everything that gui.label, gui.button, gui.pbutton, gui.rbutton, gui.checkbox, gui.selector, gui.slider, gui.field, gui.list and gui.popup can, althoug using a little longer syntax. Additionally, it can set parameters that the other functions can not, like the width and height of a control:
gui.control{type="button", text="OK", width=30}
All controls except labels and fields can have an event handler. If the event handler is set, it will be automatically called by gui.event() whenever the control is selected. System events also can have event handlers, set with gui.sethandler(). The arguments of the event handler call are the same as the values that would be returned by gui.event() if there was no event handler.

4.5. File I/O

Although PalmOS does not have a true filesystem, Plua provides the Lua virtual machine the illusion that the underlying OS supports file I/O. The "stdout" descriptor is mapped to the display. The "stderr" descriptor is mapped to a dialog box that shows what was printed on stderr.

Regular files are implemented using the File Stream API of PalmOS, so Lua programs can open, create, read from and write to files normaly, without knowing about database types or database creators. The following example shows this:

f = io.open("MyOutput", "w")
f:write("This is being written to a PalmOS stream database")
f:close()
The database MyOutput is open for writting (it is created if it does not exist), a string is written to it, and it is closed. There are also functions that allow a program to manipulate a PalmOS database directly, if desired. They were listed in the section "Extensions to the standard Lua distribution" above. The example below iterates through all MemoPad records and prints the first line of each record:
f,n = io.open("db:/MemoDB", "r")
for i = 0,n-1,1 do
  f:openrec(i)
  s = f:read("*l")
  print(s)
end
f:close()
The I/O functions work with both stream files and regular databases. With regular databases, each record works like a sub-file, that is, they can be read from the begining to the end, when an EOF conditions is signaled. In order to continue to read the database, openrec() must be called to open the next record and so on.

Listed below are the modes available for opening files with io.open().

If a database is beeing created, the creator ID is inherited from Plua and the type is always 'data'. The io.open() function can not create new records inside databases, the only way to create records is with the createrec() function. Database records can not be resized by writing beyond the last byte, the only way to resize records is with the resizerec() function.

Besides streams and databases, Plua also supports access to other I/O facilities, like resources, VFS files, TCP/IP sockets, or any device accessible by the PalmOS New Serial Manager. The following table shows all supported file types, the io.open() syntax and wether each mode is supported.

Type File name syntax r r+ w a Example
Stream database name Yes Yes Yes Yes io.open("MyStream", "a")
VFS file vfsn:/path Yes Yes Yes Yes io.open("vfs0:/Palm/Programs/Readme.txt", "r")
Regular database db:/name Yes Yes Yes No io.open("db:/MyDatabase", "r")
Regular database record db:/name/index Yes Yes Yes No io.open("db:/MyDatabase/5", "r")
Memo database memo:/name Yes Yes Yes No io.open("memo:/Data", "w")
Doc database name Yes No No No io.open("MyDoc", "r")
Compiled Plua library (used by dofile) name Yes No No No io.open("MyLib", "r")
Resource rsrc:/type/id Yes No No No io.open("rsrc:/Data/1000", "r")
TCP socket tcp:/host:port Ignored io.open("tcp:/www.lua.org:80")
UDP socket udp:/host:port Ignored io.open("udp:/host.com:7")
Serial Manager - serial craddle srm:/serial/baud/word Ignored io.open("srm:/serial/9600/8N1")
Serial Manager - USB craddle srm:/usb/baud/word Ignored io.open("srm:/usb/9600/8N1")
Serial Manager - craddle (auto-detect serial or USB) srm:/craddle/baud/word Ignored io.open("srm:/craddle/9600/8N1")
Serial Manager - raw infrared srm:/ir/baud/word Ignored io.open("srm:/ir/9600/8N1")
Serial Manager - IrCOMM (serial over IR) srm:/ircm/baud/word Ignored io.open("srm:/ircm/9600/8N1")
Serial Manager - RFCOMM (serial over Bluetooth) srm:/rfcm/baud/word Ignored io.open("srm:/rfcm/9600/8N1")
Serial Manager - arbitrary device with creator 'abcd' srm:/abcd/baud/word Ignored io.open("srm:/abcd/9600/8N1")
Driver registered with prefix 'xyz' (developed with libkit) xyz:/path Configurable io.open("xyz:/SomePath", "w")

TCP and UDP sockets accept optional parameters after the "host:port" in the file name. You can specify up to three timeout options separated by "/": DNS lookup timeout, connect timeout and linger timeout. In the following example PalmOS will wait up to 5 seconds to resolve the name "host.domain.com" and up to 10 seconds to estabilish a connection to this host:

sock,err = io.open("tcp:/host.domain.com:2000/5/10")
If any of these timeouts is reached, the io.open() call will fail with the appropriate error message. The default value for DNS lookup and connect timeouts is 8 seconds each. The third optional parameter controls lingering on closing the socket. If it is not present, like in the example above, lingering is disabled. The following example turns lingering on and set it to 2 seconds:
sock,err = io.open("tcp:/host.domain.com:2000/5/10/2")
 ...
sock:close()
After the sock:close() call, PalmOS will wait up to 2 seconds for data before shutting down the socket. This timeout does not raise any error.

The standard dofile() function works with Lua source code or compiled applications. If there is a Doc file named "name.lua", for example, it can be included by using dofile("name.lua"). For applications, the compiled lua code can be included by using dofile("appname"), where "appname" is the PRC name.

A note about error handling in file I/O: in case of success, the functions marked as returning true in fact return an userdata value (which is true, since any non-nil value is considered true in Lua). The value of this userdata is not important, it is used just to make it different from false (nil). In case of failure, all I/O functions (except for read) return two additional values besides nil. The second value is a string with the error message and the third value is the numeric error code. The error messages/codes are inspired on the Unix C Library (libc) errors. Currently the following errors may be reported:

For example, suppose open() is used to open a database, like in the following code:
f,n,e = io.open("db:/Test", "r")
If there is a database named Test, f will be assigned a handle to the opened database, n will be assigned the number of records in the database and e will be assigned nil. However, if there is no databse named Test, f will assigned nil, n will be assigned the string "No such file or directory" and e will be assigned 2 (the numeric code for ENOENT). This example illustrates how a function may return different number of values (and possibly of different types) depending on its execution.

The following example shows how to open the serial port and wait for data in a efficient way.

f = io.open("srm:/serial/57600/8N1")
while true do
  ev = gui.event()
  if ev == ioPending then
    s = f:read(8)
    print(s)
  elseif ev == appStop then
    break
  end
end
f:close()
When data is available at the serial port the ioPending event is sent. Note that it is not possible to know how much data is available. The read() function reads at most 8 bytes and returns. If less than 8 bytes are available, read() returns them without blocking. If more than 8 bytes are available, they remaining bytes are hold in the Serial Manager buffer. In the next loop interaction, another io.ioPending event will be sent, and so on.

4.6. Binary data

Most PalmOS applications save persistent data in one or more PDB's. This information is written in binary form, that is, a sequence of bytes usually representing the encoding of a C structure. In Lua, however, information is stored in numbers, strings and tables, and their internal binary representation is not relevant. In order to read and write binary data, Plua provides the functions bin.pack() and bin.unpack(). The following example shows the usage of these functions along with database access functions.

Storing a a table into a PDB record:

example = {25, "Plua", 3.1416, 9999}
data = bin.pack("BSDW", example)
f = io.open("db:/MyBinaryData", "w")
index = f:createrec(string.len(data))
f:openrec(index)
f:write(data)
f:close()

According to the format string "BSDW", the number 25 (the first element) is packed as a byte, the string "Plua" is packed as a null-terminated string, the number 3.1416 is packed as a double and the number 9999 is packed as a 16 bit word. The returned data is a binary string of 1+5+8+2 = 16 bytes. This data is stored as record in the MyBinaryData PDB.

Reading the same record into a table:

f = io.open("db:/MyBinaryData", "r")
f:openrec(index)
data = f:read("*a")
example = bin.unpack("BSDW", data)
f:close()

After this, the returned table will have the same elements as the original one.

4.7. Libraries

You can use compiled Plua applications as libraries. Lets say you have a set of useful Lua functions and you want to make them available to other developers. The obvious way is to distribute the source code, but there is an alternative: place the functions inside a MemoPad record or Doc file and compile it just like a regular application. For example, if the generated PRC is named "MyLib", another application can simply use dofile("MyLib") to load the functions. To distribute your "library", you have to distribute the PRC named "MyLib.prc" that was created when you compiled it.

For a description on how to build C libraries and integrate them into Plua, please look at the libkit examples.