LARD Language Language Reference Manual
Declarations and Scope
Declarations
Declarations are introduced by means of the keyword . (or more
accurately by means of the expression _._). The expression
D . E has the value of the expression E in the
context of the declaration D.
Declarations can take three forms:
- prototype : class
- prototype : class = definition
- use(library)
The last of these is used to include definitions from a library, and
is described in a later section.
The prototype indicates the name of the thing being declared. The
class indicates what sort of thing it is, and the definition, if
present, defines it. All three of these are just expressions.
Possible classes of declarations include types, variables and
expressions, and are described later.
The prototype may be either a single identifier, as would be the case
for a variable declaration, or it may be an expression with
parameters. These parameters, if present, must themselves be
declarations. The following examples illustrate this:
v:var(int) | v is a variable of type int. |
rand:expr(int)=lfsr(seed) | rand is an
expression of type int, with the given defintion. |
max(a:val(int),b:val(int)):expr(int)=(if (a>b) then a else
b)
| max is an expression of type int, which takes two
arguments, a and b, each of which is a value of type int. |
Scope
The scope of a declaration is the rest of the expression in which it
appears. So a declaration introduced using . is visible only
within the body expression of the . expression. A parameter
declaration is visible within the whole of the declaration whose
prototype it appears in.
Libraries may be used to extend the scope of identifiers.
Scope and Concurrency
Where a declaration preceeds the point at which flow of control forks
into concurrent threads the declaration is shared by all threads.
Where a declaration is within an expression that executes concurrently
with some other expressions, the declaration is private to that
thread.
Classes
Each expression has a class. The lard compiler finds the class of
each expression as it parses the program and checks that the classes
of parameter expressions match the classes of the coresponding
parameter declarations. This class checking is a superset of type
checking.
The classes are as follows:
class | The class of classes. This is used for
bootstrapping only; users should never declare things to have class
class |
type | The class of types. |
val(T) | For each type T there is a class of values of
that type. |
var(T) | For each type T there is a class of variables
of that type. |
expr(T) | For each type T there is a class of
expressions of that type. |
lib | The class of libraries. See the section on
libraries. |
Normal Declarations
By Normal Declaration I mean declarations that aren't parameter
declarations.
Type Declarations
Type declarations may take either of the following forms:
- Declarations with no definition, coresponding to built-in types.
This form of declaration should only occur in the standard library.
- Declarations with a definition. These alias one type to another.
For example:
- three_integers:type=(int^3)
- three_of(T:type):type=(T^3)
- intvector(n:val(int)):type=(int^n)
- day:type=scalar
Value Declarations
Value declarations have two forms:
- Declarations without definitions give enumeration values to
enumerated types. For example
- Declarations with definitions define constants. For example
Note: this does not currently work.
Variable Declarations
A variable declaration introduces a new local variable. The variable
is creatred when the enclosing expression begins evaluation and is
destroyed when it completes. Variables are initially undefined.
There is currently no mechanism for giving initial values to
variables, and variable declarations must have no definition.
Some example variable declarations are:
- n:var(int)
- foo:var(bool^10)
Expression Declarations
Expression declarations define functions or procedures. If no
definition is present the expression is built in; this should occur
only in library code.
Some example expression declarations are:
- max(a:val(int),b:val(int)):expr(int)=(if (a>b) then a else b)
- foo(n:val(int)):expr(void)=(printstr("n=");printstr(n))
Parameter Declarations
The meanings of declarations a slightly different when they are the
declarations of parameters to type or expression declarations.
Parameter declarations never have definitions, irespective of their
class.
Type Declarations
A type parameter declaration declares a parameter that matches a type
expression given in the call. The type parameter may subsequently be
used in other parameter declarations, variable declarations etc. For
example:
- sizeof(T:type):expr(int)
- swap(T:type,a:var(T),b:var(T)):expr(void)=
((tmp:var(T)).((tmp:=a);(a:=b);(b:=tmp)))
Type parameter declarations are useful as implicit parameters; see a
later section.
Value Declarations
A value parameter declaration declares a parameter that is passed by
value. A copy of the parameter is taken, and is read-only within the
body of the expresssion. See the max example elsewhere.
Variable Declarations
A variable parameter declaration declares a parameter that is passed
by value. Within the body of the expression the parameter can be
assigned to in the same way as a local variable. See the
swap example elsewhere.
Expression Declarations
An expression parameter declaration matches an unevaluated expression.
This is used in contexts where the expression may evaluate its
parameters more than once, such as the while_do_ function, or
where it may not evaluate them at all, such as the
if_then_else_ function. The following example shows how
repeat_until_ can be implemented in terms of
while_do_:
repeat_until_(cond:expr(bool),body:expr(void)):expr(void)=(
body;
while (!cond) do
body
)
Implicit Declarations
Implicit declarations are declarations that appear within the class
expressions of parameter declarations. They match sub-expressions of
the class of the paramter. They are used to allow expressions to
accept generic parameters without having explicit type parameters, as
in the swap example presented previously.
Here is how the swap example can be re-implemented using an
implicit declaration:
swap(a:var(T:type),b:var(T)):expr(void)=
((tmp:var(T)).((tmp:=a);(a:=b);(b:=tmp)))
This can be called as swap(a,b) where a and
b are variables of any type - as long as they are the same
type as each other.
This example shows how implicit parameters can match subexpressions of
the type expression:
length(v:((T:type)^(n:int))):expr(int)=n
Literal Declarations
Within the standard library it is legal to use a literal as the
prototype of a declaration. This is used to associate the types int,
char and string with numeric, character and string literals.
Libraries
A library declaration takes the following form:
name:lib=(
(foo:expr(void)=printstr("foo\n")).
(sprocket:expr(int)=99).
islib
)
The declarations within the library declaration are not visible in the
scope of the library declaration. To make them visible within some
scope, use a use declaration:
use(name)