"the elegance of shell scripts with the power of BASIC"
The YakScript language interpreter is written entirely in Java. All source code has been released as open source under the GPL. The rationale behind the design and construction of YakScript can be found in a separate document.
YakScript divides the creation of a menuing system into three layers. The first, fundamental layer are the scenarios scripts of system, which detail how the system responds to each input. This script logic is written in YakScript into a static text file. The second layer is the YakScript engine, which interprets the scripts, receives user input, and issues display update requests. The third layer is the user interface, whose central component is the display.
All layers are separate. It is thus possible to use the system simply by writing scripts (Layer 1), but the interpreter itself can also be extended (Layer 2) and a variety of different user interfaces can be created (Layer 3).
frame.title=title text frame.init=action; action; action... frame.action= option {action; action; action...} option {action; action; action...} ...
The following tags, on the other hand, do not work at time of writing:
<B>, <I>, <U>, <FONT color=color>, <FONT size=size>, <P>, <P align=align>
These and any other unknown tags will simply be ignored.
<BR>, <IMG>, <TABLE>
but if it consists only of string1, then it is tested for equality with the return value $RC.
string1 operator string2 : keywords...
Currently only string equals (==) and not-equals (!=) operations are permitted. Conditions can be chained together with the AND (&&) and OR (||) operations, which are evaluated from left to right. Attempts to use undefined operators will throw a SyntaxErrorException.
Set X apple ; Set Y orange ; Set Z apple ; $X == $Y : Debug This condition is false ; $X == $Z : Debug This condition is true ; $X != $Y && $Y != $Z : Debug This condition is true ;
The first is a syntax error, since conditions must be in the form arg1 op arg2. The second uses quotes to join "golden" and "delicious", which will pass the verifier, but which may still fail if the variable $apple has several words. The third version adds defensive quoting to the variable's result as well and will thus always work.
$apple == golden delicious : Goto yum ; # Syntax error $apple == "golden delicious" : Goto yum ; # Possible error "$apple" == "golden delicious" : Goto yum ; # Correct!
Note that quoting is done after variable substitution and that the quote marks are stripped out from the final output.
In addition to the structure provided by menus, YakScript also provides support for triggers, which can be used to pipe external input into the system. A trigger is a keyword-action pair like this:
So when a trigger with the identifier keyword is activated, the corresponding action is performed. Which triggers are active where are defined with a line like this:
trigger-frame.keyword = action; action; action...
Once activated, a trigger set stays active until overridden by a new set. New triggers in a subroutine only last until Resume is called. Setting frame.trigger to empty removes all triggers.
frame.trigger=trigger-frame1 trigger-frame2...
Triggers are executed without entering the trigger frame, so $0 corresponds to the frame the trigger was activated in and $1 onwards are frame's arguments. Trigger actions may contain Goto and Sub statements.
Name | Description |
$RC | The return value of the last executed keyword (eg. Pause or Sub). The actual value returned depends on the keyword. |
$0 | The name of the previous frame. Set whenever the frame changes; equal to "" if the script has just been loaded and there is no previous frame. |
$N | Arguments given to a Goto or Sub command, where N starts at 1 for the first argument. |
Name | Description | Default contents |
_ABORT | Invoked when the ABORT button is pressed. | Sub _ABORT |
_BACK | Invoked when the CANCEL button is pressed. | Goto $0 |
_INIT | Invoked when the frame is entered. | The contents of frame's .init action, if any |
These can be overridden on a frame-by-frame basis by the script programmer, although caution is advised if doing so.
Name | Description | Default contents |
_ABORT | The default subroutine invoked by the _ABORT option. | _ABORT.init = Pause Really abort?<P><P> <FONT color=green>SELECT</FONT> to abort,<P> <FONT color=red>CANCEL</FONT> to keep going ; Blank; FALSE : Resume ; Load index.script |
A custom abort routine can be created by defining your own _ABORT frame. Note that this can be called from anywhere, so the frame should be a subroutine (ie. canceling invokes Resume and allows execution to continue), and a "successful" abort must either Exit or Load a new script -- which may be equal to the current script -- to ensure a consistent state.
condition = [string1 [cond-operator string2]]
command = [condition :] keyword [arguments]
action = command [; command [; command ...]]
option = string
menu = option (action) [option (action) [option (action)...]]
Keywords are not case-sensitive, but everything else, including variables, is. Whitespace is C-style, ie. anything goes, except that continued lines have to be indicated with "\" (a Java property feature). Trailing and preceding whitespace is removed entirely. Using spaces around ":" and ";" is optional but recommended for legibility. Parentheses "()" and curly braces "{}" are entirely interchangeable.
Note: Horrible or at least unpredictable things will happen if you attempt to use any of the special characters :;\(){} inside menus. No escaping mechanism exists (yet).
Property | Command-line argument | Description |
yakscript.actionmap = <action-map> | -a <action-map> | Allows the user to specify a different property file detailing keyword mappings. Defaults to yakscript/action/action.properties. |
yakscript.debug = {yes,no} | -d | Print script debugging output. Defaults to no. |
yakscript.verify = <level> | -w <level> | Script verification level. Defaults to 1. Level 0: No verification. Errors trapped, script continues. Level 1: Simple verification. Errors cause script to abort. Level 2: Strict verification. Errors cause script to abort. |
yakscript.script.default = <script> | <script> | Specifies the script to start with. Defaults to index.script. (Note that the directory specified by yakscript.script.dir is prefixed to the script's name.) |
yakscript.script.dir = <directory> | - | The directory for scripts. Defaults to yakkey/scripts/. |
yakscript.script.init = <frame> | -g <frame> | Specifies an alternative starting frame. Defaults to script (ie. script.init will be executed immediately). |
yakscript.sound.dir = <directory> | - | The directory for sound samples; prefixed to the sample name whenever Play is invoked. Defaults to yakkey/snd. |
yakscript.sound.format = <format-suffix> | - | The default suffix for sound samples; suffixed to the sample name whenever Play is invoked. Defaults to .wav. |
UML representation of YakScript's internal structure
To add a new keyword to YakScript, simply write a new class that extends yakscript.Action and implements the abstract functions boolean exec(Stack stack) and String getKeyword(void). You may also wish to override some of the default functionality, see the javadocs for yakscript.Action for details.
Once your class is complete, you can add it to the default keyword-class mappings (actionmap) in the file yakscript/action/action.properties, or you can manually instruct YakScript to use a different mapping with the -a toggle. That's it, now use it in your scripts!
Two user interface examples are provided with the system:
More importantly, once a copy of YakScript has been instantiated, a Display class will almost certainly want to query YakScript for the contents of the current frame with the methods getTitle() and getOptions(). Generally, YakScript will invoke DisplayInterface.refresh() when the frame changes, so it is the Display's duty to keep itself up to date by calling the methods again and updating its own state.
Currently only one Display at a time is supported. This may change if there is demand, but non-visual feedback is probably better implemented as customized actions.
Note that any actions invoked by a Listener must refer to the current frame.
Note that Triggers are "above" normal frames and may be invoked anywhere, at any time.