a brief description

*(version 03.09.2007)*

One
of the design goals of **AWL** programming language was to
provide suitable alternative for common World Wide Web programming
languages and tools. AWL tries to achieve this objective by providing
consistent set of tools for text formatting (like HTML and CSS), data
definition (XML), vector graphics (like SVG and Macromedia Flash) and
scripting (like JavaScript/VBScript). AWL
borrows some aspects from LISP/Scheme, Perl, Ruby and other script
languages. Operating as *interpreter* (with partial
precompilation and compile-time variables and functions binding), it
implements many ideas of procedural, functional and object-oriented
programming.

**Language core**

AWL
has a variety of data types. The primitive (*scalar*) ones are
*numbers* and *character strings*. Internally, number type
is subdivided into *integer* and *float* subtypes (with
implicit conversion between them in most cases). *Strings* are
of unlimited length and may contain any 8-bit ASCII characters
(support for 16-bit Unicode is under development).

The
primary structural type is *list*. As in LISP, lists can be of
arbitrary length and *heterogenic* (can contain values of any
type, including other lists with unlimited nesting depth).

Here are examples of valid lists (made of numbers, strings and other lists):

(1,
2, “aaabb”, 22.13, “cccc”)

((11,
‘aa’), (22, ‘bb’), ‘cc’)

Empty
list ()
represents *absence of value* (in some situation termed **undef**).
Lists, containing just a single element, are identical to this
element. When the final element of list is
other list, it is merged with the start of the list:

(1, (2, (3, (4, 5))))

is just an alternative form of:

(1, 2, 3, 4, 5)

However, when final element of list must preserve its structure, the special list form (“open list”) is available. In the following case:

(1, 2, (3, 4, 5), )

actual
last element of list is sublist (3, 4, 5) – due to final **undef**
(which is not counted as final element by most list operations).

When
all list elements are *syntactically atomic* (which includes
variables, scalars, other lists) alternative, more terse list syntax
is permitted:

[1
2 “aaabb” 22.13 “cccc”]

[[11
‘aa’] [22 ‘bb’] ‘cc’]

[1 2 [3
4 5] :]

(Last example shows an alternate syntax form for open lists.)

Most of the language *expressions* actually are nested *terms*.
Terms are basic language constructs, providing a standard way to
invoke *functors*, which are primary language device of
evaluation. Syntax of term looks exactly like function call in common
languages:

**functor**(args)

Evaluation
of this term results in invoking **functor** with *args* (in
most cases, this is a list of values) as its argument(s). It is
important to note, what terms themselves are data items, too.

There
are plenty of built-in functors, performing operations on numeric and
string values. For example, evaluation of **neg**(x)
returns value of *x* negated; **add**(x,
y) adds values of *x*
and *y* together, and so
on. However, to make code more readable, language provides more
familiar (“operator”) syntax for many built-in functor
invocations: these may be coded as prefix/postfix unary or infix
binary operators (with appropriate priorities and associativities).
For example, expression (a+3) * (b–5)
may be used as the substitute for **mul**(**add**(a,
3), **sub**(b, 5)). However,
“operator” syntax of expressions is no more than a
“syntactic shortcut” for a normal (“functional”)
form of them, and is translated to the later at compile time.

Language
provides a lot of built-in functors for numeric calculations. These
include standard arithmetic operations, bitwise/logical and shift
operations, comparisons, and a lot of mathematical functions (like
**sqr** for square root; **sin**, **cos**, **tan** for
standard trigonometric functions; **exp** and **log** for
exponentiation and logarithm, etc.).

Also,
there is lot of string built-ins
(and many of them have alternative operator syntax, too). For
example, **s_cat**(s,
t) (or s +$ t)
returns concatenation of *s* and *t* (treated as strings);
**s_rep**(s,
N) (or s *$ N)
returns string *s* replicated *N* times; **s_slice**(s,
R) (or s $[R])
returns fragment of string *s* sliced by *range*
*R* (see explanation of
ranges below); and so on. Implicit coercions between scalar
types are allowed: numbers may be coerced to strings, and vice versa
(just like in Perl or Python). There are
explicit conversions as well: for example, **num**(val)
coerces value *val* to number, and **string**(val)
coerces value *val* to string.

As in most of functional languages, functors are playing crucial role in AWL: most of program execution is, in fact, functors evaluation. (In the following examples, we show all functor names in bold face).

Like
almost any programming language in existence, AWL has *variables*.
All variables are completely *typeless* (may include value of
any type at any moment) and
module-level (global) variables don’t need to be
explicitly declared.

Some
of built-in functors are *mutators*, and can alter value(s) of
their argument(s). Of course, the most important mutator operation is
*assignment*: **set**(v,
x) (or just v = x)
evaluates expression *x*, then assigns result to *v*, which
may be any variable (or, in fact, any *mutable*). Other mutators
include *increment/decrement* operations (like in C++ or Java):
for example, ++x
(or **inc**(x))
increments *x* (returning new value), and y--
(or **dec_p**(y))
decrements *y* (returning old value). The *exchange*
operation **swap**(a,
b) (or a :=: b)
exchanges values of mutables *a* and *b*. The
assignment can be *combined* with many binary operations
(internally it is implemented with **comb** built-in, which
expects term argument, and extends its evaluation to include
assignment of result to first operand). For example, v =+: w
(note syntax!) just adds value of *w* to the mutable *v*
(internally, represented as **comb**(**add**(v,
w))). Combining assignment
with some *unary* operations (especially unary –
(**neg**) and unary ~ (**not**)) is an unusual feature:
for example, v =:-
arithmetically negates value of mutable *v* (internally
represented as **comb**(**neg**(v))).

All
variables are obviously mutable, but there are other kinds of
mutables in the language. For example, some functors evaluate to
mutable result, so they are allowed anywhere mutable is expected. The
perfect example is **l_item** functor: **l_item**(n,
L) (or just L[n])
provides access to *n*-th element of list *L* (indexing
always starts at 0). Because result of evaluation is mutable, this
operation can be used to change individual elements of the list (like
in L[n] = 10
or ++ L[n]).
Other examples are **l_head**(L)
and **l_tail**(L):
these operations provide access to *head* of list L (its first
element) and *tail* of list L (all elements except first)
respectively. With help of these functors, processing and modifying
lists as *binary trees* (which internally they are in fact) is
possible.

Assignment can be applied not only to scalar values. For example, any list of mutables is mutable too, so list of any values may be assigned to it:

[a b c] = [10 20 30];

assigns
3 values to 3 variables at once. All
assignments are performed simultaneously: so [x y] = [y x]
is an another possible way to swap values of *x* and *y*.
It is important to note, what list of values in assignment can be
shorter than list of mutables (in this case, all extra mutables are
set to **undef**), or it can be longer (in this case, last
assigned mutable received all redundant values as the end of the
list). If element of mutables lists are other lists, assignments of
the are done recursively.

There
is a lot of built-ins working with list operands. To name a few,
**l_len**(L) returns
length of list *L*; **l_copy**(L)
makes an independent copy of list *L*; **l_rev**(L)
(or [~]L)
returns reverse of list *L*; **l_cat**(L,
M) (or L [+] M)
returns concatenation of lists *L* and *M*; **l_rep**(n,
L) (or L [*] n)
returns replication of list *L* exactly *n* times. (These
operations treat **undef** as list of length 0, and any scalar
value as list of length 1.)

Several
built-ins make easy adding and removing elements to/from list. Most
notably, **l_push**(L,
val1, val2, ... valN) inserts
*N* values (val1...valN)
at start of list *L* (in reverse order, so *valN* becomes
first) and **l_pop**(L,
var1, var2, ... varN) removes
*N* values from start of list *L* (assigning them to
mutables *var1 ... varN* in
direct order). Both operations have an alternative syntax
form: L [<-] (val1
... valN) and L [->] (var1
... varN) respectively.
Combining these operations with list accessor
operation **l_tail_by**(n, L)
(which has effect equivalent to *n* times application of **l_tail**
to *L*), it is easy to insert/remove elements at any place of
the list.

Although
many of scalar functors evaluate their arguments before performing
their own evaluation, this is *not* always the case. The
principal feature of language semantics is *demand-evaluation*
(or *lazy evaluation*) of arguments of many built-in functors.
Some functors evaluate their argument optionally (*conditionals*),
some may evaluate them more than once (*iterators*).

For
example, typical conditional evaluation is provided by **if**/**unless**
built-in functors. Evaluation of **if**(cond,
then, else) always evaluates
*cond* first; then, if
condition appears to be **true**
(anything different from 0, empty string or **undef**) it
evaluates expression *then,* otherwise expression *else*
is evaluated. (There is a ternary operator form for this,
borrowed from C-like languages: cond?
then : else).
Built-in **unless** does the same of **if**, except “polarity”
of condition *cond* is reversed. The
“short-circuit” conditional binaries **c_and**(P,
Q) and **c_or**(P, Q)
perform logical AND/OR operations on their operands *P* and *Q*;
but with optional evaluation of *Q* (which is not evaluated by
**c_and** if *P*
is **false**; and by **c_or**
if *P* is **true**).

*Conditional
iterators* are provided by **while**/**until**
built-ins: evaluation of **while**(cond,
loop) evaluates both *cond*
and *loop* repeatedly, while *cond* remains **true**
(and never evaluates *loop*, if *cond* is **false**
initially). (There is binary operator form for this, too:
cond ?? loop).
Built-in **until** does the same, only reversing condition
polarity. Both iterators are *pre-conditional* (e. g. checking
*cond* before *loop*), but they have *post-conditional*
counterparts: **do_while** and **do_until** (which have similar
arguments and semantics, except they check *cond* after each
evaluation of *loop*, which is
evaluated at least once). Note, what (as all expressions) loop
constructs return a value too (which is result of final evaluation of
*loop*).

Very
useful are *sequential iterators*, making easy iterating with
index variable receiving all values in the integer range, in
incremental (**for_inc**) or decremental (**for_dec**) order.
These iterators expect range to be list of two boundaries *(from,
to)*, including all integer values, for which *from <= value < to*
(note, what value of *from* is included into range, but value of
*to* is excluded from it); range is considered empty, if
*from>=to*. For ranges, shortcut syntax *from .. to*
is permitted. If only one integer value *N* is supplied for
range, range *0..N* is assumed. Iterator
**for_inc**(index,
range, loop) changes mutable
*index* starting at *from* in ascending order until *to*-1,
evaluating *loop* on each
iteration. Iterator **for_dec**(index,
range, loop) does the same,
except *index* is changed in descending order, from *to*-1
to *from*. Finally, built-in iterator **times**(count,
loop) just evaluates *loop*
exactly *count* times.

*List
iterator* **l_loop** allows looping through all elements of
list: evaluation of **l_loop**(item,
list, body) results in
iterating *item* through all elements of *list* (from first
one to last), and evaluating *body* on each iteration.
Alternative **l_loop_r** does the same, except list items are
iterated from last one to first.

In
many cases, conditions and iterators require a sequence of
expressions to be evaluated. Simplest way to achieve this is to use
block expressions (*blocks*):

{ expr1; expr2; … exprN }

evaluates
expressions in sequence (from *expr1* to *exprN*), and
returns just value of *exprN*. Note, what semicolon is always
treated as *separator* in AWL (unlike C or Java): if “;”
follows final element of the block, this means, what actual final
element (and block's return value) is **undef**. (This may be
actually needed, when return value of block is irrelevant, but may be
misleading in other cases, so some care is advised.)

Finally,
to make code more readable, some alternate syntax is allowed for
nested terms. For example, expression **func1**(arg1,
**func2**(arg2)) allows alternate syntax: **func1**(arg1)::
**func2**(arg2). This syntax is especially useful, when
control functors are deeply nested: for example, notation

**if **(cond):: Then ::
Else;**unless **(cond):: Else :: Then;**while **(cond)::
Loop;**until **(cond):: Loop;

looks (especially to adepts of procedural languages) more obvious and clear, than equivalent “functional” syntax:

**if **(cond, Then,
Else);**unless **(cond, Else, Then);**while **(cond,
Loop);**until **(cond, Loop);

In
fact, operation '::' is another form of built-in *list constructor*:
Head::Tail is completely
equivalent to list (Head, Tail).
However, because priority of this operation is lower, than of term
constructor, the above examples work as expected.

Input/output
tools are explained very briefly, as well as *streams*, which
provide means of communication with outside world.

To
put list of values to output stream *O_Str*, **f_put**
built-in is used:

**f_put**(O_Str, Val1, Val2,
... ValN);

or

O_Str <: (Val1, Val2, ... ValN);

Values
*Val1 ... ValN* are normally scalars (lists nesting is allowed),
implicitly coerced to strings and output as strings. Operand *O_Str*
may be omitted (equals to **undef**), and in this case standard
output stream is assumed. Number of successfully written list items
is returned as result.

To
get several lines from input stream *I_Str*, **f_get**
built-in is used:

**f_get**(I_Str, Mut1, Mut2,
... MutN);

or

I_Str :> (Mut1, Mut2, ... MutN);

where
*Mut1 ... MutN* are mutables, receiving lines from input stream
(as string values). If coercion to different type is needed, it must
be done explicitly. Result of **f_get** is number of input lines
successfully read and assigned (which may be less than *N* on
end of file or input error).

Coercion of them (or parsing), if needed, requires special efforts.

There's much more built-ins, associated with input/output, which are not detailed here.

In AWL (like in LISP and related languages), all expressions (including variables, terms and blocks) are data items, and may be treated as such: assigned to variables, embedded into complex data structures, passed to functors and/or returned from them. To make distinction between evaluated and non-evaluated data, some language built-ins are usable.

*Devaluation*
is an unary functor: **deval**(expr)
(with operator syntax: @expr).
Its effect is inhibition of operand's evaluation: returned value is
*expr* itself, instead of result of *expr*.
The following example:

x_ref = @**sqr **(x*x –
y*y);

makes
expression **sqr **(x*x
– y*y) the actual value
of variable *x_ref*.

Effect
of *revaluation* is exactly opposite. Unary functor **reval**(expr)
(with operator syntax: ^expr)
evaluates result of *expr* evaluation (actually, evaluating it
twice). So, considering the example above:

value = ^x_ref;

has exactly same effect, as

value =
**sqr **(x*x
– y*y);

if
the value of *x_ref* was unchanged.

Because
*lazy assignments* are needed frequently, there’s a
special replacement for var = @expr.
Using var := expr
(or **let**(var,
expr)) has exactly same
effect: assigning *expr* to mutable *var* without
evaluation.

Expression evaluated on demand can be (and frequently are) used as kind of extremely “lightweight” functions, having no parameters or local context. Of course, they can't always replace the functor definitions.

The general syntax of *user functor definition* looks like this:

! **myfunc**(par1
par2 … parN): [loc1 loc2 … locN] = body;

Here
**myfunc** is a name of
new functor; *par1…parN* are functor *parameters*;
*loc1…locN* are functor *local
variables*, and *body*
must be any valid expression. Both parameters and local
variable names belong to functor's local variables name space
(temporarily overriding global variables with same names, if they
exist), and are visible in *body*.

To call user functor, used same syntax, as for built-ins:

**myfunc**(10, 20, 30);

This
results in user functor **myfunc** being invoked with *par1* = 10,
*par2* = 20 and so on (and all local variables set to
**undef**). This call returns result of evaluation of *body*.
For example, **FtoC** can be used to convert degrees of Fahrenheit
to Celsius scale:

! **FtoC**(temp)
= (temp–32) * (5/9);

Another functor calculates distance between points (x0, y0) and (x1, y1):

! **dist**(x0
y0 x1 y1) = **sqr**((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0));

More
efficient way to do this is to use built-in **rad**(dx, dy),
calculating value of **sqr**(dx*dx + dy*dy):

! **dist**(x0
y0 x1 y1) = **rad**(x1-x0, y1-y0);

Functor parameters may have default initializers. For example:

!
**draw****_shape**(Color=Red Shape=Rectangular) = { ...
};

Because both parameters of **draw_shape** have default values,
they are substituted, if actual argument is **undef**:

**draw_shape**(Green); *`
draw_shape(Green, Rectangular) `*

If
functor argument has neither explicit nor default initializer, its
default value is **undef**.

Needless
to say, functor definitions may be *recursive*. Any invocation
of recursive functor has private set of parameters and locals. Here
is a recursive definition of factorial (of n):

! **factorial**(n) = n?
n***factorial** (n–1) : 1;

or binomial coefficients (n, m):

! **binomial**(n m) = (0 <
m && m < n)? **binomial**(n-1, m) + **binomial**(n-1,
m-1) : 1;

(There’s a special syntax to define a family of mutually-recursive functors, which is not described here.)

Return value of functor may be any data structure. Lists are frequently used as return values:

!
**transform_pt**(x y) = (a*x + b*y + c, d*x + e*y + f);

returns
result of linear transformation of point (*x*, *y*)
(defined by coefficients *a*, *b*, *c*, *d*, *e*,
*f*).

Another
example of recursive functor, operating on list, is list flattening
operation. Functor **l_flatten** returns linear list of all atomic
(non-list) elements of list *L*, collected recursively:

! **l_flatten**(L) =
**is_list**(L)?**l_flatten**(**l_head**(L)) [+] **l_flatten**(**l_tail**(L))
: L;

One
of the most interesting aspects of user functors is their ability to
have *demand-evaluated parameters* (like many language
built-ins). Although by default all parameters are evaluated *before*
evaluation of functor body, if parameter is
preceded with @, it's evaluation is considered functor's
responsibility (and may be performed any time). For
example, although AWL have no built-in, equivalent to C++/Java **for**
loop, it is easy to emulate one:

*` C-like for iterator
`*

!

{^Init; ^Cond?? {^Body; ^Iter}};

So-defined iterator can be used as easily, as built-in one:

**c_for**(I = 1,
I <= 10, ++ I, <: ["I = " I
"\n"]);

outputs
sequential numbers from 1 to 10. Lazily
evaluated functor arguments can be used to implement many language
extensions, including non-standard conditionals and iterators. They
are extremely useful to implement *wrappers*: functors, used to
surround specified evaluation with some “prologue” and
“epilogue” actions. To give some idea, how wrappers work,
here is a very trivial example:

! **R_brackets**
(@arg) = { <: '('; ^arg; <: ')'; };

! **S_brackets**
(@arg) = { <: '['; ^arg; <: ']'; };

Functors
**R_brackets** and **S_brackets** are *wrappers*,
“enclosing” evaluation of its argument in round and
square brackets. If their invocations are nested, all
“prologue/epilogue” actions are arranged in proper order:
something like **R_brackets**(**S_brackets**(<:
“Hello”)) will result in outputting “([Hello])”
(note, what argument here is *evaluation*,
and not just a value).

*Reference
to functor* is an another kind of data. However, it is important
to remember, what AWL always uses *separate name spaces* for
variables and functors. So, to refer to functor **myfunc**,
special syntax is required: !**myfunc**
(because just *myfunc* looks for variable named so). This
reference can be treated as any other data item: assigned to
variable, contained in list, or passed to other functor (thus making
functors a *first-class values*). The most obvious thing to do
with functor reference is, of course, to invoke it: **apply**(funcref,
args) (may be abbreviated to
funcref ! args)
does invocation of functor referred to by *funcref* with
argument(s) *args* (returning result). Here is a rather trivial
example:

result =
(!**add**, !**sub**, !**mul**, !**div**)[op_code] ! (x,
y);

Depending
from *op_code* (expected to be in range 0..4), it sets *result*
to *x+y*, *x-y*, *x*y* or *x/y*. Although this
example used only built-in functors, any functor defined by user is
applicable too. For another example, if we
need to check numeric values (*valA*, *valB*) for being in
ascending/descending order (depending from boolean *flag*), we
can write something like:

test_result = (flag ? !**lt**
: !**gt**) ! (valA, valB)

which
may be quickest way to perform such test, if *valA* and *valB*
are complex expressions.

When
functor reference is used only once, there is no obvious need to name
it. *Anonymous functor references* are another kind of
expressions (similar to lambda-expressions in LISP, Python and other
languages). For example:

func_list = (!(x y) = (2*x + 3*y), !(x y) = (5*x – y));

creates
*func_list* as a list of two references to anonymous functors.
Any of them may be invoked in an usual way: for example
func_list[1] ! (5,
7) evaluates to 31, and
func_list[1] ! (5,
7) evaluates to 18. As
obvious from this example, definition of anonymous functor looks like
ordinary functor definition, although it is an expression, lacks
functor name and (attention!) functor's body must be
syntactically-closed.

Some
built-ins expect functor references as arguments. For example,
**l_map**(func_ref,
list) returns new list,
constructed by applying *func_ref* to all elements of *list*
in sequence. So, **l_map**(!(x)
= (2*x + 5), [5 7 9])
returns list (15, 19, 23); and **l_map**(!(s)
= (“{” +$ s +$ “}”),
['aa' 'ee' 'ii']) returns
list (“{aa}”, “{ee}”, “{ii}”).

Finally, functor declarations may be nested. When functor definition is included into other functor definition, it creates its own local scope (actually, two scopes: for own parameters/locals and for its own nested functors). All outer scopes are accessible from inner definitions. (When looking for any variable or functor, searching is performed from inner scopes to outer ones, up to module global scope.)

Here
is a functor, solving “Towers of Hanoi” problem
(where **move_disks** is local functor of **hanoi_solve**):

! **hanoi_solve**(N):[count] =
{

! **move_disks**(n from to via) =

n ? {

count =
**move_disks**(n-1, [from via to]);

<: ["\t#" n
": " from " => " to "\n"];

++
count;

count + **move_disks**(n-1, [via to from])

} :
0;

<: ["\nTowers of Hanoi: " N "
disks...\n\n"];

count = **move_disks**(N, ['A' 'B'
'C']);

<: ["\nPuzzle solved in: " count "
steps.\n"]

};

Invoke
**hanoi_solve**(N) to get a full solution for N disks.

Another
functor example is recursive permutations generator. Invoking **permute**
will generate (as lists) all permutations of integers in range *0..N*,
and calls functor referenced with *action*, with each
permutation as argument:

! **permute** (N action):
[index] = {

index = 0 [*] N;

! **r_perm**(n): [i] =

n
<> N ? {

index[n] = n;

n
++;

**for_dec**(i, 1..n, index[i] :=:
index[i-1]);

**r_perm**(n);

**for_inc**(i,
1..n, {

index[i] :=: index[i-1];

**r_perm**(n)

})

}
:

*` end of recursion: call 'action'
with 'index' `*

action ! Index;*` Algorithm entry
point: `***r_perm**(0);

}; *` -- permute `*

Note,
what most of work here is done by local functor **r_perm**, which
is invoked recursively. To list all 16 permutations of numbers (0, 1,
2, 3), invoke it as **permute**(4,
!(list) = (<: (list, “\n”))).

AWL supports other ways of structuring data besides lists.

*Arrays*
are multidimensional data structures. As lists, array are
heterogenic, and can contain elements of any type (even other
arrays). Standard way to create array is using *array constructor*
built-in **array** (or **a_create**):

MyArray = **array**(Dim0,
Dim1, ... DimN);

The
arguments *Dim0...DimN* (scalars, evaluated and coerced to
integers) provide *dimensions* of the created array (*Dim0*
is inner dimension, *DimN* is outer one). Elements of the
created array are set to **undef**. Total number of dimensions is
called *rank* of the array. Built-in *array accessor*
operation: **a_elem**(Array,
Index0, ... IndexN) provides
mutable access to element of *Array* with indexes *Index0 ...
IndexN*, corresponding to appropriate
array dimensions. (Indexing starts from 0; negative index values are
illegal). Again, there is a shortcut notation: Array{Index0,
... IndexN}.

For example, the following code creates and returns square identity matrix of order N:

!
**identity_mtx**(N) : [i matrix] = {

matrix = **array**(N,
N);**a_fill**(matrix, 0);**for_inc**(i, N, matrix{i, i}
= 1);

matrix

};

Built-in
**a_fill**(Array,
Value) is used to quickly set
all elements of *Array* to any *Value*. There are several
built-ins to get information about arrays: for example, **a_dims**(Array)
returns list of *Array* dimensions, **a_rank**(Array)
returns rank of *Array*, and **a_total**(Array)
returns total number of elements in *Array* (which is always
product of its dimensions). To make conversion between arrays and
lists easy, **a_save**(Array)
saves contents of *Array* as list of values (iterating from
outer dimensions to inner ones) and **a_load**(Array,
List) does the reverse,
loading *Array* by the values from *List*, in the same
order, as **a_save** stores them.

Finally,
**a_copy**(Array) makes
exact copy of *Array*, with same dimensions and elements.
Although arrays can be always replaced by deeply nested lists, they
are preferred way do store multi-dimensional data, requiring less
memory and providing faster access to elements. The drawback of
arrays is what they require more efforts to reshape, or insert/remove
element(s) in the middle, which is faster with lists.

*Hashes*
are associations between set of keys, and set of values corresponding
to them. Both keys and values can be of arbitrary type (the only
illegal value for a key is **undef**). Hash is created by invoking
hash constructor **hash** (or **h_create**):

MyHash = **hash**();

The
only (optional) parameter of **hash** is the integer value,
defining initial internal capacity of the hash. To
get mutable access to element of *Hash*, associated with some
*Key*, built-in **h_elem**(Hash,
Key) is used (if there was
not element with such key, it is created). For example:

**h_elem**(MyHash,
“hundred”) = 100;**h_elem**(MyHash,
“thousand”) = 1000;

For
read-only access to hash, **h_lookup**(Hash,
Key) may be used: contrary to
**h_elem**, result is not mutable, and absent keys are not
created. To remove item *Key* from hash, **h_remove**(Hash,
Key) must be invoked
(returning value formerly associated with *Key*). Of course,
there is also iterator built-in to loop through the hash:

**h_loop**(Hash, Variable,
Body)

iterates through *Hash*,
setting *Variable* to list (*Key*, *Value*) for each
hash element before invoking *Body*. Finally, **h_count**(Hash)
returns total number of key/value pairs in the *Hash*, and
**h_clear**(Hash)
removes everything from the *Hash*, making it completely empty.

It
is important to mention, what no implicit scalar coercions are
applicable when accessing hash elements. For hash keys, all scalar
types (integers, floats, strings) are distinguished: for example,
101, 101.0 and “101” are different hash keys. When
working with hash, coercions between scalars, when needed, must be
explicit. Strictly speaking, all hash keys are compared exactly as
built-ins **ident** and **differ** do. Result of **ident**(V,
W) (V [==] W)
is true, if results of *V* and *W* are identical (same
scalars, or lists (or any structural object) with same elements);
result of **differ**(V, W)
(V [<>] W)
is the reverse.

Finally,
AWL has regular expression patterns
under development. *Patterns* are capable of describing
string context to match, and are constructed of literals and several
*pattern composer* operations. For example, **rx_string**(Str)
returns pattern to match string *Str*
(this operation is implicitly applied, when string operand is used
where pattern is expected); **rx_alt**(Pat1,
Pat2) implements pattern *alternation*
(matches either *Pat1*
or *Pat2*); **rx_cat**(Pat1,
Pat2) implements *concatenation* of patterns *Pat1*
and *Pat2*; **rx_rep**(Pat,
flag, Range) implement *repetition*
of pattern *Pat* (at
least *Range*[0] and at
most *Range*[1] times,
with repetition “greediness” specified by *flag*),
and so on. There will be much more pattern operations available in
the future (and, possible, more convenient syntax for frequently-used
patterns will be implemented, too).

AWL
provides support for *object-oriented programming*, based on
concept of *objects*, being instances of user-defined *classes*.
Before any instances of class are created, actual class definition
must be done.

*Class
definitions* have much in common with functor definitions. The
basic syntax of class definition is outlined below:

!!
**myclass**(par1 par2 … parN): [loc1 loc2 … locN]

{
class_defs_list };

Similarity
between class and functor definitions is not coincidental. As well as
functors, classes have list of *class parameters*
(*par1...parN*, which are initialized by arguments) and *class
local members* (*loc1...locN*,
which are left uninitialized by default). (Class definition also can
have *constructor*, *destructor*, and *declaration of
virtuals*, all of them optional and preceding declarations list.)
To instantiate any class, it must be
invoked (with list of arguments to match class parameters), and new
class instance is returned as result. This:

object
= **myclass**(arg1, arg2, … argN);

creates
and returns new instance of **myclass**
(initializing parameters *par1* ... *parN* with arguments
*arg1* ... *argN*). In other words, although functor
evaluation is terminated (returning result), in the case of class,
evaluation is “suspended” in the form of object,
preserving values of parameters passed. Many aspects of functor
parameters are applicable to class parameters: for example, they also
require demand-evaluation and have default initializers. Class
header is followed with list of class internal definitions
(*class_defs_list*),
which can contain definitions of local functors (*methods*) and
even local classes. Note, what definitions
in this list are separated by commas, not semicolons.

As
functor, class has his own *name space* (actually, two distinct
name spaces, for members and for local functors/classes). Normally,
all access to class internals from outside must be *qualified*,
with qualification expression looking like:

**myclass**!!expression

This
is an example of (relatively rare) *compile-time operations* of
AWL (so, there is no functional replacement for it). It forces
*expression* to be interpreted *in context of ***myclass**,
making all members and internal functors of **myclass** accessible
there. Because all variable and functor names bindings are performed
at compile time, the qualifier expression must be processed at
compile time, too. (This complication is inevitable: because AWL is
weakly-typed language, there is no way interpreter can predict, what
some expression results in class instance, which is crucial for
resolving names properly.)

The
most important feature of AWL object system is *instant
class-object binding*. Any class always has current instance
(which is void initially) associated with it. Language
allows only temporary modification of class current instance, by the
following operation:

object.expr;

completely
equivalent to functional expression **with**(object,
expr). This is *wrapper operation* for *expr*: it
evaluates and returns expr, but with *object* as current
instance of its own class. Current instance definition is
temporary and local for this expression: when evaluation of *expr*
is complete, current instance of class is restored to what it was
before (so, there is no problems in nesting invocations of **with**).

Current
instance has effect, for example, for class members: they are always
accessing appropriate members of current class instance (or **undef**,
if there is no current instance of such class). This explains, how
class functors operate. Although, due to the tradition, the internal
functors of the class are termed *methods*, they are quite
different from methods in conventional OO-languages. (For example,
they have no implicit argument to specify current class instance, as
conventional methods do, because current instance is defined
externally, without any relation to functor calls.) The only actual
difference between class “methods” and all other functors
is what class own functors have implicit access to class definitions,
but external functors must use qualification to access any class.
Beside this, there is no principal difference between class methods
and other functors.

For example, we can define a simple object (representing vectors in 3D space):

!! **Vector3D**
(x y z) {

*` length of vector `*

! **length** =
**sqr**(x*x + y*y + z*z)

};

Here,
class **Vector3D** does have parametric members *x*, *y*,
*z* and internal functor (“method”) **length**
without parameters, calculating length of vector. This functor
operates on current instance of **Vector3D**, expecting it to be
defined externally, for example:

vec1 =
**Vector3D**(10, 20, 30);

len1 = vec1.**Vector3D**!!**length**();

Different approach to calculating length of vector is to make functor receiving object as argument:

! **length**(v) = **sqr**(v.x*v.x
+ v.y*v.y + v.z*v.z)

However, this is an extremely clumsy (and inefficient) way to define such functor. This definition does exactly the same, but is more in style of AWL (and more efficient):

! **length**(v)
= v.**sqr**(x*x + y*y + z*z)

Other
functors of **Vector3D** also may prefer to to use explicit
parameters for objects. For example, the
following definitions may be added to the core of **Vector3D**
class:

*` add 3D vectors `*

!
**add**(v1 v2) = **Vector3D**(v1.x+v2.x, v1.y+v2.y,
v1.z+v2.z),*` subtract 3D vectors `*

! **sub**(v1 v2)
= **Vector3D**(v1.x-v2.x, v1.y-v2.y, v1.z-v2.z)

Both
functors expect instances of **Vector3D** as their arguments. As
above, this definition is a bit clumsy: here are two (completely
equivalent) shorter versions:

! **add**(v1 v2) =
v1.**Vector3D**(x+v2.x, y+v2.y, z+v2.z),

! **sub**(v1 v2) =
v1.**Vector3D**(x-v2.x, y-v2.y, z-v2.z)

! **add**(v1 v2) =
v2.**Vector3D**(v1.x+x, v1.y+y, v1.z+z),

! **sub**(v1 v2) =
v2.**Vector3D**(v1.x-x, v1.y-y, v1.z-z)

No
matter how these functors are defined, **Vector3D**!!**add**(vec1,
**Vector3D**(40, 50, 60)) returns **Vector3D**(50, 70, 90); and
**Vector3D**!!**sub**(vec1, **Vector3D**(40, 50, 60))
returns **Vector3D**(-30, -30, -30). There are several built-in
functors can provide information about class/object relationship. For
example, **class_of** (Object) returns reference to class, to
which *Object* belongs. The **self**
functor does somewhat opposite operation: **self**(!**myclass**)
returns current instance of **myclass** (or **undef**, if there
is no current instance at the moment).

Classes
can have *constructors* and/or *destructors* associated.
Contrary to languages like Java and C++, they
are NOT functors, but just an expressions (which,
obviously, can be blocks of code). Class constructor is invoked *after*
instance parametric members are initialized; its objective may be
checking or modifying their values (to keep class instance internally
consistent), initialization of
non-parametric members, or interaction with outside world. If object
instance allocates some external resources, destructor can be used to
release them. Here is an example of very trivial class, but having
both constructor and destructor:

!! **traced**
(A B)

= { <: ['\n[constructor: traced (' A ',' B ')]\n' ] }

~ { <: ['\n[destructor: traced (' A ',' B ')]\n'] };

All
instances of **traced** will report their creation and
destruction. In AWL (unlike Java and some other garbage-collection
languages), the destruction of objects is *predictable*: object
is destroyed precisely when it has no references left.

Class
*inheritance* is another
object-oriented concept directly supported by AWL. Any class
can have *superclass* (only
one: there is no multiple inheritance, although this idea is
considered), from which it borrows all member and functor
declarations. Declaration of derived class has the following syntax:

!! [**SuperClass**] **SubClass**
(params):[members] { decls };

Deriving
**SubClass** from **SuperClass** means, what any instance of
**SubClass** is an instance of **SuperClass** as well.
Everything defined in **SuperClass** is inherited by **SubClass**,
including all class members (both parametric and not) and all
methods/functors. When **SubClass** is used in qualifier
expression, internals of **SuperClass** are made accessible as
well. Analogically, when instance of **SuperClass** is used in
**with** functor, it temporarily becomes current instance not only
of **SubClass**, but of **SuperClass** and all its ancestor
classes as well. Functionality of class constructors and destructors
is inherited too: if there are constructors for class ancestors, they
are executed when class instance is created (constructors of
superclasses are invoked first, constructors of subclasses last), and
if class has destructors, the order of their evaluation is reverse
(destructors of superclasses last, destructors of subclasses first).

Finally,
because class and its ancestors normally needs parameters to
initialize their instances properly, the arguments of superclass are
supplied as *first argument* in the list of subclass. So, when
creating instance of subclass, argument list looks like
**subclass**((*superarg1*,
... *superargN*), *subarg1*, ... *subargM*).
Even when superclass does not need initializers,
**undef** () must be supplied as list of arguments for it (this
may be changed in future revisions of language). When there are
several class ancestors, the argument lists for them are nested:
subclass initialisation looks like

**subclass**(((*super-super-class
parameters...*), *proper super-class parameters...*), *proper
subclass parameters*)

and so on.

Ordinary
functors are not polymorphic. To add
polymorphism to functors definitions, special language device –
*virtual functors* – is needed. Normally,
virtual functor has single declaration, but
can have many definitions, for each subclass derived from originator
class. When any virtual functor is invoked, *devirtualization*
takes place: the correct definition of virtual (taken from subclass,
to which the current instance of originator class belongs) is
invoked. When subclass does not redefine virtual, it inherits its
definition from superclass. If virtual remains completely undefined,
its invocation has no effect, returning **undef**.

The (optional) declaration of virtuals (following class header, constructor and destructor) has the following syntax:

#{ virtual1 virtual2 ... virtualN }

So, it declares just *names of the virtuals*, originating from
this class.

Definition of virtual looks like the ordinary functor definition, but name of functor must be name of virtual preceded by '#':

! #virtualX (par1 ... parN) : [loc1 ... locM] = body;

Of course, this is an error, if there is no 'virtualX' declared as virtual either in this class or any of its ancestors. It's erroneous as well to define same virtual more than once in single class.

Here is an example of simple class hierarchy, using virtuals to operate with basic geometric objects (shapes). First, we declare abstract superclass, implementing geometric “shape”, having description, perimeter and area:

*` Abstract shape superclass
`*

!! **Shape**

# {

**put** *` output brief
info about self `*

**perimeter** *` calc perimeter of
self `*

**area** *` calc area of self `*

};

Subclasses (implementing real shapes) follow this definition:

*` Rectangle (A * B) `*

!!
[**Shape**] **Rectangle** (A B) {

! #**put** = <:
['Rectangle (' A ' * ' B ')'],

! #**perimeter** =
2*(A + B),

! #**area** = A * B

};

*` Square (S * S) `*

!!
[**Shape**] **Square** (S) {

! #**put** = <: ['Square
[' S ']'],

! #**perimeter** = 4*S,

! #**area** =
S * S

};

*` Circle (with radius R) `*

!!
[**Shape**] **Circle** (R) {

! #**put** = <: ['Circle
[' R ']'],

! #**perimeter** = 2***pi**(R),

! #**area**
= **pi**(R*R)

};

*` Triangle (with sides A, B,
C) `*

!! [**Shape**] **Triangle** (A B C) {

! #**put**
= <: ['Triangle (' A ',' B ',' C ')'],

! #**perimeter** =
A + B + C,

! #**area** : [p] =
{

*`(Heron's formula:)`*

p =
**perimeter**() / 2;

**sqr**(p*(p - A)*(p - B)*(p – C))

}

};

Now, we can create a list of simple geometric shapes (note using () for each shape's superclass):

Figures = (

**Square**
((), 5),

**Rectangle** ((), 6, 9),

**Circle** ((),
10),

**Triangle** ((), 5, 12, 13)

);

... and to get some information about these shapes:

**Shape**!!**l_loop** (fig,
Figures, fig.{

**put** ();

<: ": ";

<:
("Perimeter = ", **perimeter**(), "; ");

<:
("Area = ", **area**());

<: "\n";

});

This results in the following output:

Square [5]: Perimeter = 20; Area
= 25

Rectange (6 * 9): Perimeter = 30; Area = 54

Circle [10]:
Perimeter = 62.831853; Area = 314.15927

Triangle (5,12,13):
Perimeter = 30; Area = 30

(*To be continued...*)