Phil Winterbottom philw@research.att.com
Much of the terminology used in this manual is borrowed from the C language reference manual and the Plan 9 manual. The manual expects familiarity with both.
adt aggr alloc
alt become break
byte case chan
check continue default
do else enum
extern float for
goto if int
intern lint nil
par proc raise
rescue return sint
sizeof switch task
tuple typedef uint
ulint unalloc union
usint void while
The following symbols are used as separators and operators in the language:
+ - / =
> < | %
& | ? .
" ' { }
[ ] ( )
* ;
The following multi-character sequences are used as operators:
+= -= /= *=
%= &= |= ^=
<<= >>= == !=
-- <- -> ++
/*
and finishes at the characters */
.
A comment may include any sequence of characters including /*
.
Comments do not nest.
constant: integer-const character-const floating-const string-const rune-string-constAn integer constant is a sequence of digits. A prefix may be used to modify the base of a number. Defined prefixes, bases and digit sets are:
A character constant may contain one or more characters surrounded by the single quote marknone
decimal0-9
0x
hexadecimal0-9 a-f A-F
0
octal0-7
'
. If the constant contains two or more
characters the first must be the escape character \
. The
following table shows valid characters after an escape and the value of the
constant:
0 | NUL | Null character |
n | NL | Newline |
r | CR | Carriage return |
t | HT | Horizontal tab |
b | BS | Backspace |
f | FF | Form feed |
a | BEL | Buzz |
v | VT | Vertical tab |
\ | \ | Backslash |
" | " | Double quote |
Rune(2)
in the Plan9 manual) the value of the integer will be the
16-bit unicode representation of the rune.
A floating point constant consists of an integer part, a period, a
fractional part, the letter e and an exponent part. The integer, fraction
and exponent parts must consist of decimal digits. Either the integer or
fractional parts may be omitted. Either the decimal point or the letter e
and the exponent may be omitted. The integer part or period and the
exponent part may be preceded by the unary +
or -
operators. Floating point constants have the type float.
A string constant is a sequence of characters between the double quote
marks "
. A string has the type 'static array of byte'. The
NUL
character is automatically appended to the string by the compiler. The
effect of modifying a string constant is implementation dependent. The
sizeof operator applied to a string constant yields the number of bytes
including the appended NUL
.
A rune string constant is a sequence of unicode characters introduced by
$"
and terminated by "
. A rune string has the
type 'static array of usint'. A zero rune character is automatically
appended to the string by the compiler. The sizeof operator applied to a
rune string constant yields the number of runes including the appended
zero.
A group of tasks executing within the context of a process are defined to be in the same address space. Tasks are scheduled during communication and synchronisation operations. The term thread is used wherever the distinction between a process and task is unimportant.
name | size | type |
---|---|---|
byte | 8 bits | unsigned character |
sint | 16 bits | signed short integer |
usint | 16 bits | unsigned short integer |
int | 32 bits | signed integer |
uint | 32 bits | unsigned integer |
float | 64 bits | floating point |
lint | 64 bits | long signed integer |
ulint | 64 bits | unsigned long integer |
chan | 32 bits | channel |
* create a pointer to & yield the address of () a function returning [] an array ofThese operators bind to the name of each identifier in a declaration or definition. Some examples are:
int *ptr; /* A pointer to an integer */ char c[10]; /* A vector of 10 characters */ float *pow(); /* A function returning a pointer to a float */Complex types may be built from the basic types and the deriving operators. Complex types may be either aggregates, unions or abstract data types (ADT). These complex types contain sequences of basic types and other derived types. An aggregate is a simple collection of basic and derived types. Each element of the aggregate has unique storage. An abstract data type has the same storage allocation as an aggregate but also has a set of functions to manipulate the type, and a set of protection attributes for each of its members. A union type contains a sequence of basic and derived types which occupy the same storage. The size of a union is determined by the size of the largest member.
The declaration of complex types introduces typenames into the language. After declaration a type-name can be used wherever a basic type is permitted. Derived types and basic types may be renamed using the typedef statement.
The integral types are int, uint, sint, suint, byte, lint and ulint. The arithmetic types are the integral types and the type float. The pointer type is a type derived from the & (address of) operator or derived from a pointer declaration.
program: declaration-list declaration-list: declaration declaration-list declarationA declaration can define a simple variable, a function, a prototype to a function, an ADT function, a type specification or a type definition:
declaration: simple-declarations type-declaration type-definition function-declaration
simple-declarations: type-specifier simple-decl-list;
simple-decl-list: simple-declaration simple-decl-list,
simple-declaration simple-declaration: pointeropt identifier array-specopt pointeropt identifier array-specopt=
initialiser-list pointer:*
pointer*
array-spec:[
constant-expression]
[
constant-expression]
array-spec
type-specifier: storage-classopt type type:The storage class controls the scope of the declaration. Storage classes may only be applied to declarations at the file scope. The scope of a definition qualified with intern is file. A declaration qualified by extern references a definition in this or another file.byte
int
uint
sint
usint
lint
ulint
void
float
typename channel-specifier storage-class:intern
extern
channel-specifier:chan
(
chan-type)
buffer-specopt buffer-spec:[
constant-expression]
Typename is an identifier defined by a complex type declaration or a typedef statement.
chan-type:
basic
basic ,
chan-type
basic:
type pointeropt
The type specified by a chan declaration is actually a pointer to an
internal object with an anonymous type specifier. Because of their
anonymity, objects of this special type cannot be defined in declarations;
instead they must be created by an alloc statement referring to a chan. A
channel declaration without a buffer specification produces a synchronous
communication channel. Threads sending values on the channel will block
until some other thread receives from the channel. The two threads
rendezvous and a value is passed between sender and receiver. If buffers
are specified then an asynchronous channel is produced. The
constant-expression defines the number of buffers to be allocated. A send
operation will complete immediately while buffers are available. A thread
will block if all buffers are in use. A receive operation will block if no
value is buffered. If a value is buffered the receive will complete and
deallocate the buffer. Any senders waiting for buffers will then be
allowed to continue.
Values of chan-type are passed between threads using the channel for communication. If chan-type is a comma separated list of types the channel supplies a .variantprotocol A variant protocol allows messages to be demultiplexed by type during a receive operation. A form of the alt statement allows the control flow to be modified based on the type of a value received from a channel supplying a variant protocol.
initialiser-list: constant-expressionAn initialisation consists of a constant-expression or a list of constant-expressions separated by commas and enclosed by braces. An array or complex type requires an explicit set of braces for each level of nesting. All the components of a variable need not be explicitly initialised; uninitialised elements are set to zero. ADT types are initialised in the same way as aggregates with exception of ADT function members which are ignored for the purposes of initialisation. Elements of sparse arrays can be initialised by supplying a bracketed index for an element. Successive elements without the index notation continue to initialise the array in sequence. For example:[
constant-expression]
constant-expression{
initialiser-list}
initialiser-list,
initialiser-list
char a[256] = { ['a'] 'A', /* Set element 97 to 65 */ ['a'+1] 'B', /* Set element 98 to 66 */ 'C' /* Set element 99 to 67 */ };If the dimensions of the array are omitted from the array-spec the compiler sets the size of each dimension to be large enough to accommodate the initialisation. The size of the array can be found using sizeof.
type-declaration: complex typenameA complex type is composed of a list of members. Each member may be a complex type, a derived type or a basic type. Members are referenced by tag or by type. Members without tags are called unnamed. Unnamed members are referenced by type or by implicit promotion during assignment or when supplied as function arguments. A type declaration must have either a type name or a tag.{
memberlist} ;
complex{
memberlist}
decl-tag;
complex typename{
memberlist}
decl-tag;
enumeration-type tupletype complex:adt
aggr
union
decl-tag: identifier tupletype: tupleopt(
typelist)
typelist: typelist,
type-declaration
memberlist: member memberlist member member: tname pointeropt decl-tag array-specopttname is one of the basic types or a new type introduced by aggr, adt, union, or typedef. A tuple type is a complex type whose members are all unnamed. Tuples may only be addressed by assignment into other complex types or an l-valued tuple expression. For example:;
tname decl-tag(
arglist) ;
(int, byte*) func() { return (10, "hello"); } void main() { int a; byte *str; (a, str) = func(); }This example demonstrates multiple return values. The return type of func is a complex type consisting of three integers. The tuple type returned by func will match any other complex type consisting of three arithmetic types. So the following is legal:
aggr T { int a; byte b; float c; }; void main() { (a, b, c) = func(); }
adt Point { int x; /* Access by member functions only */ extern int y; /* Access by everybody */ Point set(Point*); /* Access by everybody */ intern Point tst(Point); /* Access only from Point.set */ };Member functions are defined by type and name. The pair form a unique name for the function, so the same member function name can be used in many types. Using the last example, the member function set could be defined as:
Point Point.set(Point *a) { a->x = 0; /* Set the value of the point to zero */ a->y = 0; return *a; }An implicit pointer to the abstract data type may be passed to a member function. If the first argument of the member function declaration in the ADT specification is `* typename' (Note the * precedes the name) the first parameter is passed implicitly.
adt Point { int x; extern int y; Point set(*Point); /* Pass &Point as first argument */ intern Point tst(Point); }; void func() { Point p; p.set(); /* Set receives &p as first argument */ }The implicit parameter passing mechanism is particularly useful when the ADT is an unnamed substructure. The receiving function is defined as:
void Point.set(Point *p) { ... };
enumeration-type: enum typenameEnumerations are types whose value is limited to a set of integer constants. The members of an enumeration are called enumerators. Enumeration variables are equivalent to integer variables. Enumeration constants may appear wherever an integer constant is legal. If the values of the enumerators are undefined the compiler assigns incrementing values from 0. If a value is given to an enumeration constant, values are assigned to the following enumerators by incrementing the value for each successive member until the next assigned value is reached.{
enum-list} ;
enum-list: identifier identifier=
constant-expression enum-list,
enum-list
type-definition:
typedef
tname identifier;
A typedef is required to declare complex types with mututally dependent
pointers. ALEF does not permit mutually dependent complex types; only
references between them. For example:
typedef aggr A; aggr B { A *aptr; B *bptr; } aggr A { A *aptr; B *bptr; }
function-declaration: type-specifier identifierIf a formal parameter is declared without an identifier no declaration for the corresponding variable is produced in the function body.(
arglist)
block type-specifier function-id(
arglist) ;
type-specifier(
function-id) (
arglist) ;
function-id: pointeropt identifier array-specopt adt-function adt-function: typename.
decl-tag arglist: arg pointer type arglist,
arg arg: type type pointer type(
pointer) (
arglist)
type simple-declaration...
The behaviour of exceptional conditions such as divide by zero, arithmetic overflow and floating point exceptions is not defined.
primary-expression:
identifier
constant
...
nil
( expression )
tuple
The parameters received by a function taking variable arguments are
referenced using '...'. The primary-expression '...' yields a value of type
`pointer to void'. The value points at the first location after the formal
parameters. The primary-expression nil returns a pointer of type `pointer
to void' of value 0 which is guaranteed not to point at an object.
tuple: ( tlist ) tlist: tlist , expressionWhen the declaration of a tuple would be ambiguous because of the parenthesis (for instance in automatics) use the keyword tuple:
void f() { int a; tuple (int, byte, Rectangle) b; int c; }Type checking of tuple expressions is performed by matching the shape of each of the component types. A tuple will match another tuple or complex type if each individual member of the tuple could legally be assigned to its corresponding member under the rules of assignment.
postfix-expression: primary-expression postfix-expression[
expression]
postfix-expression(
argument-list)
postfix-expression.
tag postfix-expression->
tag postfix-expression++
postfix-expression--
postfix-expression?
tag: typename identifier argument-list: expression argument-list,
expression
*
((postfix-expression)+
(expression)). One
of the expressions must be of type pointer; the other of integral type.
aggr Test { int t; Lock; /* Unnamed substructure */ }; Test yuk; /* Definition of complex Test */ void lock(Lock*); /* Prototype for function lock */ void main() { lock(&yuk); /* address of Lock in Test is passed */ }
unary-expression:
postfix-expression
++ unary-expression
-- unary-expression
unary-operator cast-expression
sizeof
cast-expression
unary-operator: one of
<- ? *
+ - ~ !
The prefix or postfix blocking test operator ? is only reliable when used on a channel shared between tasks in a single process. A process may block after a successful ? because the interleaving of the processes using the channel is undefined.
cast-expression: unary-expression ( type-cast ) cast-expression cast-type: type pointer
multiplicative-expression: cast-expression multiplicative-expression * multiplicative-expression multiplicative-expression / multiplicative-expression multiplicative-expression % multiplicative-expressionThe operands of * and / must have arithmetic type. The operands of % must be of integral type. The operator / yields the quotient, % the remainder and * the product of the operands. If b is non-zero then a = (a/b) + a%b should always be true.
additive-expression: multiplicative-expression additive-expression + multiplicative-expression additive-expression - multiplicative-expressionThe + operator computes the sum of its operands. Either one of the operands may be a pointer. If P is an expression yielding a pointer to type T then P+n is the same as p+(sizeof(T)*n). The - operator computes the difference of its operands. The first operand may be of pointer or arithmetic type. The second operand must be of arithmetic type. If P is an expression yielding a pointer of type T then P-n is the same as p- (sizeof(T)*n). Thus if P is a pointer to an element of an array P+1 will point to the next object in the array and P-1 will point to the previous object in the array.
shift-expression: additive-expression shift-expression << additive-expression shift-expression >> additive-expressionIf the first operand is unsigned, << performs a logical left shift by additive-expression bits. If the first operand is signed, << performs an arithmetic shift left by additive-expression bits. The shift-expression must be of integral type. The >> operator is a right shift and follows the same rules as left shift.
relational-expression: relational-expression < shift-expression relational-expression > shift-expression relational-expression <= shift-expression relational-expression >= shift-expressionThe operators are < (less than), > (greater than), <= (less than or equal to) and >= (greater than or equal to). The operands must be of arithmetic or pointer type. The value of the expression is 1 if the relation is true, otherwise 0. The usual arithmetic conversions are performed. A pointer of type void * or nil may be compared to any other pointer. Pointers of any other type may only be compared to pointers of the same type.
equality-expression: relational-expression relational-expression == equality-expression relational-expression != equality-expressionThe operators == (equal to) and != (not equal) follow the same rules as relational operators.
AND-expression: equality-expression AND-expression & equality-expression XOR-expression: AND-expression XOR-expression ^ AND-expression OR-expression: XOR-expression OR-expression | XOR-expressionThe operators perform bitwise logical operations and apply only to integral types. The operators are & (bit-wise and), ^ (bitwise exclusive or) and | (bitwise inclusive or).
logical-AND-expression: OR-expression logical-AND-expression && OR-expression logical-OR-expression: logical-AND-expression logical-OR-expression || logical-AND-expressionThe && operator returns 1 if both of its operands evaluate to non-zero, otherwise 0. The || operator returns 1 if either of its operand evaluates to non-zero, otherwise 0. Both operators are guaranteed to evalu-ate strictly left to right. Evaluation of the expression will cease as soon as any component of the expression evaluate to a 1. The operands can be any mix of arithmetic and pointer types.
constant-expression: logical-OR-expressionconstant-expression appears as part of initialisation, channel buffer specifications and array dimensions. The following operators may not be part of a constant expression: function calls, assignment, send, receive, increment and decrement. Address computations using the & (address of) operator on static declarations is permitted.
assignment-expression: logical-OR-expression unary-expression <-= assignment-expression unary-expression assignment-operator assignment-expression unary-expression = ( type-cast ) tuple assignment-operator: one of = += *= /= %= &= |= ^= >>= <<=The left side of the expression must be an l-value. Compound assignment allows the members of a complex type to be assigned from a member list in a single statement. A compound assignment is formed by casting a tuple into the complex type. Each element of the tuple is evaluated in turn and assigned to its corresponding element in the complex types. The usual conversions are performed for each assignment.
aggr Readmesg /* Read message to file system */ { int fd; void *data; int len; }; chan (Readmsg) filesys; int read(int fd, void *data, int len) { /* Pack message parameters and send to file system */ filesys <-= (Readmesg)(fd, data, len); }If the left side of an assignment is a tuple, selected members may be discarded by placing nil in the corresponding position in the the tuple list. In the following example only the first and third integers returned from func are assigned.
(int, int, int) func(); void main() { int a, c; (a, nil, c) = func(); }The <-= (assign send) operator sends the value of the right side into a channel. The unary-expression must be of type `channel of T'. If the left side of the expression is of type `channel of T', the value transmitted down the channel is the same as if the expression were `object of type T = expression'.
binding operator l to r () [] -> . r to l ! ~ ++ -- <- ? + - * & (cast) sizeof l to r * / % l to r + - l to r << >> l to r < <= > >= l to r == != l to r & l to r ^ l to r | to r && l to r || l to r <-= = += -= *= /= %= = ^= |= <<= >>=
statement: label-statement : expression-statement ; block-statement selection-statement ; loop-statement jump-statement exception-statement process-statement ; allocation-statement ;
block:An identifier declared in autolist suspends any previous declaration of the same identifier. An identifier may be declared only once per block. The declaration remains in force until the end of the block, after which any suspended declaration comes back into effect.{
autolist slist}
!{
autolist slist}
autolist: declaration autolist declaration slist: statement slist statement
The value of identifiers declared in autolist is undefined at block entry and should be assigned to a known value after declaration but before use.
The symbol !{ introduces a guarded block. Only one thread may be executing the statements contained in the guarded block at any instant.
selection-statement:Anif (
expression)
statementelse
statementif (
expression)
statementswitch (
expression)
cbodyalt
cbody cbody:{
caselist}
!{
caselist}
caselist: case-item alt-item caselist case-item case-item:case
constant-expression:
statementdefault :
statement alt-item:case
expression:
statement
if
statement first evaluates expression, which must yield a
value of arithmetic or pointer type. The value of expression is compared
with 0. If it compares unequal statement is executed. If an else clause is
supplied and the value compares equal the else statement will be
executed. The else clause shows an ambiguity in the grammar. The ambiguity
is resolved by matching an else with the nearest if without an else at the
same block level.
The switch
statement selects one of several statements. The
expression is evaluated and converted into an integer. The integer is
compared with the value specified in each case. If the integers compare
the statement part of the case is executed. The case
expression must yield an integer constant. For a single
switch
statement each case
expression must yield
a unique value. If no case
is matched, the
default
clause is executed. If the default
is
omitted then none of the case statements are executed.
The alt
statement allows threads to perform communication on
several channels simultaneously without polling. The expression in each
case of an alt
must contain either a send or receive
operation. The alt
statement provides a fair select between
ready channels. A thread will remain blocked in alt
until one
of the case expressions can be evaluated without blocking. The
case
expression may be evaluated more than once, therefore
care should be taken when using expressions which have side effects. If
several of the case
expressions are ready for evaluation one
is chosen at random. A break
statement terminates each case of
the alt
. If the break
statement is omitted
execution will proceed to execute the communication of the next
case
regardless of its readiness to communicate. For example:
chan(Mesg) keyboard, mouse; Mesg m; alt { case m = <-keyboard: /* Process keyboard event */ break; case m = <-mouse: /* Process mouse event */ break; }The
alt
statement is also used to descriminate between the
type of values received from channels of variant protocols. In this form
each case-item of the alt
must be a simple assignment.
The right hand side must contain a communication operation on a channel
which supplies a variant protocol. The type of the l-value is used to match
a type in the variant protocol. An alt
may be performed on an
abitrary set of variant protocol channels so long as no type is supplied by
more than one channel. There must be a case
clause for each
type supplied by the union of all channel types; ALEF requires the match
against types to be exhaustive. For example:
Aggr1 a; Aggr2 b; Aggr3 c; chan (Aggr1, Aggr2, Aggr3) c; alt { case a = <-c: print("received Aggr1"); break; case b = <-c: print("received Aggr2"); break; case c = <-c: print("received Aggr3"); break; }If an
alt
is pending on a channel the programmer must ensure
that other threads do not perform an operation of the same type on the
channel until the alt is complete. Otherwise the alt
on that
channel may block if values are removed by the other thread. A channel may
not be used in two alt
statements simultaneously.
The symbol !{ introduces a guarded caselist. Only one thread may be executing the statements contained in the guarded caselist at any instant.
loop-statement:Inwhile (
expression)
statementdo
statementwhile (
expression) ;
for (
expression;
expression;
expression)
statement
while
and do
loops the statement is repeated
until the expression evaluates to 0. The expression must yield either an
arithmetic or pointer type. In the while
loop the expression
is evaluated and tested before the statement. In the do
loop
the statement is executed before the expression is evaluated and tested.
In the for
loop the first expression is evaluated once before
loop entry. The expression is usually used to initialise the loop
variable. The second expression is evaluated at the beginning of each loop
iteration, including the first. The expression must yield either a pointer
or arithmetic type. The statement is executed while the evaluation of the
second expression does not compare to 0. The third expression is evaluated
after the statement on each loop iteration. The first and third expressions
have no type restrictions. All of the expressions are optional. If the
second expression is omitted an expression returning a non-zero value is
implied.
jump-statement:goto transfers control to the label identifier, which must be in the current function.goto
identifier;
continue
countopt;
break
countopt;
raise
identifieropt;
return
expressionopt;
become
expression;
count: integer-constant
while(1) { while(1) { continue 2; /* Same as goto contin; */ } contin: /* Continue comes here */ }
alloc p; alloc p; rescue { goto notrescue; unalloc p; dorescue: raise; unalloc p; } goto nextrescue; notrescue: if(error) if(error) raise; goto dorescue;
return
statement. An
expression is required unless the function is declared as returning the
type void. The result of expression is evaluated using the rules of
assignment to the return type of the function.
become
statement transforms the current function into the
value of the expression given as its argument. If expression is not a
function call the effect of become is exactly the same as return. However a
function call causes the current function to be replaced by the called
function. expression must have exactly the same type as the caller.
Functions performing recursion in this manner are guaranteed to run in
constant space.
rescue
and check
statements are provided for use
in error recovery:
exception-statement:rescue
identifieropt blockcheck
expression ;
alloc a, b; rescue { unalloc a, b; return 0; } alloc c; rescue { unalloc c; raise; } dostuff(); if(error) raise;
check
statement tests an assertion. If the assertion
fails a runtime error aborts the program. The file and line number of the
check statement are printed to standard error, the program is then aborted.
The expression is evaluated and compared to 0. If the compare succeeds the
assertion has failed. For example:
alloc ptr; check ptr != nil; /* Program aborts if allocation fails */A compiler option is provided to omit check statements from trusted object code. By default check statements are included.
process-statement:Theproc
function-call;
task
function-call;
par
block
proc
statement creates a new process. The new process
starts running the named function. The arguments to function-call are
evaluated by the original process. Processes are scheduled preemptively
and the interleaving of the processes is undefined. Parent and child
process share memory and file descriptors. The stacks of both processes are
addressable, so it is possible to pass the address of an automatic between
processes. The task
statement creates a coroutine within a
process. The arguments to function-call are evaluated by the original
process. Tasks are non-preemptive and are scheduled during message passing
and synchronisation primitives. The scheduling primitives that can cause
task switching are QLock.lock and Rendez.sleep. The communication
operations which can cause task switching are alt, <-= (send) and <-
(receive). A process that contains several tasks will exist until all the
tasks within the process have exited. In turn, a program will exist until
all of the processes in the program have exited. A process or task may
exit explicitly by calling the function exits or by returning from the
function in which they were invoked.
The par
statement implements fork/process/join. A new process
is created for each statement in the block. The par statement completes
when all processes have completed execution of their statements. A par
with a single statement is the same as a block. The process that entered
the par is guaranteed to be the same process that exits.
All tasks within a process will block until a system call completes.
allocation-statement:alloc
alloclist;
unalloc
alloclist;
alloclist: expr alloclist,
expr
If the pointer has chan type, the runtime system will also initialise the new channel. If the channel is asynchronous then the specified number of buffers will be allocated.
#include <alef.h>The standard include path for ALEF on Plan 9 is
/sys/include/alef
.
Lock ADTs have no runtime state and may be dynamically allocated. The thread which claimed the lock need not be the thread which unlocks it.
The thread which claimed the lock need not be the thread which unlocks it.
QLock ADTs have runtime state and may be dynamically allocated provided they are unallocated when unlocked. The thread which claimed the lock need not be the thread which unlocks it.
prog : decllist decllist : | decllist decl decl : tname vardecllist ';' | tname vardecl '(' arglist ')' block | tname adtfunc '(' arglist ')' block | tname vardecl '(' arglist ')' ';' | typespec ';' | TYPEDEF ztname vardecl zargs ';' zargs : | '(' arglist ')' ztname : tname | AGGR | ADT | UNION adtfunc : TYPENAME '.' name | indsp TYPENAME '.' name typespec : AGGR ztag '{' memberlist '}' ztag | UNION ztag '{' memberlist '}' ztag | ADT ztag '{' memberlist '}' ztag | ENUM ztag '{' setlist '}' ztag : | name | TYPENAME setlist : sname | setlist ',' setlist sname : | name | name '=' expr name : ID memberlist : decl | memberlist decl vardecllist : | ivardecl | vardecllist ',' ivardecl ivardecl : vardecl zinit zinit : | '=' zelist zelist : zexpr | '[' expr ']' expr | '{' zelist '}' | '[' expr ']' '{' zelist '}' | zelist ',' zelist vardecl : ID arrayspec | indsp ID arrayspec | '(' indsp ID arrayspec ')' '(' arglist ')' | indsp '(' indsp ID arrayspec ')' '(' arglist ')' arrayspec : | arrayspec '[' zexpr ']' indsp : '*' | indsp '*' arglist : | arg | '*' xtname | arglist ',' arg arg : xtname | xtname indsp arrayspec | xtname '(' indsp ')' '(' arglist ')' | TUPLE tname '(' indsp ')' '(' arglist ')' | TUPLE tname vardecl | xtname vardecl | '.' '.' '.' autolist : | autolist autodecl autodecl : xtname vardecllist ';' | TUPLE tname vardecllist ';' block : '{' autolist slist '}' | '!{' autolist slist '}' slist : | slist stmnt cbody : '{' clist '}' | '!{' clist '}' clist : | clist case case : CASE expr ':' slist | DEFAULT ':' slist rbody : stmnt | ID block zlab : | ID stmnt : nlstmnt | ID ':' stmnt nlstmnt : error ';' | zexpr ';' | block | CHECK expr ';' | ALLOC elist ';' | UNALLOC elist ';' | RESCUE rbody | RAISE zlab ';' | GOTO ID ';' | PROC expr ';' | TASK expr ';' | BECOME expr ';' | ALT cbody | RETURN zexpr ';' | FOR '(' zexpr ';' zexpr ';' zexpr ')' stmnt | WHILE '(' expr ')' stmnt | DO stmnt WHILE '(' expr ')' | IF '(' expr ')' stmnt | IF '(' expr ')' stmnt ELSE stmnt | PAR block | SWITCH '(' expr ')' cbody | CONTINUE zconst ';' | BREAK zconst ';' zconst : | CONST zexpr : | expr expr : castexpr | expr '*' expr | expr '/' expr | expr '%' expr | expr '+' expr | expr '-' expr | expr '>>' expr | expr '<<' expr | expr '<' expr | expr '>' expr | expr '<=' expr | expr '>=' expr | expr '!=' expr | expr '==' expr | expr '&' expr | expr '^' expr | expr '|' expr | expr '&&' expr | expr '||' expr | expr '=' expr | expr '<-' '=' expr | expr '+=' expr | expr '-=' expr | expr '*=' expr | expr '/=' expr | expr '%=' expr | expr '>>=' expr | expr '<<=' expr | expr '&=' expr | expr '|=' expr | expr '^=' expr castexpr : monexpr | '[' typecast ']' castexpr | '(' typecast ')' castexpr typecast : xtname | xtname indsp | xtname '(' indsp ')' '(' arglist ')' | TUPLE tname monexpr : term | '*' castexpr | '&' castexpr | '+' castexpr | '-' castexpr | Tdec castexpr | Tinc castexpr | '!' castexpr | '~' castexpr | SIZEOF monexpr | '<-' castexpr | '?' castexpr term : '(' elist ')' | SIZEOF '(' typecast ')' | term '(' zarlist ')' | term '[' expr ']' | term '.' stag | '.' TYPENAME '.' stag | term '->' stag | term '--' | term '++' | term '?' | name | '.' '.' '.' | CONST | NIL | ENUM_MEMBER | FCONST | STRING | '$' STRING stag : ID | TYPENAME zarlist : | elist elist : expr | elist ',' expr tlist : typecast | typecast ',' tlist tname : sclass xtname | sclass '(' tlist ')' xtname : TYPENAME | INT | UINT | SINT | SUINT | BYTE | FLOAT | VOID | CHAN '(' typecast ')' bufdim bufdim : | '[' expr ']' sclass : | EXTERN | INTERN | PRIVATE