The XL language

XL (provisional name) is a dynamically typed, Java-like language for writing scripts in Slice. Slice on the Tablet PC can execute both Python and XL scripts; however, neither language can call functions defined in the other. On the (not yet released) Android and Swing versions of Slice, only XL scripts are supported.

Python and XL scripts call the same core Slice operations, with minor exceptions (see below). Translation from Python to XL is generally very simple, except when Python libraries are used (which is rare within Slice). XL does not currently have any libraries.

XL was developed for two reasons: (1) The Python implementation for Android looked like it would be awkward to use as a scripting language. (2) We have long felt that a language specifically written for Slice scripting could make application development easier. At the current stage of development, this is not yet the case, but the opportunity now exists.

As already noted, XL has Java-like syntax, and generally preserves Java semantics with the important difference that it is dynamically typed. The most obvious differences are what is left out. XL has classes, but no fields; all methods are static (by default; the static keyword is not used), and class names are used only for name disambiguation. Accordingly, there are no objects; classes are not instantiable. XL has no exception mechanism (try/catch), since this is of limited use without instantiable classes. (A central principle of Slice scripts is that they do not save values "internally" across script invocations - such data is always stored in the state tree. Given this, instantiable classes would be of little value, and might lead to violations of this principle.)

The other major difference from Java is the addition of heterogeneous lists, as in Python (and Lisp and ...), with Python-like notation for constants (square brackets and commas). (For Java compatibility, set braces - {, } - can also be used; we always use square brackets in our scripts.) The list operations are similar to Python's (e.g. range subscripts), but with slight differences in syntax. XL also has a Python-like dictionary type, but it is rarely used (for the same reason that objects, if they existed in XL, would not be used, as discussed in the previous paragraph).

XL code is loaded into Slice apps in a way analogous to Python: In the root node of the initial XML file, the attribute XLDefs gives the names of files containing XL code. Each file contains one or more class definitions. Correspondence between class names and filenames is not assumed, and there are no visibility restrictions on classes (or methods).

Documentation structure

We start with a few examples, then describe the differences between XL and Java, describe the few differences in the use of XL, as compared to Python, within Slice, and then lastly give the syntax and semantics in detail. The first two sections should suffice for most purposes.

We assume the reader is familiar with Slice scripting in general.

Examples

XL is not intended to be a standalone scripting language. These examples are drawn from a Slice application, a version of the networked lecture app. A basic understanding of that app is required to understand these examples: In this app, a lecturer writes on slides, and his or her writing is sent to a display. The lecture and display roles of this app are different in that the display is entirely passive: it receives slides and strokes from the lecture role, but does not send messages. However, the two roles use basically the same tree structure, as follows:

Slice
   Lecture
       VerticalToolbar
       HorizontalToolbar
       Slides
          InkPanel (repeated; one for each slide)
             Label (optional; underlying image, usually imported from PPT)
             Stroke (repeated)

We illustrate XL with several examples of scripts from this app, also providing the Python versions for comparison.

The tree function

This commonly-used function creates a tree from a node name, a list of attributes, and a list of children. The only complication is that the "Type" attribute can appear in the list of attributes, and then it must be passed to the core MakeTreeNode function. The Python version:

def tree(name, attrlist, subtrees):
    # Check if Type attribute is being used
    typeattr = None
    for i in range(0, len(attrlist)-1, 2):
      if attrlist[i] == "Type":
          typeattr = str(attrlist[i+1])
          break
    t = TreeNode.MakeTreeNode(name, typeattr)
    for i in range(0, len(attrlist)-1, 2):
        attrname = attrlist[i]
        attrvalue = attrlist[i+1]
        if attrname != "Type":
           t.SetAttribute(attrname, str(attrvalue))
    for t1 in subtrees:
	    t.AppendChild(t1)
    return t

and the XL version:

 TreeNode tree(String name, List attrlist, List subtrees) {
   String typeattr = null;
   for (int i : [0..attrlist.count()-2 by 2])
      if (attrlist[i] == "Type") {
         typeattr = attrlist[i+1];
         break;
      }
   noyb t = TreeNode.MakeTreeNode(name, typeattr);
   for (int i : [0..attrlist.count()-2 by 2]) {
      attrname = attrlist[i];
      attrvalue = attrlist[i+1];
      if (attrname != "Type")
         t[attrname] = attrvalue;
   }
   for (TreeNode t1 : subtrees)
	    t.AppendChild(t1);
   return t;
 }

The correspondence is perhaps clear enough as to be self-explanatory. We make just a couple of points:

  • Although it is not possible to define non-static operations in XL, there are some built-in non-static methods on XL values (these are in addition to the core Slice operations). One illustrated here is count, which gives the length of a list or string.

  • Range expressions of the form [i..j], analogous to Python's [i:j], can be used for creating integer lists in general, not just as subscripts. (A range expression can contain negative numbers, with the same meaning as in Python, only when it is used as a subscript.)

  • Variables - locals and parameters - can be declared, but doing so is optional. In this example, they are declared. The type noyb ("none of your business") is the type of a variable that may contain values of different types. At present, no use is made of these types; they are strictly for documentation. (Methods must be given a return type, although, again, it is not checked at present.)

The next script is the one that starts network connections, depending upon the "role" of this Slice instance - lecturer or display The Python version:

def StartRole():
   if Root["Role"] == "Display":
      NetworkHelper.TreeNodeMessage += LectureHandler
      NetworkHelper.SetNetworkOption("TcpPublishingServer", "",
                                       int(Root["DisplayPort"]))
      NetworkHelper.StartNetwork()
   else:  # Role = "Lecturer"
      NetworkHelper.SetNetworkOption("TcpClient", Root["DisplayIP"],
                                            int(Root["DisplayPort"]) )
      started = NetworkHelper.StartNetwork()
      if not started:
        tryagain = Slice.Prompt("Server not found, try again?", "")
        if tryagain:
          StartRole()
        else:
          MessageBox.Show("Running as standalone app.")
          Root["NetworkMode"] = "Standalone"
   return

and XL:

   void StartRole() {
      if (Root["Role"] == "Display") {
         XLEngine.RegisterMessageHandler("LectureHandler");
         NetworkHelper.SetNetworkOption("TcpPublishingServer", "",
                                          Root["DisplayPort"].toInt());
         NetworkHelper.StartNetwork();
      }
      else {  // Role = "Lecturer"
         NetworkHelper.SetNetworkOption("TcpClient", Root["DisplayIP"],
                                           Root["DisplayPort"].toInt());
         started = NetworkHelper.StartNetwork();
         if (!started) {
            tryagain = Slice.Prompt("Server not found, try again?", "");
            if (tryagain)
               StartRole();
            else {
               Slice.Message("Running as standalone app.");
               Root["NetworkMode"] = "Standalone";
            }
         }
      }
   }

Differences from Java

Most of XL can be explained by a few differences from Java. We will not go into all the Java features that are omitted from Java, but just the principal ones. The next section presents the full syntax of XL; the semantics mostly follows Java but we will explain some finer points of difference there.

As mentioned above, the major differences between XL and Java are: (1) XL does not have objects - all methods are static and classes have no fields (even static ones); (2) XL is dynamically typed; the major consequence of this is that (3) XL has (heterogeneous) lists built in.

The syntax of XL is drawn from Java. The statements of XL - if, while, do-while, for (in two forms: index variable and iterator), switch, return, labeled statements, break, continue, and expression statements - look and work very much as in Java; try/catch is absent. (The only notable exception is that the cases in a switch statement can be arbitrary expressions, and are evaluated dynamically each time the case in question is tested.) Package and import statements are ignored. Variables may be declared (legal types are int, char, float, String, boolean, and noyb, and parameterized types, which can be any name followed by type arguments enclosed in </> brackets (as in Java); the common parameterized types would be List and Dictionary. However, type declarations are ignored; they are included only for future purposes, and, of course, for documentation. (The noyb type is used to declare variables that will, in fact, change type during execution.)

The rest of this section is divided into four subsections: method-calling, lists and dictionaries, type conversions, and printing; these represent the major areas of difference between XL and Java.

Method-calling

As already stated, all methods of XL are static. There is no overloading of method names, in the Java sense (where the particular method to be called is determined by the number and type of arguments). Methods defined in different classes can have the same name, with the call being disambiguated from the class and method names alone. Specifically, XL has the following forms of method call:

  • f(args): unqualified method name. If the method is defined in the current class (the one in which the call appears), that method is used. Otherwise, f must be defined in exactly one other class (i.e. a globally unique definition), and that definition is used.

  • C.f(args), where C is an XL class. f must be defined in C, and that definition is used.

  • C.f(args), where C is one of the core class names: Slice, TreeNode, NetworkHelper, or XLEngine. (The latter is new; see core function documentation.) This is a method call to a static core method.

  • exp.f(args), where exp is an expression. Since XL does not have objects, this can only be a call to a core Slice method. At present, the only public core classes with non-static methods are TreeNode and StrokeNode, so exp must evaluate to an object of one of those classes.

Although XL itself does not have overloading, calls to core functions are resolved using standard overloading rules (based on the dynamic types of the arguments).

Lists and dictionaries

XL has heterogeneous lists. The usual notation for list constants is [e0, e1, ..., en], as in Python. Subscripting is also similar to Python, but more flexible in one way: subscripts can be lists of integers, and then the result is a sublist (generalizing Python's slice indices).

Strings can also be used as lists, with all the operations below acting as if on a list of char's (except that, where appropriate, the results will be strings, rather than character lists).

XL's list operations are:

  • Constants: In addition to the normal constants above, lists can contain range expressions. The syntax is [e0..e1 by e2], which gives the list of all numbers starting from e0, incrementing by e2, and ending before the first number greater than e1. If e0 is omitted, it defaults to zero; if the by clause is omitted, the step defaults to 1 if e0<e1, -1 otherwise. e1 can be omitted only if the list is in the syntactic position of a subscript; see below.

  • L.count(): the length of L.

  • exp :: L: non-destructively add exp to the front of L. At present, this operation copies L, and therefore does not take constant time (as it should, and does in Python, Lisp, etc.).

  • L1 + L2: non-destructively concatenate L1 and L2. Again, unlike most languages, this operation copies both lists.

  • Subscripting: Subscripts come in a variety of flavors, though all ultimately come down to lists of integers. Indexing is from zero.

    • L[e], where e evaluates to a single integer (or a string convertible to an integer): return that element.

    • L[]: return the empty list. (This is a degenerate version of the next case.)

    • L[e1, ..., ek], where either k> 1, or e1 is not an integer or string. The expressions ei are processed in order, each contributing to the list of subscripts as follows. This always produces a list of results, even if k=1 and the processing described below produces a single index.

      • If ei is an int (or a string convertible to an int), say j, L[j] is added to the result list, if j is non-negative; if j is negative, then length(L)-j is used as the subscript.

      • If ei is a list, this is the same as subscripting by that list (see below), i.e. as if the list had been interpolated in place of ei. For example, if the value of a variable x is [0, 2, 5], and L is a list, then L[x] is that same thing as L[0, 2, 5]. (Again, it is not exactly as if the list were interpolated, in that if L has just this one subscript, and this list consists of a single integer j, the result is a one-element list containing L[j], rather than ijust L[j].)

      • If ei is a range expression, e0..e1 by e2, the range expression is evaluated as indicated above, and the generated elements are used as subscripts. In this case, e1 may be omitted, and it defaults to length(L)-1. (A syntactic restriction is that e0 and e1 may not both be omitted.)

    • Assignment: L[e1, ..., ek] = exp. Again, the case where k = 1 and ek evaluates to an int or string is special: the right-hand side value simply replaces the corresponding element in L. Otherwise, the list of indices is generated just as described above, but we need to distinguish the case where the right-hand value is a list. If it is not, then it is simply assigned to each location in L given by the list of indices. If the right-hand value is a list, then the elements of that list are assigned to the corresponding elements of L, with the list being repeated as often as necessary. For example, if L = ["a", "b", "c", "d"] then L[1, 3] = ["e", "f"] will result in L having value ["a", "e", "c", "f"], and L[1, 2, 3] = ["e", "f"] will result in L having value ["a", "e", "f", "e"]. (With this syntax, there is no direct way to replace several elements of a list by the same list; for example, L[0,1,3] = ['a', 'b', 'c'] will replace the first element of L by 'a', the second by 'b', etc. The only way to make each of those elements of L contain the list ['a', 'b', 'c'] is to write a loop: for (i: [0, 1, 3]) L[i] = ['a', 'b', 'c'];.)

We note two final points about lists: (1) When subscripting is performed on a string, the result is a string; that is, the string is converted to a list of chars only for the duration of the operation, and the result is converted back. (No operation is provided to actually convert a string to a list.) (2) The equality operation on lists uses structural equality (so takes linear time); no "object identity" operation is provided, and no other comparison operations are implemented on lists.

Dictionaries are maps from strings to other values (of arbitrary type). The empty list [] acts also as the empty dictionary. Dictionary constants are written [s0 -> e0, s1 -> e1, ...], where each si is a String-valued expression and ei is an arbitrary expression. The only operations permitted on dictionaries are single-item subscripting (with a string), single-item assignment, and union of dictionaries using operator +; for the union operation, collisions are resolved in favor of the second argument. In a for loop for (name : exp) ..., if the exp is a dictionary, it is, in effect, converted to a list of key/value pairs (two-element lists), in no prescribed order, for the iteration.

Note that the attributes of a TreeNode can be accessed by subscripting with a string, and can be assigned similarly (just as in Python). Nonetheless, TreeNodes are not dictionaries; they cannot be iterated over in for loops, and the + operator is not applicable to them.

Automatic conversions

Conversions are much as in Java (except that the decision to convert a value is, of course, made at run-time).

  • Conversion to String: All types are automatically converted to String when necessary, as, for example, when concatenating with a string (using +) or when passing a value to a core function that expects a string argument.

  • Conversion from String to list: As noted in the previous section, this is done by default, but only so that list operations can be performed on strings, so this is transparent to the programmer.

  • String to int and String to float: For arithmetic operations other than + - specifically, *, -, /, and % - strings are converted to ints if possible, and if not, then to floats, if possible.

We mention three operations provided in XL for manual conversions: toInt(), toFloat(), and toString(). These convert any reasonable thing - such as strings that are in numeric format - to the given type. Syntactically, these are used as non-static methods - e.g. e.toInt(). If they fail, the computation is aborted, so they should only be used when their argument is known to be convertible to the corresponding type of value (in particular, if applied to a string, the string should definitely have the correct numeric format).

Special cases: We note that there is one automatic conversion that is done in a core function, not as part of XL (so is also done for Python scripts): When calling GetChild, if the argument is a string that can be converted to an int, then that conversion is done. Note that there is no loss of generality here: the overloading of GetChild on a string argument searches for a child with the given tag, but a tag cannot have the form of a number, so that version could never succeed anyway. We have found that this is otherwise a source of bugs when a node attribute is used as an argument to GetChild. The script-writer should use toInt() in this case, but this is easy to forget, and since GetChild is overloaded, it will simply call the string version and fail (return null), with no exception being raised.

Printing

Just one operation is provided in XL for printing to the console:

  • e.print(). This prints e, followed by a newline.

Generally speaking, this is only useful for debugging (as are the core functions Slice.Log(..) and Slice.LogLine(..)). To show the user a message as part of the application, use Slice.Message(..)), which produces a dismissable popup window with the given message.

Differences from Python in the context of Slice

The core Slice functions that can be used from either Python or XL are documented on the framework reference page. There are a few functions that are different when scripting with Python or XL. Here we describe those methods that are specific to XL.

First, to load files of XL definitions, set the XLDefs attribute of the root.

Function names associated with script attributes are executed as usual in Slice, and may be either XL or Python functions. Slice looks first for an XL definition for a given function name, and tries Python only if no XL definition exists.

Specific differences in XL:

  • To create a global variable that can be referenced by all XL functions, use Slice.SetXLGlobal(name, exp) (analogous to SetGlobal for Python globals). The assigned value (exp) can be referenced using the variable name.

  • To make an XL function a handler for network messages, call one of the functions XLEngine.RegisterMessageHandler(function-name), XLEngine.RegisterDisconnectHandler(function-name), XLEngine.RegisterClientlostHandler(function-name), or XLEngine.RegisterClientjoinedHandler(function-name). The argument to each is the name of the function being registered, as a string. (In Python scripts, this is done by assigning to variables NetworkHelper.TreeNodeMessage, etc., using +=.)

  • The "varargs" version of function NetworkHelper.StartNetwork() cannot be used in XL. Instead, use the version that takes a list argument (which can also be used in Python).

XL syntax

Lexical rules

Lexing in XL is much like Java.

  • Identifiers: Sequences of upper- and lower-case letters, digits, and the underscore symbol, not beginning with a digit. Case-sensitive.

  • Keywords: Keywords are not case-sensitive. They are: boolean, break, by, case, catch, char, class, continue, default, do, else, float, if, false, for, import, instanceof, int, noyb, null, package, return, switch, throw, true, try, void, and while. (Note: In some cases, the syntactic construct corresponding to these keywords in Java is not implemented in XL, but the keywords are reserved for the future. These cases are: catch, try, and instanceof. noyb and by are keywords in XL but not in Java.)

  • Numbers: An integer is a non-empty sequence of decimal digits. A float is an integer followed by a decimal part and an exponent (at least one of which must be present). A decimal part is a period and an integer; an exponent is the letter E or e, an optional sign (+ or -), and an integer. (XL does not have octal, hex, or binary constants.)

  • Characters: A single character or escape sequence enclosed in single quotes. An escape sequence is a backslash followed by any character. In most cases, the backslash is simply ignored; the escape sequences with special meaning are \n, \t, and \r.

  • Strings: Sequences of characters enclosed in double quotes. Only escape sequences of the form \c are recognized; this sequence is identical to the character c (i.e. the backslash is ignored) except when c is n, t, b, f, or r, in which case the character is replaced by the corresponding control character. The escape sequence \" is a double quote that does not terminate the string.

  • Comments: Comments are as in C++: Multiline (C-style) comments consist of the character sequence /*, followed by arbitrary characters (including newlines) terminating with the first occurrence of sequence */. Single-line (C++-style) comments consist of the character sequence //, followed by arbitrary non-newline characters, terminating at the first newline.

  • Whitespace: Comments, spaces, tabs, and newlines are interpreted as whitespace and have no syntactic significance except to the extent that they separate tokens. They are also meaningful within character or string literals.

  • Operators: The following sequences of special characters are operators: .., -, ->, -=, --, *, *=, ::, <, >, <=, >=, <<, <<=, >>, >>=, =, ==, !, !=, +, ++, +=, /, /=, |, |=, ||, ||=, &, &&, &&=, ^, ^=, %, %=, and ~. These sequences are self-terminating in the sense that they do not need to be separated by spaces. For example, the sequence +++ is interpreted as operator ++ followed by operator +. The meaning of each operator is as in Java.

  • Other special symbols with syntactic significance in XL programs are: ., .*, {, }, ;, :, ,, (, ), [, ], ?. (Note that, unlike in Java, comma is not an operator.)

One special note on lexing: The lexing of sequences of the character > follows the rules above, so that, for example, the sequence >> always lexes as the right-shift operator. This creates a problem for parameterized types, and causes the type expression List<List<C>> to be syntactically incorrect. Thus, this type must be entered as List<List<C> >.

Context-free syntax

In the following grammar listing, non-terminals begin with lower-case letters and categories of terminal symbols (e.g. Identifier) begin with upper-case letters. Keywords are given in all upper-case (although unlike identifiers, keywords are not case-sensitive in XL).

The grammar is substantially LL(1), but with some exceptions. When a rule is followed by a list of tokens in square brackets, it gives a condition under which the rule is applicable; namely, the rule is to be used only if the token following the next one is one of the listed tokens. In other words, these tokens give us, in effect, two-symbol lookahead. (Note that the added lookahead symbols do not refer to the token after this production, but rather to the very next token in the input stream. Thus, the rule statement ::= Identifier : statement [ : ] says that if we are parsing a statement and the next token is Identifier, then this should be parsed as a labelled statement if and only if a colon immediately follows the identifier.)

This grammar was originally adapted from the ANTLR grammars. The extended grammar syntax was removed and the grammar simplified to remove most unused features. This is the grammar actually used to construct the XL parser.

compilationUnit  ::=  packageDeclarationOpt importDeclarationList typeDeclarationList

packageDeclarationOpt  ::= 
          |   packageDeclaration

packageDeclaration  ::=  PACKAGE qualifiedName ;

importDeclarationList  ::= 
          |   importDeclaration importDeclarationList

typeDeclarationList  ::= 
          |   classDeclaration typeDeclarationList

importDeclaration  ::=  IMPORT qualifiedName dotStarOpt ;

dotStarOpt  ::= 
          |   .*

qualifiedName  ::=  Identifier dotIdentifierList

dotIdentifierList  ::= 
          |   . Identifier dotIdentifierList

classDeclaration  ::=  CLASS Identifier classBody

classBody  ::=  { classBodyDeclarationList }

classBodyDeclarationList  ::= 
          |   memberDecl classBodyDeclarationList

memberDecl  ::=  methodDeclaration

methodDeclaration  ::=  type Identifier formalParameters block

type  ::=  classType
          |   primitiveType

classType  ::=  Identifier typeArgumentsOpt

primitiveType  ::=  BOOLEAN
          |   CHAR
          |   INT
          |   FLOAT
          |   VOID
          |   NOYB

typeArgumentsOpt  ::= 
          |   typeArguments

typeArguments  ::=  < type commaTypeList >

commaTypeList  ::= 
          |   , type commaTypeList

formalParameters  ::=  ( formalParameterDeclsOpt )

formalParameterDeclsOpt  ::= 
          |   formalParameterDecls

formalParameterDecls  ::=  typeOpt Identifier typeIdentList

typeOpt  ::=   [ Identifier, < ]
          |   type

typeIdentList  ::= 
          |   , typeOpt Identifier typeIdentList

block  ::=  { statementList }

statementList  ::= 
          |   statement statementList

localVariableDeclarationStatement  ::=  localVariableDeclaration Semicolon

localVariableDeclaration  ::=  type variableDeclarator variableDeclaratorList

variableDeclaratorList  ::= 
          |   , variableDeclarator variableDeclaratorList

variableDeclarator  ::=  Identifier variableInitializerOpt

variableInitializerOpt  ::= 
          |   = expression

colonExprOpt  ::= 
          |   : expression

elseStmtOpt  ::=   [ tok != tokens.Else ]
          |   ELSE statement

statement  ::=  block
          |   Identifier : statement   [ : ]
          |   localVariableDeclarationStatement   [ Identifier, < ]
          |   ASSERT expression colonExprOpt ;
          |   IF parExpression statement elseStmtOpt
          |   FOR Lparen forstatement
          |   WHILE parExpression statement
          |   DO statement WHILE parExpression ;
          |   SWITCH parExpression { caseStmts }
          |   RETURN expressionOpt ;
          |   THROW expression ;
          |   BREAK identifierOpt ;
          |   CONTINUE identifierOpt ;
          |   expression ;
          |   ;

forstatement  ::=  type typeFollower  [ Identifier, < ]
          |   typeFollower

typeFollower  ::=  Identifier : expression ( statement  [ : ]
          |   expressionList ; expressionOpt ; expressionList ) statement

caseStmts  ::= 
          |   caseList neStatementList caseStmts
          |   DEFAULT : neStatementList caseStmts

neStatementList  ::=  statement statementList

caseList  ::=  CASE expression : caseListOpt

caseListOpt  ::= 
          |   caseList

expressionList  ::= 
          |   expression commaExprList

commaExprList  ::= 
          |   , expression commaExprList

parExpression  ::=  ( expression )

identifierOpt  ::= 
          |   Identifier

expressionOpt  ::= 
          |   expression

expression  ::=  primary exprContOpt

exprContOpt  ::= 
          |   exprCont

exprCont  ::=  binop primary exprContOpt
          |   ? condExprTail
          |   incrOptr exprContOpt

condExprTail  ::=  expression : expression

primary  ::=  simplePrimary primarySuffix
          |   unaryOp primary

simplePrimary  ::=  listExpression
          |   parExpression
          |   Identifier argumentsOpt
          |   literal

argumentsOpt  ::= 
          |   arguments

primarySuffix  ::= 
          |   . Identifier argumentsOpt primarySuffix
          |   listExpression primarySuffix

listExpression  ::=  { listElementListOpt }
          |   [ listElementListOpt ]

listElementListOpt  ::= 
          |   listElement commaListElementList
          |   ,   [ ] , } ]

commaListElementList  ::= 
          |   ,   [ ] , } ]
          |   , listElement commaListElementList

listElement  ::=  dotDotOpt expression mapOrRangeOpt

dotDotOpt  ::= 
          |   ..

mapOrRangeOpt  ::= 
          |   .. expressionOpt byExprOpt
          |   -> expression

byExprOpt  ::= 
          |   BY expression

arguments  ::=  ( expressionList )

literal  ::=  Intlit
          |   Floatlit
          |   Charlit
          |   Stringlit
          |   TRUE
          |   FALSE
          |   NULL

binop  ::=  assignmentOp
          |   logicalOp
          |   bitwiseOp
          |   comparisonOp
          |   consop
          |   shiftOp
          |   arithOp

consop  ::=  ::

logicalOp  ::=  ||   |   &&

bitwiseOp  ::=  |   |   &   |   ^

comparisonOp  ::=  ==   |   !=   |   <=   |   >=   |   <   |   >

shiftOp  ::=  <<   |   >> 

arithOp  ::=  +   |   -   |   *   |   /   |   %

assignmentOp  ::=  =   |   +=   |   -=   |   *=   |   /=   |   &=
          |     |=   |   ^=   |   %=   |   <<=   |   >>=

unaryOp  ::=  +   |   -   |   ~   |   !    |   ++   |   --

incrOptr  ::=  ++   |   --

All control structures and operators behave, as much as possible, as in Java, so we will not document each one. Here are the exceptions:

  • Syntactically, types can be given wherever they could be given in Java, but they are (at present) ignored.

  • The equality operator, ==, can be applied to any type of value except a dictionary. It employs structural equality on all other types (so that the equality test between two lists or strings takes linear time), except "object" types: XL values that are actually Slice values, such as TreeNodes (which are obtained as the values of special Slice variables like Root and as the results of some core function calls); for those values, object identity is used.

  • In a switch statement, the cases are arbitrary expressions, which are evaluated when the switch statement is executed. Case types are arbitrary; the equality test used is as for the == operator.

  • The iterator version of the for statement ( for (name : exp) ...) can be used to iterate over lists, strings, or dictionaries. For the latter, the name is assigned pairs (two-element lists) giving all the key/value bindings in the dictionary, in no prescribed order.
    Last updated on Thu Jun 10 15:06:52 CDT 2010.