This page describes the basic syntax and meaning of an ELVIS source file. For a formal description of the syntax, including an EBNF grammar, see this document (PDF letter; LaTeX source).
This is what a typical ELVIS source file might look like:
/*** Example ELVIS source file *** *********************************/ // That was a multiline comment, delimited by /* and */ // Single-line comments begin with // // Top-level options projectName: Example packageId: com.example.example // Controller declaration %controller { Action btnClicked; // An event handler } // Widget declarations Window win "Example" { // The main window resizable: true; // An attribute ColumnLayout { // A layout widget Label "Please click a button:"; /* You can declare more than one widget at once, and give them all * attributes in one fell swoop: */ Button btn1 "Button 1", btn2 "Button 2" { // See %controller block at top of file onClick: btnClicked; // Pick an event handler } /* You can accumulate attributes and child widgets onto widgets already * declared: */ btn1 { height: 20; } ComboBox combo { /* An attribute can have multiple values, separated by commas. * Attribute values are optionally quoted; quotes are necessary in * some cases, however, such as when the value contains a comma. */ list: 42, Gettysburg, Denny Crane, "Hi, Mom!"; } } }
/* This comment spans multiple lines */ Window win /* This comment is in the middle of a line */ { // This comment lasts for one line Button "Button"; // As does this one }
Comments are as in C, C++, or Java. Single-line comments begin
with “//
” and extend to the end of the line.
Multi-line comments begin with “/*
” and
extend to the next “*/
”.
projectName: Project; mockup: true;
Options are like attributes that apply to the entire file. They have the same syntax as attributes, but aren't contained in any widget.
%controller { /* These might be triggered by menu items in the File menu or by buttons in * the toolbar */ Action new, open, save; /* This is triggered when the user interacts with the combo box */ Action getNewValue; }
If the widgets on the screen are to do anything, there needs to be a controller to handle each event, such as a button click, and call the appropriate back-end code. The controller is a set of event handlers, each of which needs to be defined here so that they can be specified by the widgets and then implemented in the controller.
The controller block begins with the special string
“%controller
” and is enclosed in braces.
// See %controller block Window win "Example" { Menu "File" { MenuItem "New ..." { onClick: new; } MenuItem "Open ..." { onClick: open; } MenuItem "Save ..." { onClick: save; } } Button "New" { onClick: new; } Button "Open" { onClick: open; } Button "Save" { onClick: save; } ComboBox combo { list: Crane, Poole, Schmidt; onClick: getNewValue; } }
An event handler is a function or method that is called when the user “does something,” such as clicking a button, choosing a menu item, or pressing a key. The event handler code must be written elsewhere in the target language, and it must conform to the interface defined by the event handler. The event handlers defined in the source file are collectively known as the controller.
Connections between widgets and event handlers are made by certain
attributes, which conventionally begin with
“on
”, such as onClick
.
MenuItem quitCmd "Quit" { mnemonic: N; onClick: quit; }
The type of the widget. This must be one of the widget types listed in the widget library.
This is the only part of the widget declaration that is always required. Most widget types are only useful if more is given; however, some, such as MenuSeparator, may reasonably be declared with nothing but the type.
This is the name of the widget, as it will appear in the generated code.
An identifier must start with a letter or underscore, and may thereafter
contain digits as well. (These are much the same rules as for C, C++, or
Java, except that the $
symbol is not allowed.)
The identifier is optional. A widget without an identifier is called an anonymous widget, and it incurs these restrictions:
Notwithstanding these restrictions, leaving widgets anonymous can make code clearer and easier to read, as superfluous identifiers are distracting and unhelpful. Some widgets, such as Labels, are usually anonymous.
The label string is simply shorthand for the label
attribute. The following pieces of code are equivalent:
Button "The Button";
Button { label: "The Button"; }
The precise meaning of the label
attribute differs by widget
type, but generally it is, in some sense, the label. For instance:
The body of the declaration, much like a block of code in C, C++, or
Java, is delimited by curly braces (“{
” and
“}
”). If there are no attributes or child
widgets being declared, the empty braces may be replaced by a single
semicolon:
Label "Push the button, Frank." { /* Empty declaration body */ }
Label "Push the button, Frank.";
Widget { attribute: value; otherAttribute: "value"; }
As is suggested by the term, an attribute is some property of a particular widget --- perhaps its width, its color, its displayed text, or the event handler triggered when it is clicked.
An attribute statement consists of the name of the attribute, followed by a colon, a comma-separated list of values, and a terminating semicolon. Most attributes only permit one value.
things: 1, 2, 42, "Say \"Cheese!\"", "I like semicolons; they are useful", "This string is really long, and we want to break it across lines in \ the ELVIS code, but we don't want it to actually span multiple lines \ the interface, so we use a backslash at the end of each line.", "This string spans multiple lines.";
An attribute value may be surrounded by quotation marks. This is optional unless one of the following is true:
Within quotes, the following escape sequences are understood:
\"
represents a quotation mark. Without the backslash,
the quotation mark would end the string.\\
represents a single backslash.\
by itself at the end of a line says to skip the end of
the line and all leading spaces and tabs in the next line. This allows
long lines of text to be split across lines in the ELVIS source but not
in the interface.All other escape sequences (that is, a backslash followed by any other character) will be left alone, to be interpreted by whatever language the ELVIS source is compiled to.
Just as many widgets contain others on the computer screen, widget declarations contain others in the body, between the delimiting curly braces (“{” and “}”). Generally a widget literally contains its child widgets: A window may contain a menu bar, which contains menus, which contain menu items.
To avoid the heaps of duplicated code typically involved in GUI coding, ELVIS allows declarations for several widgets to be given at once. All of the following pieces of code are equivalent:
Button b1 "1", b2 "2", b3 "3" { onClick: digit; }
Button b1 "1" { onClick: digit; } Button b2 "2" { onClick: digit; } Button b3 "3" { onClick: digit; }
Button b1 { label: "1"; onClick: digit; } Button b2 { label: "2"; onClick: digit; } Button b3 { label: "3"; onClick: digit; }
Note that all of the widgets declared at once have the same attributes,
except that the label
attribute may be set
differently using label strings.
The body of a multiple-widget declaration may only contain attributes, not child widgets, since a child widget can only have one parent.
Once a widget has been declared, it can still be augmented with more attributes and child widgets. The following pieces of code are equivalent:
GridLayout { TextField firstName, middleInitial, lastName { row: 1; } firstName, lastName { spanColumns: 2; characters: 20; } middleInitial { characters: 1; } /* ... */ }
GridLayout { TextField firstName { row: 1; spanColumns: 2; characters: 20; } TextField lastName { row: 1; spanColumns: 2; characters: 20; } TextField middleInitial { row: 1; characters: 1; } /* ... */ }
In this way, several widgets whose attributes are similar, but not identical, can be “factored” so that any identical attributes can be given in one place. This makes the code quicker to write and to read, and also makes its simpler to change the code later on - in the above example, for instance, if one needs to move the three text fields to the second row, one need only change the number in one place.
Another use of accumulation is for organization. It may be clearer, say, to group all the event handler attributes in one section, having declared the other attributes earlier.
To accumulate onto an existing widget, simply use its identifier alone in a new declaration without a widget type. Any attributes and child widgets declared in the body will then be added to the widget as if they had appeared when the widget was first declared.
You can accumulate onto multiple widgets by separating their identifiers with commas (much like for multiple declarations). However, only attributes may be accumulated this way (see the discussion under multiple declarations).
An accumulation must name a widget which has already been declared in the same declaration body. (In fact, this is the only way the parser knows the difference between an accumulation and an anonymous widget declaration — if the identifier doesn't name an existing widget, it is assumed to be a widget type.)