YakScript V1.0 (beta)
Language Specification

$Id: yakscript.html,v 1.10 2001/06/20 06:55:02 jani Exp $

"the elegance of shell scripts with the power of BASIC"

Introduction

YakScript is a simple interpreted scripting language designed for providing the logic for interactive systems. While the built-in feature set is sparse (if sufficient for most applications), what sets YakScript apart from most other scripting languages is that it has been designed from the ground up to allow multiple interfaces into the system and to be extended very easily.

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.


Overall design

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).


Layer 1: Writing Script Logic

Syntax

YakScript scripts are Java property files, so standard Java conventions for commenting, whitespace, etc apply. (The significant exception is that line continuation is implicitly assumed, so using the backslash (\) is optional.) Aside from standard property file conventions, the following characters have special meanings in YakScript (and are discussed in detail later):

Frames

The basic unit of YakScript is the frame. A single frame has the following format:
frame.title=title text
frame.init=action; action; action...
frame.action=
option   {action; action; action...}
option   {action; action; action...}
...

Titles and Options

Titles and options are static pieces of text used to construct the display. While YakScript makes no guarantees about the capabilities of the display, if using Yakkey the strings may contain HTML. The extent of HTML support depends on the Java implementation in use, but the following tags should be safe:
<B>, <I>, <U>, <FONT color=color>, <FONT size=size>, <P>, <P align=align>
The following tags, on the other hand, do not work at time of writing:
<BR>, <IMG>, <TABLE>
These and any other unknown tags will simply be ignored.

Action Keywords

Actions are the logic component of YakScript: program logic, responses to user input, speech output etc is handled in actions. Actions are fundamentally reactive and are triggered by selecting the corresponding option. One important exception is the .init action, which is run when the frame is first entered, before the display is updated.

Actions are built up from keywords and their arguments. Detailed documentation for all YakScript keywords is maintained as automatically generated Javadocs.

Control Keywords

Input/Output Keywords

Miscellaneous

Conditions

Actions may be prefixed by a condition ending in a colon (:). An action with a condition is executed only if the condition evaluates as true. A condition is normally in the format
string1 operator string2 : keywords...
but if it consists only of string1, then it is tested for equality with the return value $RC.

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 ; 

Variables

Any alphanumeric string prefixed with "$", found anywhere in a script (titles, options, conditions, actions) is considered a variable and YakScript will attempt to substitute its value before evaluating/displaying the statement. Non-system variables must be initialized before use with the Set command; an attempt to use an undefined variable will result in an UndefinedVariableException.

Quoting

Quote characters (") can be used to combine strings as one argument. Consider the following three statements:
$apple == golden delicious     : Goto yum ; # Syntax error
$apple == "golden delicious"   : Goto yum ; # Possible error
"$apple" == "golden delicious" : Goto yum ; # Correct!
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.

Note that quoting is done after variable substitution and that the quote marks are stripped out from the final output.

Triggers

(Triggers are not fully supported in the 1.0 release of YakScript.)

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:

trigger-frame.keyword = action; action; action...
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:
frame.trigger=trigger-frame1 trigger-frame2...
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.

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.

System Data

System Variables

The following variables are maintained by the system, and any user modifications to them will be overwritten.

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.

System Options

The YakScript interpreter automatically adds the following options to all frames:

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.

System Frames

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.

YakScript Pseudo-BNF

cond-operator = { ==, !=, &&, || }

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).

Actual Examples


Layer 2: Configuring and Extending the Interactive Interpreter

Configuring YakScript

YakScript is not a stand-alone program, so it must be instantiated from another program. YakScript reads its configuration from the property file yakscript.properties in the base of the class path. A YakScript front-end can also choose to feed its command line arguments to the YakScript method register(), which will extract and parse any relevant options.

Command-line arguments

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.

The YakScript verifier

By default, the verifier is run for each script before it is executed. It can catch the following mistakes: The following mistakes are not caught:

Internal structure

UML representation of YakScript's internal structure

Internally, YakScript is built from classes representing single actions. All actions extend the abstract base class Action, the class ActionFactory generates Actions from text strings. Basic syntax parsing (according to the rules outlined earlier) is contained within the class YakScript. YakScript also contains a stack of FrameStates and a pointer to a class that implements YakScriptInterface, presumably Yakkey, used for callbacks that require user interface updating.

Adding functionality

The code for each keyword is located in the yakkey.action package, eg. the class for Play is yakkey.action.Play.

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!


Layer 3: Writing the User Interface

YakScripts provides support for three user interface components: Displays, Listeners and Triggers. The difference between Listeners and Triggers is subtle and is more a reflection of how YakScript handles them than any fundamental differences between input devices.

Two user interface examples are provided with the system:

Displays

Instantiating YakScript requires a script file and an instance of a class that implements yakscript.DisplayInterface. The interface is quite simple and consists only methods for requesting user notifications and refreshing the screen, it is even possible (but probably not desirable) to define all methods as empty.

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.

Listeners

More often than not, Listeners will be integrated into the Display. In a typical case, the Display will consist of buttons than listen to mouse events. Selecting a button will call yakscript.doAction(), after which YakScript will process the action and (probably) call refresh() when finished.

Note that any actions invoked by a Listener must refer to the current frame.

Triggers

And also more often than not, Triggers will be separate threads, monitoring for events outside the main action-display interface. Triggers can be used as timers, for speech recognition, incoming message notifications, anything at all that goes "outside" the normal menu structure. The Trigger class will invoke yakscript.doTrigger() to start the processing.

Note that Triggers are "above" normal frames and may be invoked anywhere, at any time.