Spice reference manual, v1.3, 23rd September 1998

1 introduction

This document is intended to be a relatively terse reference manual for the current definition of Spice. A grammar notation is used to specify the allowed productions. Terminals are shown in "quotes". * is used to indicate zero or more terms, + for one or more terms. Square brackets are used for optional terms. Round brackets are used for grouping terms.

a ++ ","

is a contraction for

a ("," a)+

Similarly,

a ** ","

is a contraction for

a ("," a)*


1 introduction
2 lexis
  2.1 words
  2.2 marks
  2.3 literals
  2.4 comments
3 top-level syntax
  3.1 programs
  3.2 aside: qualifiers
  3.3 modules
  3.4 imports
  3.5 style definitions
  3.6 property declarations
  3.7 procedure definitions
  3.8 variable definitions
  3.9 class definitions
  3.10 unit definitions
  3.11 enumeration definitions
4 small syntax
  4.1 expressions
  4.2 type expressions
5 scope rules
6 the standard library
  6.1 exceptions
  6.2 generic procedures
  6.3 numbers
  6.4 enumerations
  6.5 values with units
  6.6 strings
  6.7 symbols
  6.8 booleans
  6.9 arrays
  6.10 locales
  6.11 dates
  6.12 regular expressions
  6.13 procedures
  6.14 dictionaries and tables
  6.15 io
  6.16 types
7 appendix: limited spice
8 appendix: spice standard modules
9 their own destiny
10 appendix: syntactic summary
11 appendix: operator and symbol table
12 appendix: reserved words

2 lexis

A Spice program is made up of a series of tokens. Tokens are separated by token gaps. Token gaps are unimportant except that they mey serve to separate tokens that would otherwise combine into single tokens (eg the two tokens "x" and "then" must be separated by a gap, otherwise they would be recognised as a single token "xthen"), and token gaps containing newlines ("<nl>") may apply the semicolon insertion rule:

The sequence A <nl> B, where A and B are tokens, is replaced by A;B unless there is "good reason" not to; specifically, "good reason" is:

A token gap is any sequence of spaces, newlines, horizontal or vertical tabs, and form-feeds, possibly containing comments (see below).

A token is a word, a mark, or a literal. A word is a sequence of letters and digits; a mark is a sequence of mark characters; and a literal is a string, character, numeric, or unit constant. The lexis makes no substantive difference between those words (or marks) that are reserved and those that may freely be used as identifiers (or operators).

2.1 words

A word is a series of digits, letters, underbars ("_"), and dollar marks ("$"), startinh with a letter. Words are case sensitive and of unlimited length.

Built-in Spice identifiers do not contain underbars or dollar marks. Spice conventions are that type names start with upper-case letters and that value names do not.

2.2 marks

A mark is either a simple mark or a compound mark. A simple mark is one of the characters "(", ")", "{", "}", "[", "]", ";", ",". A compound mark is a series of mark characters, where a mark character is one of "~!@%^&*-+=|:.?/<>". Note that "#" is not a mark character.

Note that this means that "<>", "+++", "->" etc are all possible Spice marks; they are reserved for future expansion.

2.3 literals

2.3.1 numeric and unit literals

A numeric literal represents an integer or floating-point number, possibly with units, possibly in a radix other than the default 10. Within a numeric literal, underbar characters may be present; they are immediately discarded, being there only for presentation (eg to make 7476294 readable).

2.3.1.1 plain integer literals

A plain integer literal is a series of digits. It represents the obvious number in radix 10.

2.3.1.2 radix-specified integer literals

A radix-specified integer literal consists of a radix specifier, which is a series of digits followed by the letter x (in either case), followed by a series of letters and digits. The letters must be consistent with the radix, in that if the radix is k then no letter past a + (k - 10) is permitted; if the radix is 10 or less, any letters terminate the numeric part of the literal.

2.3.1.3 integer literals with units

A plain integer literal, or a radix-specified integer literal with radix 10 or less, may be immediately followed by a unit name, which is a series of letters. The integer value represents a value in those units. Thus 45cm, 19mile, and 6seconds are all legal, assuming that those units have been declared.

The value denoted by nU, where n is the number and U the units, is that returned by n @inUnits `U`.

2.3.1.4 plain floating literals

A floating literal is a series of digits, a decimal point, and another series of digits; it represents the "obvious" floating-point number. It may be followed by an exponent specifier, which is the letter e, optionally followed by a sign (+ or -), followed by a plain integer literal.

2.3.1.5 radix-specified floating literals

A radix-specified floating literal is a radix specifier followed by a series of letters and digits, a decimal point, a series of letters and digits, and an optional exponent specifier.

The radix specifier gives the radix of the floating point number but not the exponent. If the radix is 10 or less, then letters terminate the numeric part of the literal. If the radix is more than 10, the sign in any exponent specifier is not optional.

2.3.1.6 floating literals with units

A floating literal with radix 10 or less may be followed by a unit name, as for integer literals with units.

2.3.1.7 general specification of units

If a number in a radix greater than 10 is supposed to be in some units, then one of the general syntaxes for values-in-units must be used.

2.3.2 string literals

A string literal is a character sequence starting and ending with the character ("). The characters between the quotes are any characters except quotes (any flavour), newlines, control characters, or backslashes except as permitted by escape sequences:

2.3.3 archaic string literals

As for string literals, except that "double quotes" are replaced by "single quotes".

A string literal using single quotes is permitted for temporary backwards compatability only; quotes are too precious to consume two of them on a single job.

A string literal in single quotes provokes an archaicString recovery action.

2.3.4 symbol literals

As for string literals, except that "double quotes" are replaced by "backquotes", and the literal is of type Symbol.

2.4 comments

A comment is either the mark "//" followed by all characters up to a newline or end-of file, or the mark "/*" followed by any series of characters excluding "*/" and then by "*/", or the unquoted character "#" followed by all characters up to a newline or end-of-file.

3 top-level syntax

A Spice program is composed of a collection of modules. A module consists of a header, which identifies the module and what it relies on, and its body, which is a series of definitions possibly interspersed with executions. Definitions define variables, procedures, classes, units, and styles; executions are expressions that perform some run-time activity.

3.1 programs

def 1. Program ::= Spice ( Module* | ModuleBody )

def 2. Spice ::= "spice" String ( "," ( Name ":" Expr ) ) *

The String identifies the version of Spice that the writer assumed when the module was written; it is a dot-separated sequences of integers or names (eg "1.0" or "5.beta"). The Name-Expr pairs define settings for compiler preferences; each Expr must be evaluable at compile-time.

No specific preference settings have been defined so far.

3.2 aside: qualifiers

Qualifiers are annotations to declarations. Syntactically they look like expressions in square brackets, but this is just a dodge to allow for future expansion of the syntax.

def 3. Qualifier ::= "[" CommaExpr "]"

Usually the items of a Qualifier will be names (such as public and qualified); sometimes they will be more general. Qualifiers are discussed in the sections that require them, except we shall mention here two general declaration qualifiers:

3.3 modules

def 4. Module ::= "module" ModuleName ModuleBody [ "endmodule" ]

def 5. ModuleName ::= Word++"."

A module name is a seqence of dot-separated identifiers. The last name in the sequence is the leafname of the module; apart from in module and import constructs, modules are referred to only by their leafname or an alias for it. Note SEMI stands for a semicolon character which can often be omitted when followed by a newline etc.

def 6. ModuleBody ::= Import**SEMI Definition**SEMI

A module body consists of its import declarations followed by its own definitions. The definitions exported by the imported modules are available for use in the definitions; further, all the names defined in the defintions are available within all of the definitions -- there is no requirement to declare before use at the top-level.

def 7. Definition ::=
     StyleDef
     | ProcedureDef
     | VarDef
     | ClassDef
     | UnitDef
     | EnumDef
     | PropertyDef
     | Expr

A definition is a style, procedure, class, unit, enumeration, property, or variable definition, or an expression to be evaluated when loading the module it appears within.

3.4 imports

def 8. Import ::= "import" ( ImportItem [ "from" Expr ] ) ** ","

def 9. ImportItem ::= [ Qualifier ] ( ModuleName | "(" Expr ")" )

An import definition identifies a module to be imported and possibly a location to import it from; that location is given as a String-valued expression representing a URL. The name can be given directly or by the evaluation of an expression.

If the qualifier public is given, then all the identifiers in the imported module are added to this module's public interface. This allows modules to act as "collectors" for definitions from several other places.

Normally the imported identifiers can be referred to by their simple names. However, if the qualifier qualified is given, they must be referred to by prefixing them with leafname::, where leafname is the leafname of the exporting module. If the qualifier qualified(X) is given, they must be referred to by prefixing them with X::.

If two (or more) modules with the same leafname are imported, at most one of them is allowed to remain unqualified.

3.5 style definitions

A style definition may be wrapped within a mode or media definition, which constrains what mode or medium that the style applies to.

def 10. StyleDef ::= Mode | Media | Style

def 11. Mode ::=
     "define" "mode" Name ( "{" Style * "}" | Style * "enddefine" )
     | "mode" Name "{" Style * "}"

def 12. Media ::=
     "define" "media" Name ( "{" Style * "}" | "enddefine" )
     | "media" Name "{" Style * "}"

def 13. Style ::=
    
        "define"
        "style"
        Selectors
        ( "{" Property * "}" | "=" Property * "enddefine" )
     | "style" Selectors "{" Property * "}"

A Style specifies selectors, which identify suitable nodes in an XML parse tree, and gives properties which apply to those nodes and which specify how they are to be rendered.

3.5.1 style properties

def 14. Property ::= Name [ ":" PropValue | "=>" Expr ] SEMI

A style property is a named collection of values, which are given either by an expression or as a fixed set of PropValues.

def 15. PropValue ::= ( PropSeq [ "with mode" Name ] [ "!" ] ) ++ ","

def 16. PropSeq ::= Term ++

A property value is a CSS-style sequence of alternative-thingies separated by commas. Each sequence can be tagged as applying to a particular mode.

A property alternative-thingy is a sequence of property items. All the items "compose" to form the value of the property.

def 17. Term ::= LimitedExpr | BraceStatement

A LimitedExpr is an expression which consists only of brackets, operator expressions, literals, names, and function calls. This restriction permits simplicity in parsing.

Within a BraceStatement, which is evaluated each time the value of this property is demanded, the identifier this is bound to the current style object (allowing the other properties to be fetched from it).

3.5.2 style selectors

def 18. Selectors ::= Related ++ ","

Several selector patterns may all share the same style specification; they are written together, separated by commas.

def 19. Related ::= Sibling * [ ":" Name ]

Name is a pseudo-element (a synthetic tag on the XML tree) and can only appear at the end of a selector nesting sequence; this constraint is inherited from CSS. (There doesn't seem to be a good reason for the restriction in Spice.)

def 20. Sibling ::= Children ++ "+"

def 21. Children ::= Element ++ ">>"

def 22. Element ::= "(" Selectors ")" | Tag [ Attribution ]

def 23. Tag ::= Word | Symbol | "*"

def 24. Attribution ::= "[" AttrValue "]" | "." Name

Note that the basic words in a selector may be reserved words; you're allowed to call a selector "if" or "endwhile" (but not "++" or ")", unless they are quoted as Symbols). The selector corresponds to an XML tag; "*" is used to qualify any tag. The optional AttrVal and .Name qualifiers restrict the selector to nodes with specific properties.

The syntax .Name is CSS-compatible shorthand for [Name].

def 25. AttrVal ::= Word [ "[" AttrBinding ** "," "]" ]

def 26. AttrBinding ::= ( "=" | "~=" ) Expr

An AttrVal restricts the selector match to nodes that have the named attribute. Further, if there is an AttrBinding, the value of that attribute must correspond to the binding.

The meaning of "correspond" is that, for "=", the value of the attribute and the value of the Expr should be equal; and for "~=" that the attribute be a string and that the value of Expr be a string well-contained in the attribute (ie surrounded at each end with whitespace or end-of-string).

It's entriely likely that this should be reviewed; there's so much more possible using Spice data-types.

3.6 property declarations

def 27. PropertyDef ::= "property" SimpleDecl

def 28. SimpleDecl ::= Name [ Qualifier ] [ "=" Expr ] [ TypeWord Type ]

A property definition makes a property function with the given name. A property associates a result value with a key value. When a property is looked up for a key value, if there is a result value for that key, it is returned; otherwise the result value is the property's value on the prototypical object of the key's type object (which may itself involve further recursive lookup).

A property declaration at top level specifies the value for [the prototypical object for] Any [which is absent].

3.7 procedure definitions

Procedures are defined by method and function definitions. Methods defined by method new are initialisers and are used to set up new objects; for more details see under expressions.

def 29. ProcedureDef ::= SompleFunctionDef | NewProcedureDef

def 30. SimpleFunctionDef ::= "function" Header BraceStatement

def 31. NewProcedureDef ::=
        "define"
        ( "method" [ "new" ] | "function" )
        Header
        [ "throws" Expr ]
        [ "returns" Type ]
        [ "extends" Header ]
        ProcedureBody

The Header defines the procedure argument's names and types, the procedure name itself, and the "expected shape" of calls to the procedure. throws is a placeholder for future expansion. returns gives the type of the value(s) returned by a procedure.

def 32. Header ::= [ Qualifier ] [ "on" Type ] CallShape [ "=" Arg ]

A Header has an optional Qualifier, an optional on part, the CallShape, and an optional updater argument Arg.

The allowed Qualifers include public and private. A procedure with qualifier frozen cannot be overloaded outside its module; a procedure with qualifier fluid can be. (By default, functions are frozen and methods are not.) An overloading of a procedure which appears outside its owning module must have the qualifier extension, unless it appears in a class and extends a method of that class.

If the on part of the Header is given, it specifies the type of the first argument to the procedure. If the updater argument is given, this definition defines the updater of the named procedure, which must be declared in this module and have a compatible CallShape.

def 33. CallShape ::= PrefixShape | DottedShape | InfixShape

def 34. PrefixShape ::= NameA [ Arglist ]

def 35. DottedShape ::= NameB . NameA [ Arglist ]

def 36. InfixShape ::= ArgA @ NameA [ ArgB ]

There are three different CallShapes; the conventional prefix form f(x), the method-oriented form x.f(a), and the infix-oriented form x @f y. NameA is the name of the procedure being defined in all cases. The arguments are given by the optional Arglist for the prefix form, by the Arglist and NameB for the method form, and by ArgA and ArgB for the infix form; ArgA and ArgB are both Args, see below.

A method is implicitly in method form, with this. assumed as the first argument unless overridden explicitly. For method new it is permitted to make the method name the name C of the current class; in this case, the initialiser is that called by expressions of the form new C.

def 37. Arglist ::= "(" Args ")"

def 38. Args ::= Arg ** ","

def 39. Arg ::= Name [ TypeWord [ "!" ] Type | "==" Expr ] | [ Name ] "..."

An Arglist is just a comma-separated list of Args. An Arg is a Name, optionally followed by a Type introduced by a TypeWord, or optionally followed by a value introduced by "=="; or [for the last argument only] by ..., making it an indefinite argument, bound to an array of all the extra arguments to the procedure. If the Name is omitted from an indefinite argument, the name arguments is assumed.

All argument identifiers are immutable, ie, cannot be assigned to, but if they are data-structures, their components can be changed.

def 40. TypeWord ::= "is" | "as" | ":"

The TypeWord is (and its more tightly-binding sibling ":") mean that the argument should be of the given type. as means that the argument is converted to be of that type.

The prefixing of the Type with the operator !, mean that the argument does not take part in overloaded dispatch; all overloadings of that procedure must have that argument, and mark it as non-dispatch.

== Expr is equivalent to : T where T is a type whose only value is the value of Expr, which is evaluated at compile-time.

def 41. ProcedureBody ::= BraceStatement | "=>" StatementSeq "enddefine"

A procedure body is either done C-style, as a statement sequence enclosed in braces, or is introduced by => and ends with enddefine.

3.7.1 multiple definition

A method (or function) with a given name may be defined multiple times. If so, it is a polymorphic method. The different definitions must have the same number of arguments (up to any indefinite arguments) and no two definitions can have the same sequence of types for their arguments.

Furthermore, for any pair of definitions with signatures (ie the type-sequence of its arguments) T1 and T2, there must be a definition with type-sequence T3 where the ith element of T3 is a super-type of the ith elements of T1 and T2 (note that T3 might be equal to one of T1 or T2).

A call of such a polymorphic method will execute the most specific instance of it, ie, the one whose arguments are as specific as possible. In particular, methods can be redeclared in classes, when they over-ride the definitions in their parents.

If the Type of an argument is of the form T then the argument is not used for polymorphic dispatch and all overloadings must use the same type T for that argument.

By default, a function can only be overloaded within its original module; it must be defined with qualifier fluid to allow futher overloading. If a function is defined with qualifier only, then only one definition of that function is permitted.

A method is fluid by default; to prevent further overloading, it must be declared with qualifier final.

3.7.2 super and extends

An overloaded procedure may wish to invoke the "next most general" definition to complete (or start) its work. The identifier super is bound to that next most general definition. When there is ambiguity about that, it is resolved by the extends clause of the function, which takes the form of a function call where the operands are all type names; super is bound to the version of the function that takes those types as its argument.

3.8 variable definitions

A variable declaration introduces one or more identifiers, may give their initial value, and may give their type.

def 42. VarDef ::= ( "const" | "var" ) NameDecl

def 43. NameDecl ::= [ Qualifier ] SingleDecl ++ "," [ "is" Type | "are" Type ]

def 44. SingleDecl ::= ( OneDecl | "(" OneDecl ++ "," ")" ) [ "=" Expr ]

def 45. OneDecl ::= Name [ ":" Type | "..." ]

The Qualifier may include public and private. A var may have qualifier readonly, in which case it cannot be updated outside of its defining module; consts can never be assigned to. All the names declared in a single const or var declaration share the same Qualifier.

Each SingleDecl may initialise one or more names to the value(s) of an Expr; if the Expr is omitted, they are initialised to the default value of their type; if no type is specified, they are given type Any.

A SingleDecl will usually contain a single OneDecl, but it may contain several, in which case the Expr must be present and they are initialised from it as for assignment. The ... form is not permitted except as the last (or only) OneDecl; it bundles all the (remaining) values from the Expr into an array which is assigned to the Name.

It is forbidden for OneDecls to be typed if the entire NameDecl is typed, and vice versa.

When a var or const appears at top-level, or within a class, it defines a permanent variable that comes into existance when the module is loaded and goes away only when no references to it remain.

When a var or const appears within a procedure, it defines a local variable; a new location is allocated each time the declaration is executed. This location does not share with any other location. It persists as long as there are any references to it, which means that if it is not captured by any lambda-expression or hole-expression referring to it it may be disposed of when its scope is left (if not sooner).

3.9 class definitions

A Class defines a shape shared by a collection of objects which are its instances. Every Class is itself an object, and has a particular instance called its prototypical instance.

def 46. ClassDef ::=
        "define"
        "class"
        [ Qualifier ]
        Name
        [ "extends" CommaExpr ]
        ( "{" ClassProperty ** SEMI "}" | ClassProperty ** SEMI "enddefine" )

The Name is defined to be a constant bound to the class object, and is suitable for use as a type name.

A class may be qualified as public, private, or array; if it is qualified array, its instances are array-type objects, and can be indexed with integers. A public class has its methods and functions (but not its slots) declared public by default.

If the extends clause is present, its CommaExpr must be a comma-separated sequence of names of other classes, and this class inherits all of their slots. (Slots that are inherited twice along different routes of course only count once in the new class.)

def 47. ClassProperty ::= Procedure | SlotDecl | VarDef | Property

The properties of a class are its procedures and slots. A NameDecl within a class acts exactly like a NameDecl outside a class -- they declare top-level permanent identifiers. Within a class, method definitions by default give their implicit this argument that class as their type.

def 48. SlotDecl ::= ( "slot" | "shared" ) SimpleDecl

For SimpleDecl, see the definition of Property.

Slots are declared by slot and shared declarations. Each slot declaration arranges that objects of this type have a slot in them, suitable for storing values of the given Type, and defines a procedure Name of one argument which has the class as its type and whose action is to deliver the value of its slot; that procedure also has an updater which alters the value of that slot. A shared declaration does not allocate any slots in the object, but makes a procedure which accesses (and updates) a single shared location.

If a slot or shared identifier is qualified readonly, then the procedure that it exports has no updater, although the locally-visible version does.

When an object of this type is constructed, the Exprs of its slots are run to get the initial values for those slots.

A property declaration with a class specifies the value for the prototypical object of that class.

3.10 unit definitions

A unit definition informs the compiler about units and their dimensions.

def 49. UnitDef ::= "define" ( "unit" | "units" ) UnitDef ++ "," "enddefine"

def 50. UnitDef ::= Word [ "=" Expr ]

The Word of a UnitDef is declared to be legitimate unit names; they become suffix operators (and are allowed as the procedure part of dot-expressions). If the Expr of a UnitDef is present, it must be an compile-time expression delivering an anonymous Unit value, which is named by the Word.

for example, we may have define unit mile = inch * 12 * 3 * 1760 enddefine, which defines mile in terms of inch, or define unit length = new Unit(1,`length`) enddefine, which defines a new unit of dimension `length`.

3.11 enumeration definitions

def 51. EnumDef ::= "define" "enum" [ Qualifier ] Name "=" Name ++ "," "enddefine"

An enum declaration for Foo is shorthand for declaring a new class called Foo extending Enum, which provides two private slots (for the name and the magnitude of enumeration values) and an instance of this class for each of the Names. The enumeration values atart at 1 and the default value for Foo is an instance with value 0 and name brokenFoo.

The compiler will optimise this to in-line enum values when possible.

4 small syntax

Most of the rest of the syntax of Spice is in terms of expressions. Spice expressions may denote tuples of values; we say thay are multiple valued. Spice expressions may be evaluated in three modes; value mode, when they are evaluated for their value; update mode, when they are evaluated to update some location or data structure; and void mode, which is value mode except that all the values that are delivered are then discarded.

Unless otherwise specified, an expression is illegal in update mode. All function calls and operator applications are legal there; every procedure P can have an updater U, which is the procedure that is called when a call of P appears as the target of an assignment.

4.1 expressions

A Statement is an expression, or sequence of expressions separated by commas. The value of a sequence of Statements is the value of the last Statement; the values of all the others are discarded.

def 52. Statement ::= Expr ++ "," SEMI

def 53. StatementSeq ::= Statement *

def 54. Expr ::=
     Name
     | Expr "{" UnitExpr "}"
     | Hole
     | Literal
     | Expr "..."
     | PreOp Expr
     | Expr PostOp
     | Expr InOp Expr
     | Expr AssignOp Expr
     | "(" Expr ")"
     | Expr [ "." DotExpr ] [ "(" OptCommaExpr ")" ]
     | Expr "[" Expr "]"
     | "[" Expr "]"
     | LambdaExpr
     | "op" ( PreOp | InOp | PostOp )
     | "new" Name [ "(" Expr ")" ]
     | "none" OptExpr
     | "return" [ [ "with" ] CommaExpr ]
     | "break" [ [ "with" ] CommaExpr ]
     | "continue"
     | "throw" Expr
     | TryCatchExpr
     | "memo" CommaExpr
     | IfExpr
     | WhileExpr
     | ForExpr
     | SwitchExpr
     | BraceStatement
     | VarDef
     | ProcedureDef

Operator expressions are disambiguated by the "usual" precedence rules. Here, Expr is an expression that cannot contain top-level commas.

def 55. OptExpr ::= [ Expr ]

def 56. CommaExpr ::= Expr ++ ","

def 57. OptCommaExpr ::= [ CommaExpr ]

4.1.1 name

A Name is either a simple identifier, or an imported identifier short::id, where short is the short name of a module and id is the name of the identifier within it.

4.1.2 values with units

An expression followed by a UnitExpr in braces puts the expression into the specified units. This is used when the unit specification cannot contain the unit directly (eg its name begins with x or it is something like m/s).

def 58. UnitExpr ::= ( Word ** "*" | _ 1 _ ) [ "/" Word ** "*" ]

A UnitExpr allows a simple unit expression in terms of products and quotients of units.

4.1.3 hole

A Hole is a "virtual operand" used when making partial applications. Thus the expression "_ + 1" is a function that adds 1 to things. The expression "_2 - _1" is reverse subtract; the arguments to a partial application are numbered 1..N, left-to-right.

def 59. Hole ::= "_" [ Integer ]

4.1.4 literal

Literals may be symbols, strings, characters, numbers, or reserved literals. Symbols, strings, numbers, and reserved literals are themselves lexical items.

def 60. Literal ::=
     StringLiteral
     | SymbolLiteral
     | NumberLiteral
     | ReservedLiteral
     | Character

def 61. Character ::= Backslash ( String | Word )

A character literal is expressed as a string literal or name prefixed by a backslash. If a string, it must be one character long, and the character literal is the only character of the string. If a name, it must be one of the reserved names newline, space, tab, return, or formfeed, with the obvious meanings.

4.1.5 explosions

The value of the expression E... is the explosion of the value of E, ie it is equivalent to (E).explode; all the components of all the values of E.

In update mode (ie as the target of an assignment) E must be an updatable expression; it is assigned an array constructed of all the values on the source of the expression.

4.1.6 prefix operators

The value of a prefix expression is found by applying the procedure bound to the prefix operator to the value delivered by the operand, except for the special operator once, which arranges that its operand is evaluated only once, "as soon as possible".

The prefix operator one delivers the first of its arguments, and absent if it called with no arguments.

4.1.7 postfix operators

The value of a postfix expression is found by applying the procedure bound to the postfix operator to the value delivered by the operand.

def 62. PostOp ::= LexicalPostfixOperator | "@" ( Word | "(" Expr ")" )

[Note that @ can serve as a infix or postfix call marker.]

4.1.8 infix operators

def 63. InOp ::= LexicalInfixOperator | "@" ( Name | "(" Expr ")" )

Note. The logical operators && and ||, and the relational operators ==, <=, >=, <, >, !=, /==, ===, /=== are special in that they all automatically one their arguments.

Further, the relational operators are continued relationals; if R and S are relational operators, the expression a R b S c is shorthand for a R b && b S c, except that b is only evaluated once.

4.1.9 assignment operators

The assignment operators X= for some operators X (ie +=, -=, *=, /=, &=, |=, ^=) are shorthand for assignments where the target and left operand of X are the same, ie, E1 X= E2 is equivalent to E1 = E1 X E2 except that side-effects of E1 are only applied once.

The assignment operator = evaluates its left operand in update mode with the "source value" being its right operand.

The left operand is some sequence of updateable expressions, and the right operand has delivered some sequence of values. Assignments are done right-to-left; each assignment target takes the appropriate number of values from the right-hand end of the value sequence and the rest of the value sequence is handed to the remaining targets. If the assignment target is an identifier, one value is assigned into it. If it is a procedure call, its updater is invoked, passing it the remaining values to assign (it will return those it does not need).

At most one assignment target in an assignment is allowed to be a procedure call with an updater which takes an indefinite number of assigned values. (Otherwise, in (f(), g()) = (1,2,3), where f and g both have indefinite updaters, there would be no way to tell how the values were to be distributed between f and g.)

4.1.10 bracketed expressions

The expression (E) has the same value that E does.

4.1.11 call expressions

The expression x.f is a call of f with argument(s) x, as are the expressions f(x) and x @f. The expression x.f(y) is a call of f with arguments (x,y), as is x @f y.

When a call appears as the target of an assignment, eg f(x)=E, it is equivalent to calling the updater of f with the argument list (x,E).

def 64. DotExpr ::= Name | "(" Expr ")"

The expression permitted after a dot may be a Name or an arbitrary expression in brackets.

4.1.12 array expressions

The expression "[E]" is an array expression; it creates a new array by evaluating its operand and piling all the results into the new array.

4.1.13 index expressions

The value of the expression a[i] is that obtained by calling the index function associated with the type of a, supplying it with a and i. If a is an array, this attempts to index the array. If a is a table, it looks i up in that table. Otherwise it attempts to find the i-property of a.

When an index expression is used in update mode, the updater of the associated index function is called.

4.1.14 lambda expressions

A lambda-expression is an inline procedure definition; it is the procedure taking the specified arguments and computing the desired result.

def 65. LambdaExpr ::= "(" Args "=>" StatementSeq ")"

4.1.15 op expressions

The "op" syntax allows operators to be converted into the corresponding functions. Only operators that do not assign to their arguments can be so converted (because Spice does not have pass-by-reference). By a happy coincidence, this eliminates those operators that have different meaning in their prefix and postfix forms, apart from "+" and "-"; we declare that "op +" means addition and "op -" means subtraction. The standard functions "positive" and "neg[ative]" correspond to the prefix "+" and "-".

The procedures delivered by op && and op || will, of course, have both arguments evaluated when they are called.

4.1.16 new expressions

A new expression new Id(E) extracts the prototypical object PI for id, makes a copy of that object, and then hands it to Id together with the arguments E:

new Id(X) == Id.getPrototypicalInstance.copy.Id( X )

If the argument list (E) is omitted, this does not mean that it is present but empty; it is as though one had written

(X => new Id( X ))

where X can denote multiple values. Id should be a constructor function or a class object (if the latter, no arguments should be supplied).

4.1.17 none expressions

The expression none denotes no values. The expression none E evaluates E and discards all the values it computes.

4.1.18 return expressions

The expression return E or return with E evaluates E and then returns from the current procedure. return is equivalent to return none.

4.1.19 break expressions

The expression break E or break with E evaluates E and then breaks out of the current loop, bypassing the evaluation of its with result clause (if any). break is equivalent to break none.

4.1.20 continue expressions

The expression continue abandons evaluation of the current loop iteration and starts the next one.

4.1.21 throw and try expressions

this section is present for discussion. We need to clear up some details to do with error-handling in general. But this stuff is "known technology", so we should be OK.

The expression throw E raises an exception with value the value of E. If this exception is not caught within the program, it is caught by the top-level Spice environment, which will deal with it in an implementation-specific fashion.

The try expression allows exceptions to be caught and handled.

def 66. TryCatchExpr ::= "try" StatementSeq CatchSeq "endtry"

def 67. CatchSeq ::= ( "catch" Arglist [ "with" ] StatementSeq ) **

The StatementSeq of the try is evaluated and the try expression resturns its result if no exception is thrown. If an exception E is thrown, then the catch body who's Arglist best matches the thrown value is executed (just as though the catch blocks were alternative definitions for an overloaded function) and the reult of the try is the result of that catch block.

If there is no catch (x: Any) catch clause, it is as though the clause catch (x:Any) with throw x had been supplied.

Note that this is not sequential testing. Sequential testing means that you have to write the general case last, not first, and makes things more order-dependant than they need be. Making the catch clauses share the semantics of procedure call makes the language more coherent and offers alternative implementation tactics.

4.1.22 memo expressions

this section is present for discussion as a placeholder for a possible future facility.

Evaluating the expression memo E delivers no values, but arranges that the expression E will be evaluated when the current scope is left, however it is left -- even if there's an exception thrown. Memoised expressions are evaluated in reverse order of their appearance.

If the current scope is a try expression body, its memo expressions are evaluated at the end of its catch blocks if an exception is thrown. If not, if an exception is throw, the memo expresssions are evaluated before the excetion "escapes" out to any enclosing try-expression.

4.1.23 if

An if expression expresses a choice. Both C-style (including the "ternary operator" E?E:E form) and new-style if-endif forms are supported.

def 68. IfExpr ::=
     NewIfExpr
     | Expr "?" CommaExpr ":" Expr
     | "if" "(" Expr ")" Statement [ "else" Statement ]

def 69. NewIfExpr ::=
        ( "if" | "unless" )
        Expr
        Then
        StatementSeq
        ( "elseif" Expr Then StatementSeq )
        * [ "else" StatementSeq ]
        ( "endif" | "endunless" )

def 70. Then ::= "then" | "do"

An if must have a matching endif, and an unless must have a matching endunless. then and do are equivalent in if and unless expressions.

4.1.24 aside: loop results and "all"

The while, until and for loops of Spice all deliver results. Normally, a loop delivers one result, either from its with result clause, or, if the loop terminates early using break, from the break expression. However, if the loop's do is followed by all, all of the results of the loop body are part of the loop result.

def 71. LoopDo ::= [ "with" "result" CommaExpr ] "do" [ "all" ]

All breaks in a loop with result must have associated Exprs, and if a loop has a break-with-Expr it must have a with result.

4.1.25 while and until

The until loop is equivalent to the while loop with the test inverted.

def 72. WhileExpr ::=
     "while" Expr LoopDo StatementSeq "endwhile"
     | "until" Expr LoopDo StatementSeq "enduntil"
     | "while" "(" Expr ")" Statement

4.1.26 for

def 73. ForExpr ::=
     "for" ForBindings LoopDo StatementSeq "endfor"
     | "for" "(" Name "in" Expr ")" StatementSeq
     | "for" "(" CIteration ")" Statement

def 74. CIteration ::= [ [ "var" ] Name "=" Expr ] SEMI OptExpr SEMI OptExpr

def 75. ForBindings ::= ( ForBinding | "while" Expr | "until" Expr ) ++ ","

A ForBinding binds names to sequences of values. The while and until forms allow the loop to be terminated early without having to use break.

def 76. ForBinding ::=
     [ "each" ] ForName "in" Expr [ ":" Type ]
     | ForName "from" Expr [ "to" Expr ] [ "step" Expr ]

def 77. ForName ::= Name [ TypeWord Type ]

For-loop identifiers are declared there, are immutable, and are local to the loop. What is more, a new identifier is bound each time round the loop (this is noticable only if the loop body forms closures using lambda-expressions or holes which involve the identifiers).

The meaning of a ForBindings is that each Name introduced is given successive values from its Expr, in parallel (ie on the Nth iteration each Name has its Nth value), until at least one of the Exprs is exhausted. If a Type on the Expr is given, the Expr should be of that type, and a type-specific iteration is done; otherwise it is as though it had been written as [Expr @explode]:Array.

The form from E1 to E2 is equivalent to in [E1..E2]. If step E is used, the increment between values is E rather than the default 1. If to E is omitted, then the loop is unbounded (it will terminate only via break or if a parallel iteration terminates).

The for-form for (Name in Expr) binds Name to each property of Expr in turn.

4.1.27 switch

def 78. SwitchExpr ::= "switch" Expr ( "{" Case ** ";" "}" | "into" Case ** ";" "endswitch" )

Switches are available in C-style or Spice-style.

def 79. Case ::= ( "case" CommaExpr ":" ) + StatementSeq | "default" [ ":" ] StatementSeq

Each case clause starts with some number of case expressions (or default) and is followed by the expressions to evaluate when the switch expression takes the value of one of those labels.

A Spice switch may switch on any values; it is not restricted to integer constants (although it may be much more efficient on them). In particular, a Spice switch may switch on strings and symbols.

A switch can have at most one default.

The result from a switch is the result from the selected case statement or statement sequence. Thus a switch may deliver multiple values.

4.1.28 brace expressions

A brace expression is just a syntactic grouping of statements, and delivers the value that statement sequence delivers.

def 80. BraceStatement ::= "{" StatementSeq "}"

4.1.29 var and procedure expressions

A VarDef and a ProcedureDef counts as expressions, but they deliver no values.

Note that this means that procedure definitions may be nested. A procedure may access the local variables of the procedure that it appears in; Spice has full lexical scope.

4.2 type expressions

A type expression is syntactically an expression. While the operators of a type expression look the same as those in a value expression, they have different meanings.

def 81. Type ::= Expr

The language of type expressions is intended to allow the programmer to give the compiler useful information about the program.

The basic type expression is the name. A name used as a type expression should be the name of one of the built-in types or a class. Any is the name of the universal type, Object is the name of all object types.

If T and U are type-expressions, the type-expression T|U is the union of the two types; any T or U will do.

If T is a type-expression, then T?? represents the optional type of T; it is the type of values which may be T or may be the value absent. absent is also spelt null.

If T and U are type-expressions, so is T,U, which represents the type of multiple values with first component(s) or type T and second component(s) of type U.

If T is a type expression then so is T**, the type of lots of multiple values all of type T.

If T is a type-expression, T[] ("array of T", "row of T") is the type of arrays who's elements are required to be of type T.

5 scope rules

Spice has simple scope rules.

All the Definitions in a module are mutually in scope. classes do not introduce new scopes; the declarations within a class put names into the top-level namespace. The same name cannot legally declared more than once, except that procedures with the same name may be defined multiple times with type-distinct parameter lists. (As slots are procedures, this means that different classes can have slots with the same name.)

A procedure definition introduces a new scope; the arguments and the body share that scope. (Thus is it illegal to have two arguments with the same name, or an argument to have the same name as a top-level local of the procedure.)

A LambdaExpr introduces a new scope. All the names from the surrounding scope are visible inside the lambda, with the same rights; in particular, it is permitted to assign to locals of the surrounding context, and for this to "work": Spice has full lexical scoping.

A try expression introduces a new scope which extends to the first catch clause (or to the endtry if there are no explicit catch clauses). A catch clause introduces a new scope which ends at the next [non-nested] catch or endtry.

A BraceStatement introduces a new scope which ends at its closing brace.

Each of the arms of an if or unless expression have their own scopes. The body of a while or until loop is a scope. (What's more, variables declared in the body of a loop are re-declared each time round the loop.)

Each SpiceCase introduces a new scope which ends at the end of its StatementSeq.

A for-do statement introduces a new scope. The identifiers declared in a ForBinding are in scope in any succeeding while or until clause, and in the loop body, but not in the Expr parts of other ForBindings of this loop. As for while and until loops, these identifiers are re-declared each time round the loop.

The right operand of an && or || or relational operator has its own scope. (This odd rule is because x && ((var y = 42), z) is a legal expression, but then questions would arise about y's existence or value if x were false.)

6 the standard library

Spice has a large library of standard values and procedures.

6.1 exceptions

(to be done)

6.2 generic procedures

6.3 numbers

Spice has several kinds of number: small integers (Int), big integers (BigInt), short and long floating point values (Float, Double), ratios (ie fractions) (Ratio), and complex numbers (Complex).

The operators +, -, *, /, div, rem, % work more-or-less as you'd expect on plain numbers (they work on some other values, too, for which see the section on units). div is integer division; the operands must be integers and the result is truncated toward 0. rem is integer remainder; the operands must be integers and the result is the appropriate remainder. / will produce a floating-point value if the result is not an exact integer.

The operator /: is exact division; its result uses ratios to express non-floating non-integer components. This is the primary way (the only way, at root) that ratio values are generated.

The operator .. ("upto") delivers multiple results: all the integers from the left operand (inclusive) to the right operand (also inclusive).

The bitwise operators &, |, ~, <<, >> work on integer operands only. >> does a signed right shift. To do an unsigned right shift, use the procedure bitSrl (x @bitSrl y shifts x right y places).

The relational operators <=, <, >=, >, ==, /== work as you'd expect. Note that it's unwise to use == and /== on floating-point values.

There are also a number of standard functions.

[various complex functions etc to be done]

6.4 enumerations

Enumeration values can be queried for their numeric value and name, and can be created from numeric values or names. Note that the number of enumeration values is fixed by the declaration; the constructor delivers existing values, not fresh ones.

6.5 values with units

Spice has values with units. Values with units represent lengths, or times, or other inter-related quantities. All the unit procedures are imported qualified from Spice.language.units.

A unit expresses an amount along a given dimension. A basic dimension is represented by a symbol naming that dimension. A compound dimension is the result of a product or quotient of dimensions.

The arithmetic operators also work on values with units. There is a minor complication in that different units of the same kind may be inter-converted. Multiplication and division also work on Unit objects themselves to produce new Unit objects; this is intended for use in define unit definitions.

6.6 strings

Strings are sequences of Unicode characters in canonical form. Strings created as literal tokens are immutable. Strings, like arrays, can be indexed using the s[i] notation. The infix operator <> concatenates strings (and arrays).

There are many built-in functions on strings. Note that strings, like arrays, can be rebased.

6.7 symbols

In Spice, the symbol literal `foo` represents a symbol (a value of type Symbol) whose spelling is the string foo. The important difference between symbols and strings is that there is only one symbol with a given spelling. (You can tell the difference using the Spice identity operator ===.)

You can index symbols using s[i], but if you do so, you are probably using symbols for something other than their intended use; supplying the programmer with an infinite set of mnenomic values.

6.8 booleans

The built-in operators !, &&, || on booleans operate as you might expect; ! negates its operand, && is boolean and and || is boolean or, neither of which evaluate their second operand unless it's necessary.

6.9 arrays

Array elements can be accessed and updated using the a[i] notation.

6.10 locales

[locales and things that depend on them to be specified]

6.11 dates

[date procedures to be specified]

6.12 regular expressions

[placeholder for regular expressions]

6.13 procedures

There are several standard functions on procedures.

The different kinds of procedures can be recognised by predicates.

6.14 dictionaries and tables

The type Lookup is the parent type of a variety of mapping data types; the simple mapping types map single values to single values, while the compond mapping types map tuples of values to tuples of values. There are no direct instances of Lookup. The type Maplet is the type of pairs of (simple) values. The type Dictionary is an extension of Lookup that maps Symbols to values.

6.15 io

Spice includes some simple I/O operations in its core; the type Pathname, the type File, and the type Stream.

6.15.1 pathname syntax and accessors

A pathname has several components. The basic elements are Words which, within this section, are sequences of characters not otherwise reserved to the pathname syntax.

In this section, the identifier p is presumed to contain a Pathname value.

def 82. Pathname ::= [ Scheme ] [ Root ] [ Directories ] [ Name ] [ Suffix ] [ Type ]

def 83. Scheme ::= Word ":"

The Scheme of a pathname directs how the components are to be interpreted. The standard schemes are file, http, and socket.

p.pathScheme is a Symbol.

def 84. Root ::= "//" Dotted

The Root identifies where the pathname is anchored. For an http or net pathname, the Root is the IP address where the named entity is located. For a file scheme, the Unit identifies some root in the filing system in an implementation-specific way.

p.pathRoot is an array of Symbol values, one for each word-part of the Dotted.

def 85. Directories ::= [ "/" ] ( Dotted "/" ) ++

The Directories identify some place within the Root where an entity is found, by giving a sequence of Dotteds. For a file scheme, thet are the names of filing-system directories. For an http scheme, they are successive components of the directory part of the URL. For a socket scheme, they identify the port number to be used for the socket; typically there is but one Dotted and it names an IP service.

p.pathDirectories is an array of Symbols, each symbol being the spelling of a Dotted.

def 86. Name ::= Word

The Name is the leafname of the entity being described.

p.pathName is a Symbol.

def 87. Suffix ::= "." Word

The Suffix is the suffix part of the entity name.

p.pathSuffix is a Symbol.

def 88. Type ::= ";" Word

The Type is the type of the entity. When a pathname is being used to create an object, is specifies the type of object to create. When it is being used to access an existing object, the object should be of a compatible type.

p.pathType is a Symbol.

def 89. Dotted ::= Word ++ "."

6.16 types

The standard types are

7 appendix: limited spice

Not all environments will be able to implement the full Spice language. This section will define some "natural boundaries" along which the language can be carved. The programmer can make explicit that support for a feature is required by explicitly importing the relevant Spice module; Spice implementations that do not offer a feature must reject the import.

An implementation may omit support for complex numbers. In this case, operations that would generate them should throw the exception noComplexSupport.

An implementation may omit support for ratios. In this case, operations that would generate them (ie, /:) should throw the exception noRatioSupport.

An implementation may omit support for BigInt. In this case, operations that would generate them should throw the exception noBigNumSupport.

An implementation may omit support for styles. In this case, the compiler will reject code that uses styles with the apology noStyleSupport.

An implementation may omit support for property. In this case, the compiler will reject property declarations with the noPropertySupport apology.

An implementation may omit support for units. In this case, the compiler will reject unit literals and define unit definitions with the noUnitSupport apology. The type Unit and the unit-specific procedures will not be declared.

An implementation may omit support for exception-handling. In this case, the compiler will reject try expressions with the noExceptionSupport apology. throw expressions will be permitted, but their effect is to terminate execution up to the "top level" of the implementation; the thrown value may be discarded, or displayed, or some other implementation-specific action may be taken.

An implementation may omit support for memo; the compiler will reject any memo expressions with the apology noMemoSupport.

An implementation may omit support for multiple-valued loops. In this case, loops containing do all will be rejected with the apology noLoopSupport.

An implementation may omit support for multiple values (apart from procedure arguments). In this case, the compiler will reject the use of commas, apart from in argument lists and in expressions whose values are discarded, with the noMultipleValueSupport apology.

An implementation may omit support for multiple inheritance (ie require that a class extends clause can mention only one name). In this case, it will reject multiply-inheriting class with the apology noMultipleInheritanceSupport.

An implementation may omit support for multi-methods (ie procedures that dispatch on more than one argument). In this case, the compiler will reject procedures that are overloaded on multiple arguments. with the apology noMultiDispatchSupport. However, it will accept procedures that are overloaded on any single argument -- it doesn't have to be the first.

An implementation may omit support for procedures overloaded on anything but their first argument. The compiler will reject procedures that overload other arguments with the apology noAnyDispatchSupport.

An implementation may omit support for overloading completely, only allowing [only] procedures and rejecting other procedures with the apology noOverloadSupport. (Similarly, developers may reject the compiler with the noWaySunshine retort.)

8 appendix: spice standard modules

Spice compes with a collection of standard modules. Most of them are automaticlly imported by the compiler (except in limited implementations), but it is permitted to export them explicitly.

"module name" "description"
"Spice.core.complex" "handling complex arithmetic"
"Spice.core.ratios" "handling rational numbers"
"Spice.core.bignums" "handling big integers"
"Spice.core.styles" "style operations"
"Spice.core.units" "define unit definitions and operations"
"Spice.core.properties" "property declarations and operations"
"Spice.core.multimethods" "support for multimethods"
"Spice.core.multivalues" "support for multiple values"
"Spice.core.monomethods" "support for monomethods (single-argument polymorphism)"
"Spice.core.multiheritance" "support for multiple inheritance"
"Spice.core.dates" "the Date type and its operations"
"Spice.core.io" "basic input/output support, including pathnames"
"Spice.core.files" "file input/output support"
"Spice.core.net" "network input/output support"
"Spice.core.tables" "lookup table support"

9 their own destiny

Other things that Spice may or may not feature:

10 appendix: syntactic summary

This section gathers together all the syntax from the rest of the document.

def 1. Program ::= Spice ( Module * | ModuleBody )

def 2. Spice ::= "spice" String ( "," ( Name ":" Expr ) ) *

def 3. Qualifier ::= "[" CommaExpr "]"

def 4. Module ::= "module" ModuleName ModuleBody [ "endmodule" ]

def 5. ModuleName ::= Word ++ "."

def 6. ModuleBody ::= Import ** SEMI Definition ** SEMI

def 7. Definition ::=
     StyleDef
     | ProcedureDef
     | VarDef
     | ClassDef
     | UnitDef
     | EnumDef
     | PropertyDef
     | Expr

def 8. Import ::= "import" ( ImportItem [ "from" Expr ] ) ** ","

def 9. ImportItem ::= [ Qualifier ] ( ModuleName | "(" Expr ")" )

def 10. StyleDef ::= Mode | Media | Style

def 11. Mode ::=
     "define" "mode" Name ( "{" Style * "}" | Style * "enddefine" )
     | "mode" Name "{" Style * "}"

def 12. Media ::=
     "define" "media" Name ( "{" Style * "}" | "enddefine" )
     | "media" Name "{" Style * "}"

def 13. Style ::=
    
        "define"
        "style"
        Selectors
        ( "{" Property * "}" | "=" Property * "enddefine" )
     | "style" Selectors "{" Property * "}"

def 14. Property ::= Name [ ":" PropValue | "=>" Expr ] SEMI

def 15. PropValue ::= ( PropSeq [ "with mode" Name ] [ "!" ] ) ++ ","

def 16. PropSeq ::= Term ++

def 17. Term ::= LimitedExpr | BraceStatement

def 18. Selectors ::= Related ++ ","

def 19. Related ::= Sibling * [ ":" Name ]

def 20. Sibling ::= Children ++ "+"

def 21. Children ::= Element ++ ">>"

def 22. Element ::= "(" Selectors ")" | Tag [ Attribution ]

def 23. Tag ::= Word | Symbol | "*"

def 24. Attribution ::= "[" AttrValue "]" | "." Name

def 25. AttrVal ::= Word [ "[" AttrBinding ** "," "]" ]

def 26. AttrBinding ::= ( "=" | "~=" ) Expr

def 27. PropertyDef ::= "property" SimpleDecl

def 28. SimpleDecl ::= Name [ Qualifier ] [ "=" Expr ] [ TypeWord Type ]

def 29. ProcedureDef ::= SompleFunctionDef | NewProcedureDef

def 30. SimpleFunctionDef ::= "function" Header BraceStatement

def 31. NewProcedureDef ::=
        "define"
        ( "method" [ "new" ] | "function" )
        Header
        [ "throws" Expr ]
        [ "returns" Type ]
        [ "extends" Header ]
        ProcedureBody

def 32. Header ::= [ Qualifier ] [ "on" Type ] CallShape [ "=" Arg ]

def 33. CallShape ::= PrefixShape | DottedShape | InfixShape

def 34. PrefixShape ::= NameA [ Arglist ]

def 35. DottedShape ::= NameB . NameA [ Arglist ]

def 36. InfixShape ::= ArgA @ NameA [ ArgB ]

def 37. Arglist ::= "(" Args ")"

def 38. Args ::= Arg ** ","

def 39. Arg ::= Name [ TypeWord [ "!" ] Type | "==" Expr ] | [ Name ] "..."

def 40. TypeWord ::= "is" | "as" | ":"

def 41. ProcedureBody ::= BraceStatement | "=>" StatementSeq "enddefine"

def 42. VarDef ::= ( "const" | "var" ) NameDecl

def 43. NameDecl ::= [ Qualifier ] SingleDecl ++ "," [ "is" Type | "are" Type ]

def 44. SingleDecl ::= ( OneDecl | "(" OneDecl ++ "," ")" ) [ "=" Expr ]

def 45. OneDecl ::= Name [ ":" Type | "..." ]

def 46. ClassDef ::=
        "define"
        "class"
        [ Qualifier ]
        Name
        [ "extends" CommaExpr ]
        ( "{" ClassProperty ** SEMI "}" | ClassProperty ** SEMI "enddefine" )

def 47. ClassProperty ::= Procedure | SlotDecl | VarDef | Property

def 48. SlotDecl ::= ( "slot" | "shared" ) SimpleDecl

def 49. UnitDef ::= "define" ( "unit" | "units" ) UnitDef ++ "," "enddefine"

def 50. UnitDef ::= Word [ "=" Expr ]

def 51. EnumDef ::= "define" "enum" [ Qualifier ] Name "=" Name ++ "," "enddefine"

def 52. Statement ::= Expr ++ "," SEMI

def 53. StatementSeq ::= Statement *

def 54. Expr ::=
     Name
     | Expr "{" UnitExpr "}"
     | Hole
     | Literal
     | Expr "..."
     | PreOp Expr
     | Expr PostOp
     | Expr InOp Expr
     | Expr AssignOp Expr
     | "(" Expr ")"
     | Expr [ "." DotExpr ] [ "(" OptCommaExpr ")" ]
     | Expr "[" Expr "]"
     | "[" Expr "]"
     | LambdaExpr
     | "op" ( PreOp | InOp | PostOp )
     | "new" Name [ "(" Expr ")" ]
     | "none" OptExpr
     | "return" [ [ "with" ] CommaExpr ]
     | "break" [ [ "with" ] CommaExpr ]
     | "continue"
     | "throw" Expr
     | TryCatchExpr
     | "memo" CommaExpr
     | IfExpr
     | WhileExpr
     | ForExpr
     | SwitchExpr
     | BraceStatement
     | VarDef
     | ProcedureDef

def 55. OptExpr ::= [ Expr ]

def 56. CommaExpr ::= Expr ++ ","

def 57. OptCommaExpr ::= [ CommaExpr ]

def 58. UnitExpr ::= ( Word ** "*" | _ 1 _ ) [ "/" Word ** "*" ]

def 59. Hole ::= "_" [ Integer ]

def 60. Literal ::=
     StringLiteral
     | SymbolLiteral
     | NumberLiteral
     | ReservedLiteral
     | Character

def 61. Character ::= Backslash ( String | Word )

def 62. PostOp ::= LexicalPostfixOperator | "@" ( Word | "(" Expr ")" )

def 63. InOp ::= LexicalInfixOperator | "@" ( Name | "(" Expr ")" )

def 64. DotExpr ::= Name | "(" Expr ")"

def 65. LambdaExpr ::= "(" Args "=>" StatementSeq ")"

def 66. TryCatchExpr ::= "try" StatementSeq CatchSeq "endtry"

def 67. CatchSeq ::= ( "catch" Arglist [ "with" ] StatementSeq ) **

def 68. IfExpr ::=
     NewIfExpr
     | Expr "?" CommaExpr ":" Expr
     | "if" "(" Expr ")" Statement [ "else" Statement ]

def 69. NewIfExpr ::=
        ( "if" | "unless" )
        Expr
        Then
        StatementSeq
        ( "elseif" Expr Then StatementSeq )
        * [ "else" StatementSeq ]
        ( "endif" | "endunless" )

def 70. Then ::= "then" | "do"

def 71. LoopDo ::= [ "with" "result" CommaExpr ] "do" [ "all" ]

def 72. WhileExpr ::=
     "while" Expr LoopDo StatementSeq "endwhile"
     | "until" Expr LoopDo StatementSeq "enduntil"
     | "while" "(" Expr ")" Statement

def 73. ForExpr ::=
     "for" ForBindings LoopDo StatementSeq "endfor"
     | "for" "(" Name "in" Expr ")" StatementSeq
     | "for" "(" CIteration ")" Statement

def 74. CIteration ::= [ [ "var" ] Name "=" Expr ] SEMI OptExpr SEMI OptExpr

def 75. ForBindings ::= ( ForBinding | "while" Expr | "until" Expr ) ++ ","

def 76. ForBinding ::=
     [ "each" ] ForName "in" Expr [ ":" Type ]
     | ForName "from" Expr [ "to" Expr ] [ "step" Expr ]

def 77. ForName ::= Name [ TypeWord Type ]

def 78. SwitchExpr ::= "switch" Expr ( "{" Case ** ";" "}" | "into" Case ** ";" "endswitch" )

def 79. Case ::= ( "case" CommaExpr ":" ) + StatementSeq | "default" [ ":" ] StatementSeq

def 80. BraceStatement ::= "{" StatementSeq "}"

def 81. Type ::= Expr

def 82. Pathname ::= [ Scheme ] [ Root ] [ Directories ] [ Name ] [ Suffix ] [ Type ]

def 83. Scheme ::= Word ":"

def 84. Root ::= "//" Dotted

def 85. Directories ::= [ "/" ] ( Dotted "/" ) ++

def 86. Name ::= Word

def 87. Suffix ::= "." Word

def 88. Type ::= ";" Word

def 89. Dotted ::= Word ++ "."

11 appendix: operator and symbol table

symbol precedence associativity description
_ NONE NONE "hole marker in partial application"
... NONE NONE "trailing values of argument list"
=> 10 NONE "marker in procedure definitions and expressions"
. 170 left "property lookup pseudo-operator"
"[]" 160 left "property lookup/indexing pseudo-operator"
"()" 160 left "procedure call pseudo-operator"
"++, --" 150 "pre/post" "increment and decrement"
"-, ~" 150 pre "minus, bitwise complement"
"!" 150 left "logical not (cannot use not)"
typeof 150 left "get run-time type of value"
new 150 left "part of constructor invocation"
unitName 150 post "unit conversion operator"
% 150 post "percentage unit conversion operator"
** 145 left "raise-to-the-power-of"
"*, /, /:, div, rem" 140 left "multiplication, division"
"+, -" 130 left "addition, subtraction"
"<>" 130 left "string concatenation"
"<<, >>, >>>" 120 left "bitwise shifting"
".." 120 left "range construction"
"<, <=, >, >=, ==, !=, /==, ===, /===, ==" 110 left "relationals"
"&" 100 left "bitwise and"
"^" 90 left "bitwise xor"
"|" 80 left "bitwise or"
"@" 75 left "infix function call marker"
"&&" 70 left "logical and (cannot use and)"
"||" 60 left "logical or (cannot use or)"
= 40 right "assignment"
"XX=" 30 right "assign-and-op for XX=dyadic op"
"," 20 left "sequential evaluation"

12 appendix: reserved words

keyword description
all "specifies that a loop returns its body's results"
are "specifies type of properties or identifers"
break "break statement; break from loop, optionally with result"
const "declares an immutable variable"
catch "specifies an exception-handling clause"
continue "continue statement; start next iteration"
define "introduces a definition"
do "punctuation in while and for loops"
each "punctuation in for-loops"
else "introduces else-part of if-expression"
elseif "introduces else-if part of if-expression"
endfor "closes a for-loop"
endif "closes if-expression"
endmodule "(optional) ends module body"
endtry "ends a try-expression"
endwhile "ends a while-loop"
enduntil "ends an until-loop"
endunless "closes an unless expression"
extends "specifies parent of a class"
for "introduces a for-loop"
foreign "part of foreign-function interface"
from "part of import, part of for-loop"
function "declares a function"
if "introduces if-expression"
implements "reserved for class expansion"
import "specifies other modules to import"
in "punctuation in for-loops"
import "import a module's public interface into this module"
is "specifes type of identifier"
media "introduces media-constrained styles"
method "declares a method"
memo "specifies an expression to evaluate on scope exit"
mode "introduces mode-constrained styles"
module "names module"
new "part of constructor declaration/invocation"
none "delivers no values"
on "gives type of first argument in procedure header"
op "converts operator into its procedure value"
class "introduces a new class"
property "declares a property of a class"
result "part of loop-result syntax"
return "return statement; exit from procedure"
returns "specifies return type of procedure"
shared "declares a class-specific slot of a class"
slot "declares a instance-specific slot of a class"
spice "identifies version, gives preferences"
step "punctuation in for-loops"
style "introduces style-sheet"
then "introduces then-part of if-expression"
throws "reserved for exception declaration"
to "punctuation in for-loops"
try "introduces a try-expression for controlling exceptions"
unless "an if with inverted test condition"
until "a while with inverted test condition"
var "declares a mutable top-level or local variable"
while "introduces a while-loop"
with "general punctuation marker"