Interrupt routines in RFO-BASIC! let a program break out of a strict sequence of operations. At the end of the execution of a BASIC statement, if certain events occur in the outside world (such as the pressing of a key), BASIC checks to see if the program has an interrupt routine to handle that event. If so, BASIC remembers where it was and effectively performs a GOSUB to that interrupt routine. The routine takes action in response to the event and then returns to the point in the program that was interrupted, by executing a "resume" statement that matches the event that occurred.
An interrupt routine consists of the code starting with a built-in label, through the matching resume statement, such as:
ONBACKKEY: <BASIC statements> BACKKEY.RESUME
A BASIC program can contain only one interrupt routine of each type. Violating this rule would define the starting label multiple times. The routine itself can be conditionally coded to react to the event in multiple ways.
The starting label is predefined. To create an interrupt routine, you must use this label as shown. (You can use lowercase letters, but the formatter will capitalize it.) The resume statements are BASIC keywords and the formatter will capitalize them too.
Generally, interrupt routines should set a variable for the main program to sense, rather than perform extensive or time-consuming operations such as request user input. If another event occurs while the program is in the interrupt routine (before it has executed the corresponding resume statement), then BASIC remembers the event but does not interrupt the interrupt routine. Once the interrupt routine resumes normal operation, BASIC interrupts normal operation again, transferring control to the same or another interrupt routine, depending on what kind of event occurred.
BASIC interrupts do not correspond to hardware interrupts in the Android device. They do not actually interrupt the execution of a BASIC statement. The BASIC interpreter checks for a variety of external events after executing each statement. Unlike most processors, BASIC interrupts have no priority over one another. If external events occur that would trigger several interrupts, the sequence in which the interrupt routines receive control is undefined.
Notably, an interrupt cannot curtail the execution of the PAUSE statement. If a program's next scheduled activity is in 5 seconds, a BASIC programmer may write a loop to PAUSE for 100 msec up to fifty times, so that the program is more responsive to events such as keystrokes.
- Caution — RESUME and program control
Interrupt routines are not ordinary GOSUB subroutines. If you do not use the designated resume statement to return to the interrupted code, but use RETURN, then you have not officially exited the interrupt routine. This means the program will not receive any more interrupts. If an interrupt routine responds to the event by ending program execution (using the EXIT statement), then it need not have a resume statement. Conversely, it is an error to execute one of the resume statements at any time except to return from the corresponding interrupt handler.
Resume statements will return from an interrupt, but they might not cleanly exit other loops that the interrupt handler has started. To use the keyboard interrupt as an example:
ONKEYPRESS: DO INKEY$ K$ IF K$ = "@" THEN KEY.RESUME ... UNTIL 0 KEY.RESUME
KEY.RESUME in orange returns from the interrupt handler, but does not clear away the
DO/UNTIL loop. If the main code next executes an
UNTIL statement, control will be sent back inside the interrupt handler! The next execution of
KEY.RESUME will be a run-time error because there is now no active interrupt. Instead of this
KEY.RESUME, break out of the
DO/UNTIL loop (with
D_U.BREAK) and resume using the statement shown in green.
- Caution — Variable scope of user-defined routines
User-defined functions operate with their own private variable scope: Variables can be defined in functions with the same name as, and independent of, variables defined elsewhere in the program. However, this is not true if a user-defined function is called within an interrupt routine. User-defined functions § Variable scope explains the implications of this, and the facilities added by private versions of RFO-BASIC! to work around it.
- Error trapping
Most run-time errors can be trapped. This results in a jump to the
OnError: label, if you coded one. It looks like an interrupt, but is independent of interrupts.
Background transition interrupt
This interrupt is triggered on a transition of the background state; that is, when the BASIC program loses or gains control of the screen. The interrupt handler takes this form:
ONBACKGROUND: <BASIC statements> BACKGROUND.RESUME
The interrupt handler can use BACKGROUND() to determine what happened:
- The function returns true (1) if the program is running in the background.
- The function returns false (0) if the program is running in the foreground.
A program loses the screen and goes into the background state when the user presses the HOME key or the program itself executes the HOME statement. This triggers a background transition interrupt. In addition, one background transition event occurs when the BASIC program initially gains control of the screen.
A program can give text output, such as using PRINT, even if it does not own the screen. However, the program cannot render graphics without owning the screen. The program can continue to execute GR. statements to build a display; it simply cannot render it. Programs may check that they own the screen before executing GR.RENDER:
IF !BACKGROUND() THEN GR.RENDER
A program that runs continually to refresh the screen might exit (or at least suspend its use of power-consuming resources such as WiFi and GPS) if it no longer owns the screen. On the other hand, a music player program might continue playing music despite not owning the screen.
This interrupt is triggered when a message has arrived at the Android device over the Bluetooth connection (that is, in a situation where BT.READ.READY() would return a nonzero value). The interrupt handler takes this form:
ONBTREADREADY: <BASIC statements> BT.ONREADREADY.RESUME
If the Android device has been paired over Bluetooth with another device and the other device has sent a message, then the BASIC program can use this interrupt handler to sense its arrival. The advantage of using an interrupt handler is that the program can be doing other things than stalling to wait for the message to arrive.
The interrupt handle should read and handle the message (perhaps store it in a variable and signal to the mainline that new information is present) and then use BT.ONREADREADY.RESUME to return to normal activities until receipt of the next message.
This interrupt is triggered when Android issues the "low memory" warning. The interrupt handler takes this form:
ONLOWMEMORY: <BASIC statements> LOWMEMORY.RESUME
When Android runs out of available internal memory, it first broadcasts its "low memory" warning to all apps. A BASIC program that does not have a low memory interrupt handler will print "Warning: Low Memory" on the console. Eventually, Android may shut down apps running in the background.
The interrupt handler could release or discard resources, or could try to exit gracefully.
A variety of run-time errors can be trapped. A trap is not really an interrupt in response to an event occurring asynchronously to the BASIC program, but a trap sends control out of sequence as an interrupt does. The trap handler begins with a predefined label:
ONERROR: <BASIC statements>
The error trap has different rules from real interrupts:
- There is no matching resume statement, nor does BASIC have a statement to dismiss the trap.
- There is no way to resume execution at the point of the trap. Interrupts occur between BASIC statements, but a trap may occur during the execution of a single statement, and BASIC has no guaranteed way to complete the statement sensibly.
Traps and interrupts are separate. Traps neither lock out interrupts, nor are locked out by them:
- Interrupts are not disabled during the trap handler (because you never declare that it is complete).
- Error traps can occur within an interrupt handler, if the handler should commit a trappable error.
- Errors can be trapped within the trap handler. A common result is an infinite loop.
If a trappable error occurs and the BASIC program has no trap handler, then BASIC prints an informational message on the console and stops running.
Many of the demonstration programs in
Sample_Directory avoid error messages by ending with an empty error trap handler:
Though the trap handler cannot easily return to, or resume at, the point of the error, it may be able to jump to a sensible point in the program. For example, if the error is a failure to obtain a web page, the trap handler may use a counter to simply re-try the operation a certain number of times, and might modify variables (for example, use a robust server rather than the fastest one) to increase the likelihood of success.
The safest way for the trap handler to try to resume the program is to use GETERROR$() to discover what error occurred, and use local variables to discern which exact statement failed. Take proper action to abort that operation, then GOTO a portion of the program that will again set up variables for, and again execute, that operation. If there is any chance that the error occurred within an interrupt handler, use variables to determine whether this is the case; if so, the trap handler should either GOTO the inside of the same interrupt handler, so that it can dismiss the interrupt, or execute the corresponding resume statement itself to end the interrupt.
Write the error handler after the rest of the program is substantially complete and debugged, because an error handler will make it harder to locate and correct errors elsewhere. The first version of the program with an error trap should use GETERROR$() to handle only one type of error, keeping in mind that all other run-time errors will jump to the same error trap. In these cases for which a solution is not yet devised, the error trap should execute the following statements to give the result as though there were no error trap:
PRINT GETERROR$() END
This interrupt is triggered when the user presses a keyboard key. The keyboard may be any of the following:
- A soft keyboard displayed on the screen
- A full typing keyboard, such as a Bluetooth keyboard that is paired with the Android device
- A set of buttons, on a flip-phone.
The BACK key, and any MENU key that may be present, cause different interrupts, as discussed below. In addition, certain Android devices contain keys that are sensed directly by Android and cannot be detected by the BASIC program.
The keyboard interrupt handler takes the following form:
ONKEYPRESS: <BASIC statements> KEY.RESUME
Each pressing of an applicable key causes one keyboard interrupt. The interrupt handler can execute the INKEY$ statement to determine which key was pressed, and take appropriate action (including ignoring the keystroke) before resuming normal operation.
If the user should press another key while the program is in the interrupt handler, BASIC buffers the keystroke. After the interrupt handler returns to the calling program with KEY.RESUME, BASIC sends control back to the interrupt handler to service that next keystroke. However, if the program executes a PAUSE statement that specifies a large number of microseconds, BASIC is disabled for this duration and several keys can be pressed and buffered before the interrupt handler receives control. In this case, the handler should loop to process as many keys as have been pressed:
ONKEYPRESS: DO INKEY$ PressedKey$ IF PressedKey$ = "@" THEN D_U.BREAK <BASIC statements> UNTIL 0 KEY.RESUME
This interrupt is triggered when the user presses the BACK key. This key has a triangle pointing to the left. It may be a soft key on the screen (in a line of geometric shapes at the top or bottom of the screen). On a flip-phone, it may be a physical button. The interrupt handler takes this form:
ONBACKKEY: <BASIC statements> BACK.RESUME
The interrupt handler is the only way a BASIC program can sense the pressing of the BACK key. The interrupt handler does not have to include statements to determine whether the BACK key was pressed, as that is the only reason to execute the interrupt handler.
If there is no BACK key interrupt handler, then pressing BACK halts the execution of the program.
This interrupt is triggered when the user presses the MENU key. The interrupt handler takes this form:
ONMENUKEY: <BASIC statements> MENUKEY.RESUME
The interrupt handler receives control when the MENU key is pressed. If the device has no MENU key, the interrupt handler never receives control. Android devices since Honeycomb typically do not have a MENU key.
This interrupt is triggered only when the BASIC program has executed the TIMER.SET statement to call for an interrupt a specified number of milliseconds later. The interrupt handler takes this form:
ONTIMER: <BASIC statements> TIMER.RESUME
The interrupt handler performs actions the programmer wants to happen periodically. If the timer is used for a one-shot interval measurement, the interrupt handler should execute TIMER.CLEAR. Otherwise, BASIC will mark the same interval again and call the interrupt handler again at the end of that interval. The interrupt handler could call TIMER.SET to specify a different interval before the next interrupt.
If the console is displayed on the screen and the program has PRINTed one or more lines, the program can sense a touch of any of those lines with the console touch interrupt. The handler takes this form:
ONCONSOLETOUCH: <BASIC statements> CONSOLETOUCH.RESUME
When graphics are instead displayed, a console touch interrupt cannot occur.
The interrupt routine can execute the CONSOLE.LINE.TOUCHED statement to determine which line was touched, and optionally, whether it was a short touch or a long touch. A BASIC program could do this without an interrupt routine, but it is easy to get multiple indications of a single touch. The console interrupt senses the transition and calls the interrupt routine only once. An interrupt routine might simply set a variable. The mainline would test the variable, perform the appropriate action, and reset the variable to 0. Such an interrupt routine might look like this:
ONCONSOLETOUCH: CONSOLE.LINE.TOUCHED LineTouched CONSOLETOUCH.RESUME
An interrupt routine could contain more complicated logic, such as declining to give the mainline an indication of the touch if the mainline has declared that that line is disabled.
Often, a BASIC program uses the console as a menu to PRINT a series of user choices. Sensing which line was touched indicates the choice the user made.
Sample_Programs/f35_bluetooth.bas, supplied with the RFO-BASIC! installation, uses a console touch interrupt.
If BASIC graphics are displayed on the screen and the user touches the screen, the program can sense this touch with a graphic touch interrupt. The handler takes this form:
ONGRTOUCH: <BASIC statements> GR.ONGRTOUCH.RESUME
When the console is instead displayed, a graphic touch interrupt cannot occur. As long as the finger remains on the screen, another graphic touch interrupt does not occur.
There are several statements at GR.TOUCH and GR.TOUCH2 that let the program determine what point on the screen is being touched. The program can follow the finger to identify gestures such as drag-and-drop, and can sense the touch of a second finger to identify gestures such as "pinch" to zoom out of an image. The BASIC program can do this without an interrupt routine, but using an interrupt routine may simplify multiprocessing and may let the program PAUSE to conserve the battery.
- De Re BASIC! (v1.91), pages 62-63 (BACKGROUND()), page 82 (labels), page 88-91 (various interrupts), page 129 (Bluetooth), pages 133-134 (timer), page 168 (OnGrTouch).
- Forum 5401: RESUME statement after THEN
|BASIC features||Audio player • Bluetooth • Data structures • Graphics • HTML • Interrupt routines • Sockets • SQL • User-defined functions|
|Programming notes||Coexisting with Android|