Dedicated environment:
Dedicated Editor:
Five kinds of type:
Unbounded, exact & approximate
Unbounded length strings
Records without field names.
Sorted bags, with elements of any one type.
Finite mapping of keys of any one type onto items of any one type.
PUT 0, -1 IN z
z is a compound with two numeric fields.
PUT 0, "zero" IN z
is a type error.
These are polymorphic:
HOW TO SWAP a WITH b:
PUT a, b IN b, a
Functions
HOW TO RETURN cosh x:
PUT exp x IN ex
RETURN (ex+1/ex)/2
Global variables
HOW TO PUSH v: SHARE stack PUT v IN stack[#stack+1]
>>> PUT {} IN tel
>>> PUT 4141 IN tel["Lambert"]
>>> PUT 4071 IN tel["Leo"]
>>> PUT 4138 IN tel["Frank"]
>>> WRITE tel["Leo"]
4071
>>> WRITE tel
{["Frank"]: 4138; ["Lambert"]: 4141; ["Leo"]: 4071}
>>> FOR name IN keys tel:
WRITE name, ": ", tel[name]/
Frank: 4138
Lambert: 4141
Leo: 4071
>>> PUT {} IN user
>>> FOR name IN keys tel:
PUT name IN user[tel[name]]
>>> WRITE user[4141]
Lambert
>>> WRITE user
{[4071]: "Leo"; [4138]: "Frank"; [4141]: "Lambert"}
Consider the following commands:
PUT 0 IN a
a must therefore be a number
PUT a IN b
a and b are therefore the same type
WRITE t[b]
t is a table, whose keys have the same type as b
The (possibly incomplete) information about a type is represented by a polytype
A polytype can be
Thus
a= Number
b= B (say)
t= Table(B,T)
Each variable is given a polytype, initially just a name (ie unknown).
Whenever a line of source is processed it is analysed and new polytypes are created and unified with the existing ones.
Two polytypes are unified as follows:
Unify (Number, A, Number)
with (B, Table(C, D), C) from
PUT d IN t[c] PUT 0, a, 0 IN b, t, c
They are both compounds of the same length so unify
From this,
And this is the unification, with the additional information that
A = T= Table(Number, D)
B = Number
C = Number
The algorithm as presented so far can be used to calculate the 'local' type requirements of a user defined command.
These are then 'projected' on to its parameters. Thus with
HOW TO SHOW s AT k:
WRITE s[k]
the polytype of SHOW is (Table(K, X), K).
When nothing is known about a command, its polytypes are all names.
A function like
HOW TO RETURN cosh x:
PUT exp x IN ex
RETURN (ex+1/ex)/2
can be treated as if it was
HOW TO COSH x GIVING y:
PUT exp x IN ex
PUT (ex+1/ex)/2 IN y
Similarly the use of an expression like
WRITE a**(i+1)
can be treated as
ADD i AND 1 GIVING tmp1 RAISE a TO tmp1 GIVING tmp2 WRITE tmp2
Generic texts, lists and tables, e.g.
WRITE #t
FOR x IN t: WRITE x
PUT {} IN t
WRITE {a..b}
Adds new kinds of polytypes:
Adds new rules to unification.
Types are checked incrementally
The 'source' and code of an ABC program are represented by a single (threaded), largely abstract, tree.
Each node in the tree has an identifier, and a number of sub-nodes.
For instance, for a PUT command, you have
[ PUT | . | . ]
/ \
expr address
Mini-interpreters for tasks not performed frequently, e.g.
Code for mini-interpreters held as strings.
For instance, for displaying the put command:
PUT: "PUT 1 IN 2"
The 1 and 2 refer to the corresponding sub-nodes.
Stack of polytypes kept.
Tree is processed by
This results usually in recursive analysis of the subtrees.
For instance, for PUT we have:
PUT: "eeU"
e -- analyse and push the next subnode
U -- pop(x); pop(y); unify(x, y);
For the command
INSERT item IN list
we have
INSERT: "e}eU"
} -- pop(x); push(listof(x))
For the list display in
WRITE {1; 2; 3}
we have
List display: "e<eu>}"
< > -- loop over all sub-nodes
u -- pop(x); pop(y); push(unify(x, y));
For ranges in list displays, like
WRITE {a..b}
we have
range bounds: "e.ueu"
. -- push(text-or-number);
WRITE {[a]: b; ... }
has
table: "ee<~eu~eu>]"
~ -- swap top two elements on stack
]-- pop(a); pop(k); push(table(k, a));
For instance:
Op Stacke: Ke: KA ~: AK e: AKE u: AK' ~: K'A e: K'AE u: K'A' etc. ]: Table(K', A')
Incremental, interactive type checking
No declarations
Uses unification
Simple small interpreter for type checker