========== Static Type Checking for POP-11 ===============================
For some years I have been trying to get static type checking working for
POP-11. I have just achieved a substantial measure of success, having checked
all defined procedures in a POP-11 file of some 1049 lines, including one
single procedure of some 79 lines which employs some fairly characteristic
open-stack use. Moreover, the type-checker found some actual bugs in what was
a modified version of an already debugged program. Incidentally, one of the
most useful roles for the typechecker -is- in modifying a complicated program
to perform essentially the same computation but with different call
conventions, data-structures or whatever.
Declarations are made quite independent of the main program text, using a
single macro "declare". The types of most procedures have to be declared, but
that of their local variables is inferred.
Parametric polymorphism and overloading are supported. For example, the
checker will typecheck a definition of -applist-, overloaded with two
signatures which encompass many of the uses of that versatile procedure.
Below is an initial stab at documentation for the type checker:
CONTENTS - (Use <ENTER> g to access required sections)
-- Introduction
-- Subsystem required
-- The concept of type
-- Monologs
-- Pseudonyms
-- Primitive types
-- Singletons
-- Unit and Top types
-- The Syntax of Type Declarations
-- The Product of Types - type "*" type
-- The Union of Types - type "+" type
-- The Kleene Star and related constructs - "{" type "}"
-- Procedure Types and Grammatical Quotient - type "->" type, type "/" type
-- Monologs are expressed by the form: "<" type ">"
-- Parametrically Polymorphic Monologs
-- Universal Quantification.
-- Type Functions - Hd and Tl
-- Recognisers
-- Overloading
-- Parametric Polymorphism of Procedures.
-- The "Int" type versus the "Integer" type.
-- How the type-checker works
-- The code predicate analyses a list of VM instructions
-- An environment holds declarations, productions and constraints
-- Predicates for basic operations on type-grammars
-- subtype(Ty1,Ty2,LE)
-- sum(Ty1,Ty2,Ty)
-- product(Ty1,Ty2,Ty) forms the product of grammars.
-- divide(Ty1,Ty2,Ty_quot,Ty_div,Env_in,Env_out)
-- solve_grammar solves productions and equational constraints.
-- Limitations
-- Limitations of current system - Status report
-- A Hard Example
-- References
Introduction
------------
Consider the POP-11 procedure:
define test_fail(x);
lvars x; x + 'fred'
enddefine;
If we have the type-checker loaded, and do
<enter> lcpt
("load current procedure checking type") the type-checker will print an error
message.
Error in type-checker:
In procedure " test_fail "
Can't resolve constraints .ty14 = <_2>*(String) / (Int)*(Int)
This means "I had on the stack something of undetermined type (<_2>) and
a string, and I needed a pair of integers.
However, if your POP-11 is OK, you will get something more reassuring
In response to:
define fact(n); lvars n;
if n=0 then 1 else n*fact(n-1) endif
enddefine;
you will get, after a commentary whose verbosity you can limit:
Type -- fact : ((Int) -> (Int))
Here the (...) brackets around "Int" indicate simply that "Int", which is an
alias for the actual type representation, has been expanded using a
type-equation.
Another type for -fact- is also correct, and can be verified by making the
declaration:
declare fact:Number->Number;
before invoking the type-checker. We say that -fact- is -overloaded-.
We can specify the type of lists with more precision than is available in SML:
declare solve_quadratic :
Number*Number*Number-> (List of (Number*Number));
define solve_quadratic(a,b,c);
lvars a,b,c,d = sqrt(b**2-4*a*c);
[% (-b + d)/(2*a), (-b -d)/(2*a) %]
enddefine;
That is to say "solve_quadratic" takes three numbers and returns a list
of -exactly- two numbers.
The types -inferred- for local variables may also be printed out. For
-solve_quadratic- they are:
Variable declarations in procedure
d : (Number)
c : (Number)
b : (Number)
a : (Number)
An annotated version of the Virtual Machine Code can also be output.
In the extract which follows, the "CODE" column is the VM instruction, the
"Type of STACK" specifies what is on the stack, and the "Val" column is a
limited symbolic representation of the value of the top of the stack. This is
used primarily for conditionals. The forms "_1", "_2" ... are unbound Prolog
variables.
5 pop(c) Number*Number
6 pop(b) Number
7 pop(a) ()
8 push(b) (Number) b
9 pushq(2) (Number)*(Int) b, 2
10 callw(**) Number b ** 2
11 pushq(4) (Number)*(Int) b ** 2, 4
12 push(a) (Number)*(Int)*(Number) .., 4, a
13 callw(*) (Number)*(Number) b ** 2, 4 * a
14 push(c) (Number)*(Number)*(Number) .., 4 * a, c
15 callw(*) (Number)*(Number) b ** 2, 4 * a * c
16 callw(-) Number b ** 2 - 4 * a * c
17 callw(sqrt) Number sqrt(b ** 2 - 4 *
a * c)
The "open stack" aspects of POP-11 are supported. Given just the definition:
define oneton(n);
lvars i=0,n;
[% while i<n do i,i+1->i endwhile %]
enddefine;
the type-checker will infer:
Type -- oneton : (Number) -> <List of {(Int)}>
that is to say "-oneton- takes a number and produces a list of integers".
The {....} brackets mean the Kleene-star operation, in this case "zero or more
short integers". The <....> brackets enclose "monologs", which will be
explained later.
Subsystem required
------------------
The type checker requires Prolog to be loaded. If Prolog is not loaded, all
declarations will be ignored.
The concept of type
-------------------
A -type- is a sequence of zero or more POP-11 objects. (The word "item" is
used in 2 ways in the POP-11 literature, either as something that is read in
by the -itemiser- or as single thing that exists internally. We use "object"
to mean something that exists internally to POP-11, while using "token" to
mean something read in by the itemiser.)
Such sequences can be regarded formally as a -language-, whose alphabet is
POP-11 objects (see Hopcroft and Ullman [1979] for a definition of language).
A language is described by a -grammar-. The descriptive apparatus that the
type-checker provides for users to describe types is based on -regular
expressions-. For example, the type-grammar:
Int * Number
characterises a sequence of two objects, the first of which is a short integer
and the second of which is a number.
Monologs
------------
A monolog is a type all of whose members are sequences of length one. In
POP-11, the type of any (non-active) variable is a monolog. Thus -Int- is
a monolog. Angle brackets <...> are used to enclose monologs in certain
circumstances.
Pseudonyms
--------------
A pseudonym is an identifier which is equated to a type. Thus -Integer- is
a pseudonym for objects whose dataword is either "integer" and "biginteger".
Primitive types
-------------------
A primitive type is a set of objects which have the same -datakey-. For
example -Int- is a built-in pseudonym for the primitive type of short
integers.
Singletons
--------------
A singleton is a type which consists of a monolog of just one object. For
example -False- is a singleton which consists of a sequence of just the
<false> object.
Unit and Top types
----------------------
-Unit- is a synonym for the type which consists just of the null sequence.
-Top- is the -maximum legal monolog-. That is to say it is a monolog which
contains all monologs that can legally be bound to variables. The type-checker
regards it as -illegal- to bind a stack-mark to a variable. -Top- is -not- the
least upper bound of the type-grammar.
The Syntax of Type Declarations
--------------------------------
In the syntax definitions below, we use the convention that non-terminals are
written without distinguishing marks, while terminal symbols are quoted. The
only extension to POP-11 is a -declare- statement. This may be added to an
existing program file, or placed in a separate file.
declaration -> "declare" decls ";"
decls -> decl "," decls
decl -> var_list ":" type
| var "?" type
var_list -> var "," var_list
type -> typeid
| "(" type ")"
| type "*" type
| type "+" type
| "{" type "}"
| "{" type "}" integer
| type "->" type
| "<" var ">"
| type "of" type
| "All" var_list ";" type
| type_function "(" type ")"
| type "/" type
The final, -quotient- form is not supported on input, but may appear in
output.
The non-terminal -var- is a POP-11 word, as is -typeid-. The system uses words
with a prepended ".".
The form "(" type ")" is used in input to override operator precedences.
The Product of Types - type "*" type
---------------------------------------
The form type "*" type is a -product- of regular expressions, and corresponds
roughly to the cartesian product of types in SML. It differs from the SML form
in that it is -associative-, so that (Int*Number)*Word is the same type as
Int*(Number*Word).
The Union of Types - type "+" type
------------------------------------
The union of types is expressed by:
type "+" type
Many built-in types are unions, for example 'Integer' is the union of the
monologs <integer> and <biginteger>.
The Kleene Star and related constructs - "{" type "}"
-------------------------------------------------------
The Kleene star operation is expressed by:
"{" type "}"
It means the union:
Unit + type + type*type + ....
We also need an extension of the notation to include a union of powers of a
type -not- beginning with -Unit-. The form:
"{" type "}" integer
means:
type^n + type^(n+1) ....
where n is the value of the integer.
Procedure Types and Grammatical Quotient - type "->" type, type "/" type
--------------------------------------------------------------------------
The (right) quotient of grammar G1 by a grammar G2 is expressed as G1/G2.
Internally, the system also uses a left quotient, e.g. for computing Tl (see
below).
Procedures are characterised by an -argument grammar- and a -result grammar-.
<type_args> -> <type_result>
Here <type_args> characterises what the procedure -takes off the stack-, and
<type_result> characterises what it puts on. A procedure type is a -monolog-.
Monologs are expressed by the form: "<" type ">"
---------------------------------------------------
The angle brackets "<" and ">" are used to enclose type variables that
denote a monolog.
Parametrically Polymorphic Monologs
-------------------------------------
Parametric polymorphism of non-procedures is supported by the construct:
type "of" type
Certain kinds of object (lists, members of a vector-class) are regarded as
-sequence types-. E.g. List of {Int} is a list of one or more integers.
Universal Quantification.
---------------------------
Type variables must be universally quantified in definitions.
"All" var_list ";" type
Type Functions - Hd and Tl
----------------------------
As well as the basic operations required for defining a regular grammar,
certain functions, which are -effective- on regular grammars, are provided.
The type-function -Hd- is used to obtain the -first monolog- of a grammar.
While -Hd- is used in specifying the type of the -hd- procedure, it should not
be confused with it. Note that:
Hd(Hd(type)) = Hd(type)
The type-function -Tl- is used to obtain the grammar that results from
removing the first monolog. That is Tl(a) is the -left quotient- of a by
Hd(a)).
Other type functions may be provided if the need arises. The most obvious are
Rev, Last and ButLast.
Recognisers
----------------
In POP-11, the members of a type-union are not discriminated by constructors
as they are in SML, although they are of course distinguishable at run-time.
The type-checker is able to handle this situation by allowing an identifier to
be declared to be a -recogniser-. The type checker knows that if a recogniser
is applied to a variable in the test of a conditional that it can restrict the
type of the variable in both branches of the conditional. Thus, for example:
declare test_recog:(Word+Number) -> Number;
define test_recog(x); lvars x;
if isnumber(x) then x+1 else subscrw(1,x) endif
enddefine;
will be passed as correct by the type-checker because -isnumber(x)- allows it
to infer -x:Number- between -then- and -else- so that -x+1- is valid,
and it can infer -x:Word- between -else- and -endif-, so that subscrw(1,x)
is valid.
The form <var> ? <type> is used to declare a -recogniser-. Thus
isnumber ? Number
declares isnumber as a recogniser for the type Number. Recognisers thus
declared have the type Top -> Boolean, that is to say they can be applied to
any object of the POP-11 language and always return a boolean, but they -only-
return true if the object is of the given type. The procedure -test_recog-
will fail under the following declaration:
declare test_recog:(Word+String) -> Number;
Error in type-checker:
No type recognised by recogniser <word+string>
The built-in procedure -null- is a special recogniser, which has the type
All a; List of a -> Boolean
If -null(V)- occurs in the test of a conditional, in the "yes" branch of a
conditional, the type of
Overloading
-----------
A variable whose type is the union of more than one procedure-type is said to
be "overloaded". Overloading allows us to be more specific about the behaviour
of procedure. For example, we may need to know that if we add two integers we
get an integer and not just a number because we want to use the result as an
index into an array. For example, "+" is overloaded:
declare + : (Int*Int->Int + Number*Number -> Number);
Note that a function is -not- overloaded if its argument type or result
type is a a union. Thus -fred- is not overloaded:
declare fred: (Int+String) -> Int;
Parametric Polymorphism of Procedures.
---------------------------------------
Let us consider a definition of a list-mapping function:
declare maplist_1:All a,b; List of {<a>} * (<a> -> <b>) -> List of {<b>};
define maplist_1(l,f);
lvars l,f;
if null(l) then []
else f(hd(l)) :: maplist_1(tl(l),f)
endif;
enddefine;
We can type-check a definition of function product:
declare fnprod: All a,b,c; (<b> -> <c>) * (<a> -> <b>) -> (<a> -> <c>);
define fnprod(f,g);
lvars f,g;
procedure(x);lvars x; f(g(x))
endprocedure
enddefine;
Actually, a slightly more general type-signature will also work:
declare fnprod: All a,b,c; (<b> -> c) * (<a> -> <b>) -> (<a> -> c);
The following signature for the built-in function will support most practical
uses of this function.
declare <> : All a,b,c; ( (<a> -> <b>) * (<b> -> c) -> (<a> -> c) ) +
( (<a> -> Unit) * (Unit -> c) -> (<a> -> c) );
Let us
declare aux_fp: All a,b,c; <a> * (<a> -> <b>) * (<b> -> c) -> c;
define aux_fp(f,g); lvars f,g;
g(f(x));
enddefine;
The "Int" type versus the "Integer" type.
-----------------------------------------
The "Int" type means "short integer". Strictly speaking it is inaccurate to
say that if you add two short integers you will necessarily get a short
integer. The standard overloading of "+" implies that you -do-. This is
intended to support -optimisation- in which fixed-integer addition replaces
the addition function. However the type-checker does not currently do this
optimisation.
A more rigorous version of the basic type declarations would have "Integer"
replacing "Int" in most operations except the bitwise-logical ones. Since
the type-checker can be run with a variety of type-signatures, even for
built-in procedures, there is a possibility of having efficient versus
rigorous versions.
How the type-checker works
---------------------------
The type-checker works by analysing the POPLOG Virtual Machine code generated
as a result of compiling POP-11 code, or in principle, code from any other
language. It makes use of a variant of -lib showcode-, which is thus
disabled from its normal mode of functioning (in other words, a much richer
description of the code is obtained).
The code predicate analyses a list of VM instructions
-------------------------------------------------------
It is written in Prolog, with a central predicate -code-. The call:
code([Instr|Code1],N_instr,Stk_in,Stk_out,T_in,T_out, Env_in,Env_out)
will analyse the VM instruction -Instr-, operating on the stack -Stk_in- in
the environment -Env_in-. The term -T_in- may be an expression for the top of
stack, and will be so if a recogniser has been applied to a variable.
The -code- predicate returns -Stk_out-, the stack resulting from applying the
instruction, -T_out- the resulting top-of-stack and -Env_out- the resulting
environment.
The code predicate, apart from optionally printing out a representation of the
state of the Virtual Machine, simply calls a predicate which analyses the
specific VM instruction. Thus the -PUSHQ- instruction is analysed by
the predicate -pushq-. The names of these predicates are all lower-case
versions of the names of the VM instructions, apart from those which clash
with standard system predicates (thus we use -callw- instead of -call-).
An instruction-predicate operates on the stack-grammar using the predicates
described below to perform the basic operations of multiplication and
division. For example, the -push- predicate will use -product- to multiply
the stack-grammar by the type of the variable that is being pushed. Jumps
and labels are handled by introducing -productions- for the state of the stack
at a label.
An environment holds declarations, productions and constraints
----------------------------------------------------------------
The environment is a list of local environments, each of which may
hold type-declarations, productions and equations. For efficiency reasons,
global type-declarations are moved into a POP-11 property at the end of each
procedure definition. The operators ':', '?', '==>', '=>' are infix, as
is '->'. The Kleene star operation and its variants are represented by
**(Type,N), which means {Type}N in the notation defined above.
Type declarations in an environment have the form Var:Type, except for
recogniser declarations which have the form Var?Type.
Productions in an environment have the form NT => Type, where NT is a
non-terminal symbol. The productions for a non-terminal are said to be -ripe-
if it is known that there no further productions for that non-terminal will be
forthcoming.
Predicates for basic operations on type-grammars
----------------------------------------------------
Each of the basic operation on a type-grammar is implemented by a predicate.
Thus:
sum(Ty1,Ty2,Ty)
will compute -Ty-, the union of the types Ty1 and Ty2. Some of the predicates
may affect an -environment-, e.g subtype(Ty1,Ty2,LE) will succeed if -Ty1- is
a subtype of -Ty2- provided that substitutions specified in the local
environment LE are performed. In the current implementation, type-aliases
are expanded as required by each predicate. Most of the predicates have
special clauses to handle monologs, since these have special properties.
For example, it is not in general true that (Ty1*Ty)/(Ty2*Ty) = Ty1/Ty2
But it is so if Ty is a monolog.
subtype(Ty1,Ty2,LE)
------------------------
This is quite a complicated predicate with clauses for monologs, products with
monologs and the sum-of-powers. It uses subtype_m as an auxiliary to handle
monologs, and this is the source of productions in LE. Thus:
:-subtype('.a',Int,LE)
succeeds, binding:
LE = [.a => m([integer])] ?
sum(Ty1,Ty2,Ty)
-------------------
This forms Ty = Ty1 + Ty2.
product(Ty1,Ty2,Ty) forms the product of grammars.
------------------------------------------------------
This forms a standardised Ty = Ty1*Ty2. Standardisation includes applying
associativity of "*", doing a cyclic interchange through the
extended Kleene-star, and combining extended Kleene-stars.
divide(Ty1,Ty2,Ty_quot,Ty_div,Env_in,Env_out)
----------------------------------------------
This is the most complex of the predicates. It divides type -Ty1- by type
-Ty2- giving the quotient -Ty_quot-. The type -Ty_div- is not currently used
by the system, but is intended as the least divisor that gives the same
quotient.
In some cases the division is -deferred- by inserting an equation
Ty = Ty1/Ty2
in the environment. For example, in the definition of factorial we find:
15 callw(fact) (Int)*r.26 fact(.U.)
16 callw(*) .ty13*(Int) .U.
Assert: .ty13 = (Int)*r.26 / (Int)*(Int)
solve_grammar solves productions and equational constraints.
----------------------------------------------------------------
Periodically, at the end of procedures, and when ripe labels and assignments
to ripe variables are encountered, the type-checker collects all productions
with ripe left-hand-sides and "solves" them. Essentially, it looks to see if
they form a linear grammar, and replaces recursion by the Kleene star,
systematically substituting for such solved type variables.
Suspended divisions are converted into inequalities, which may be used to
generate bindings for type-variables.
Limitations
-----------
These are divided into limitations which are inherent in the type-checker
and/or the POP-11 languages, and those which arise from features of the Poplog
Virtual Machine which have not been represented in the type-checker.
The main -inherent- limitation is that any stack operation that depends on
taking a -computed number of objects off the stack- cannot be supported. In
effect this limits constructions like that of vectors to using a -stackmark-
or its equivalent, or else using a literal. Doing this is normally regarded as
good practice in POP-11.
Thus {% 11,22,33 %} will pass the type-checker but consvector(11,22,33,n);
will not.
Types must be describable by a regular grammar. Thus:
define non_regular(n);
if n = 0 then 1 else "(", non_regular(n-1), ")"
endif;
enddefine;
will give rise to:
Environment:
1:
r.15 => (Int) + (Word)*r.15*(Word)
2:
non_regular : [(Int)->r.15]
End of Environment
Where the output type of -non_regular- is characterised by a non-regular
grammar.
However it should be possible to generate a weaker regular grammar (e.g.
at least the Kleene-star of the terminals.
Limitations of current system - Status report
------------------------------------------------
Below is a list of problems that I have found with the current system.
(1) Datawords are used rather than keys as the basis for primitive types.
(2) Variable declarations are currently handled by using a Prolog variable
for the type of the variable. It would be better to use type-variables, and
hold up the solution until all assignments to a variable have been made and it
becomes ripe. Currently therefore lvars v; 3 -> v; 3.5 -> v; will fail,
because v will get the type "Int".
(correction of this is underway)
(3) Wrong association of -> as compared to SML.
(4) Impossible divisions give rise to failed callw.
(5) Impossible divisions are not detected early (e.g. (Int)/(Int->Unit)).
Note that this requires care in correcting, because division by a sum of types
G/(G1 + G2) = G/G1 + G/G2
but the RHS divisions to not need to be -exact- for the LHS division to be
exact.
(6) Quantified expressions are not compared with "alpha conversion" as reqd.
(7) Global variables are not checked for being defined.
(8) Vector constructors with literal counts are -not- in fact supported.
(9) The only way to type-check a program that makes a record-class or
vector-class using -defclass- is to declare the constructors selectors and
recogniser -as ordinary procedures-. However the -defclass- statement itself
will not pass the type-checker, since the type-checker does not currently know
about the fields of keys.
(10) Various VM code instructions are not treated, including:
callq ucallq
(11) Locating an error within a complicated procedure currently requires
searching through a lot of other output, but I hope to improve things so that
the erroneous line in the source is displayed. Generally, searching for the
first '.ty' in the report will point to the source of error.
(12) Certain declarations still have to be input using Prolog. (This has been
improved vastly recently)
(13) while {% destvector(v) %} will typecheck correctly, [% destvector(v) %]
will not.
(14) Overloaded declarations are not type-checked - currently the overloaded
declaration must be split up into separate declarations and each run through
the checker for the overloaded procedure. However the overloaded declaration
can be used elsewhere.
(15) There is a possiblity of a clash between user type-variables, given a
prefix '.' on instantiation, and other type-variables generated by the system.
A Hard Example
--------------
To illustrate the fact that the type-checker can deal with some serious
POP-11 code, including partial application, I include the text of -ts_vg_body-
which has passed the type-checker, with the discovery of a bug. All the
type-signatures required for the checker are -not- given. -ts_vg_body- has
has the type signature:
declare
ts_vg_body:Int*Int*Int*Int*Number*(Int+Word)*Word*Style*Seq_TAS*Origin*
(Style*Map*Int*Int*Int*Origin->TAS) -> TAS;
and the definition:
define ts_vg_body( xl, xr, yt, yb, r, c, justif, S,DD,Org,f_cons);
lvars
xl xr ;;; Left and right margins.
yt yb ;;; Top and bottom margins
r c ;;; The scale and offset for the new baseline
justif, ;;; Justification, l,r,c
S,
DD, ;;; The term to be typeset
Org,
f_cons,
n = size(DD),
dy = dy_S(S)/14.0 ;;; Scaled w.r.t. 14 point font
;
scale(xl,dy) -> xl;
scale(xr,dy) -> xr;
scale(yt,dy) -> yt;
scale(yb,dy) -> yb;
lvars
i,
ht = yt, ;;; ht = height of new box = sum of heights of boxes
;;; initialised to yt because of topmargin
dx = 0, ;;; dx = width of new box = max of widths of boxes
;
for i from 1 to n do ;;; Work out maximum width of sub-boxes,
lvars D_i = sel(i,DD); ;;; to get width of new box.
unless is_expand(D_i) then
max(dx, dx_D(D_i))
-> dx;
endunless;
endfor;
lvars
DD1 = {% appdata(DD,
expand_to_width(%dx%))
%}, ;;; Expand it to the right width.
D_1 = sel(1,DD1), ;;; The top sub-document
D_n = sel(n,DD1), ;;; The bottom sub-document
a1 = asc_D(D_1), ;;; The ascent of the top box
(dx_n,dy_n,a_n)
= param_D(D_n),
map =
{%for i from 1 to n do ;;; Work out the x-coords. of the
lvars D_i = sel(i,DD1);
D_i;
if justif == "l" then xl ;;; sub-boxes, justified left,
elseif justif == "c" then ;;; justified centre
xl + int_of(0.5*(dx-dx_D(D_i)))
elseif justif == "r" then ;;; justified right
xl + dx-dx_D(D_i)
else
mishap('Justification specifier not l c r', [%DD%])
endif;
ht,
ht + dy_D(D_i) -> ht;
endfor%};
ht - dy_n + a_n -> a_n;
if c = "frac" then ;;; Setting a fraction ?
int_of(dy_D(sel(1,DD1)) ;;; Ascent is height of DD1(1)
+ 0.3*dy_S(S)) ;;; + enough to line up bar of "+"
-> a_n;
elseif r/==0 or c/==0 then
if isnumber(c) then
int_of(a_n + r*(a1 - a_n) + c) -> a_n;
endif;
endif;
f_cons(
S,
map,
(int_of(xl+dx+r), ht + yb),
a_n,
Org)
enddefine;
The usefulness of the inferred types for local variables can be seen from
the list below. Note that the ".lx..." variables are anonymous lexicals which
hold stack counts, and the ==> constraints arise from recognisers (and
are in fact redundant at the end of a procedure).
Variable declarations in procedure
.l8433 ==> [c : (Int)
c : (Word) ]
D_i1 : (TAS)
.lx6155 : (Int)
map : <vector of {((TAS)*(Int)*(Int))}>
a_n : (Int)
dy_n : (Int)
dx_n : (Int)
a1 : (Int)
D_n : (TAS)
D_1 : (TAS)
.lx6154 : (Int)
.lx6153 : (Int)
DD1 : (Seq_TAS)
.l8419 ==> [D_i : <_string+_id+_hbox+_hboxs+_vbox+_hline
+_frac+_subsuper>
D_i : (TAS_expand) ]
D_i : (TAS)
dx : (Int)
ht : (Int)
i : (Int)
dy : (Number)
n : (Int)
f_cons : ((Style)*<vector of {((TAS)*(Int)*(Int))}>*(Int)*(Int)*(Int)
*(Origin) -> TAS)
Org : (Origin)
DD : (Seq_TAS)
S : (Style)
justif : (Word)
c : <integer+word>
r : (Number)
yb : (Int)
yt : (Int)
xr : (Int)
xl : (Int)
References
----------
Hopcroft and Ullman [1979], "An Introduction to Automata Theory, Languages
and Computation", Addison-Wesley.
|