external;

{
	Utilities.p (of PCQ Pascal)
	Copyright (c) 1989 Patrick Quaid.

	This module handles the various tables and whatever
run-time business the compiler might have.
}

const
{$I "pasconst.i"}

type
{$I "pastype.i"}

var
{$I "pasvar.i"}

	procedure error(s : string);
	    forward;
	function streq(s1, s2 : string) : boolean;
	    forward;
	function strcmp(s1, s2 : string) : integer;
	    forward;
	procedure nextsymbol;
	    forward;

function basetype(orgtype : integer): integer;

{
	This routine returns the base type of type.  If this
routine is used consistently, ranges and subtypes will work with
some consistency.
}

begin
    while (idents[orgtype].offset = vsubrange) or
	  (idents[orgtype].offset = vsynonym) do
	orgtype := idents[orgtype].vtype;
    basetype := orgtype;
end;

function highertype(typea, typeb : integer): integer;

{
	This routine returns the more complex type of the two
numeric types passed to it.  In other words a 32 bit integer is
'higher' than a 16 bit one.  When real numbers get in the language,
floating point will be the most complex numeric type.
}

begin
    if (typea = inttype) or (typeb = inttype) then
	highertype := inttype;
    if (typea = shorttype) or (typeb = shorttype) then
	highertype := shorttype;
    highertype := typea;
end;

procedure promotetype(var from : integer; other : integer; reg : integer);

{
	This routine extends reg as necessary to make the 'from'
type equivalent to 'other'.  Again, when real numbers are
implemented this will also be responsible for converting the reg to
FFP format.
}

var
    totype : integer;
begin
    from := basetype(from);
    other := basetype(other);
    totype := highertype(from, other);
    if from = totype then
	return;
    if totype = inttype then begin
	if from = shorttype then
	    writeln(output, "\text.l\td", reg)
	else if from = bytetype then begin
	    writeln(output, "\text.w\td", reg);
	    writeln(output, "\text.l\td", reg);
	end;
	from := inttype;
    end else if totype = shorttype then begin
	if from = bytetype then
	    writeln(output, "\text.w\td", reg);
	from := shorttype;
    end;
end;

function match(sym : integer): boolean;

{
	If the current symbol is sym, return true and get the
next one.
}

begin
    if currsym = sym then begin
	nextsymbol;
	match := true;
    end else
	match := false;
end;

{
	The following routines just print out common error messages
and make some common tests.
}
 
procedure mismatch;
begin
    error("Mismatched types");
end;

procedure neednumber;
begin
    error("Need a numeric type");
end;

procedure noleftparent;
begin
    error("No left parenthesis");
end;

procedure norightparent;
begin
    error("No right parenthesis");
end;

procedure needleftparent;
begin
    if not match(leftparent1) then
	noleftparent;
end;

procedure needrightparent;
begin
    if not match(rightparent1) then
	norightparent;
end;

procedure enterspell(str : string);

{
	This enters the string into the spelling table.
}

begin
    while str^ <> chr(0) do begin
	spelling[spellptr] := str^;
	str := string(integer(str) + 1);
	spellptr := spellptr + 1;
    end;
    spelling[spellptr] := chr(0);
    spellptr := spellptr + 1;
end;

function enterstandard(stobject, stoffset, sttype, stupper, stlower,
			stsize, stindtype : integer) : integer;

{
	This just adds the appropriate record to the array.  It
gets its name because it was originally used to add standard procs
and funcs, but in fact in can be used for just about anything.
}

begin
    idents[identptr].name := string(integer(adr(spelling)) + spellptr - 1);
    idents[identptr].object  := stobject;
    idents[identptr].offset  := stoffset;
    idents[identptr].vtype   := sttype;
    idents[identptr].upper   := stupper;
    idents[identptr].lower   := stlower;
    idents[identptr].size    := stsize;
    idents[identptr].indtype := stindtype;
    identptr := identptr + 1;
    enterstandard := identptr - 1;
end;

procedure ns;

{
	This routine just tests for a semicolon.
}

begin
    if not match(semicolon1) then begin
	if (currsym <> end1) and (currsym <> else1) and (currsym <> until1) then
	    error("missing semicolon");
    end else
	while match(semicolon1) do;
end;

function typecmp(typea, typeb : integer) : boolean;

{
	This routine just compares two types to see if they're
equivalent.  Subranges of the same type are considered equivalent.
Note that 'badtype' is actually a universal type used when there
are errors, in order to avoid streams of errors.
}

var
	t1ptr,
	t2ptr  : integer;
begin
    typea := basetype(typea);
    typeb := basetype(typeb);

    if typea = typeb then
	typecmp := true;
    if (typea = badtype) or (typeb = badtype) then
	typecmp := true;
    if idents[typea].offset <> idents[typeb].offset then
	typecmp := false;
    if idents[typea].size <> idents[typeb].size then
	typecmp := false;
    if idents[typea].offset = varray then begin
	if (idents[typea].upper - idents[typea].lower) <>
	   (idents[typeb].upper - idents[typeb].lower) then
	    typecmp := false;
	typecmp := typecmp(idents[typea].vtype, idents[typeb].vtype);
    end;
    if idents[typea].offset = vpointer then
	typecmp := typecmp(idents[typea].vtype, idents[typeb].vtype);
    if idents[typea].offset = vfile then
	typecmp := typecmp(idents[typea].vtype, idents[typeb].vtype);
    if idents[typea].offset = vrecord then begin
	t1ptr := idents[typea].indtype;
	t2ptr := idents[typeb].indtype;
	while (t1ptr <> 0) and (t2ptr <> 0) do begin
	    if not typecmp(idents[t1ptr].vtype, idents[t2ptr].vtype) then
		typecmp := false;
	    t1ptr := idents[t1ptr].indtype;
	    t2ptr := idents[t2ptr].indtype;
	end;
	typecmp := t1ptr = t2ptr;
    end;
    if (idents[typea].offset = vordinal) and
	(idents[typea].indtype <> 0) then begin
	t1ptr := idents[typea].indtype;
	t2ptr := idents[typeb].indtype;
	while (t1ptr <> 0) and (t2ptr <> 0) do begin
	    if not streq(idents[t1ptr].name, idents[t2ptr].name) then
		typecmp := false;
	    t1ptr := idents[t1ptr].indtype;
	    t2ptr := idents[t2ptr].indtype;
	end;
	typecmp := t1ptr = t2ptr;
    end;
    typecmp := false;
end;

function numbertype(testtype : integer) : boolean;

{
	Return true if this is a numeric type.
}

begin
    testtype := basetype(testtype);
    if testtype = inttype then
	numbertype := true
    else if testtype = shorttype then
	numbertype := true
    else if testtype = bytetype then
	numbertype := true;
    numbertype := false;
end;

function typecheck(typea, typeb : integer) : boolean;

{
	This is similar to typecmp, but considers numeric types
equivalent.
}

begin
    if (idents[typea].object = obtype) and
	(idents[typeb].object = obtype) then begin
	typea := basetype(typea);
	typeb := basetype(typeb);
	if typea = typeb then
	    typecheck := true;
	if numbertype(typea) and numbertype(typeb) then
	    typecheck := true;
	typecheck := typecmp(typea, typeb);
   end else
	typecheck := false;
end;

function addtype(typoff, typtype, typup, typlow,
			typsize, typind : integer) : integer;

{
	Adds a type to the id array.
}

var
    index	: integer;
    found	: boolean;
begin
    idents[identptr].name    := string(adr(spelling));
    idents[identptr].object  := obtype;
    idents[identptr].offset  := typoff;
    idents[identptr].vtype   := typtype;
    idents[identptr].upper   := typup;
    idents[identptr].lower   := typlow;
    idents[identptr].size    := typsize;
    idents[identptr].indtype := typind;

    identptr := identptr + 1;
    addtype := identptr - 1;
end;

function findid(idname : string): integer;

{
	This finds the index whose 'name' field is the same as
idname, or zero if it doesn't find it.  Note that this searches
backwards, in order to properly do scopes.  It will run into the
most local identifiers first.
	I once thought about implementing case sensitivity through
a compiler directive.  It would have been fairly simple, actually:
just use separate routines in place of streq and strcmp in the
following routines.  These new routines should be case sensitive,
of course.
}

var
    index	: integer;
begin
    index := identptr - 1;
    while index > 0 do begin
	if streq(idname, idents[index].name) then
	    findid := index;
	index := index - 1;
    end;
    findid := 0;
end;

function checkid(idname : string; startspot : integer): integer;

{
	This is like the above, but only checks as far back as
startspot in order to implement scopes.  This is used to make sure
there are no identifiers with the same name under the same scope.
}

var
    index	: integer;
begin
    index := startspot;
    while index < identptr do begin
	if idents[index].object <> field then
	    if streq(idname, idents[index].name) then
		checkid := index;
	index := index + 1;
    end;
    checkid := 0;
end;

function findfield(idname : string; startspot : integer) : integer;

{
	This just finds the appropriate field, given the index of
the record type.
}

var
    index	: integer;
begin
    index := idents[startspot].indtype;
    while index <> 0 do begin
	if streq(idname, idents[index].name) then
	    findfield := index;
	index := idents[index].indtype;
    end;
    findfield := 0;
end;

function searchreserved() : integer;

{
	This just does a binary chop search of the list of reserved
words.
}

var
    top		: integer;
    middle	: integer;
    bottom	: integer;
    compare	: integer;
begin
    bottom := 1;
    top := lastreserved;
    while bottom <= top do begin
	middle := (bottom + top) div 2;
	compare := strcmp(reserved[middle], symtext);
	if compare = 0 then
	    searchreserved := middle
	else if compare < 0 then
	    bottom := middle + 1
	else
	    top := middle - 1;
    end;
    searchreserved := 0;
end;

function isvariable(index : integer) : boolean;

{
	Returns true if index is a variable.
}

var
    what	: integer;
begin
    what := idents[index].object;
    if what = local then
	isvariable := true
    else if what = refarg then
	isvariable := true
    else if what = valarg then
	isvariable := true
    else if what = global then
	isvariable := true
    else
	isvariable := false;
end;

function suffix(size : integer): char;

{
	Returns the proper assembly language suffix for the various
operations.
}

begin
    if size = 1 then
	suffix := 'b'
    else if size = 2 then
	suffix := 'w'
    else if size = 4 then
	suffix := 'l'
    else {must be a bug!}
	suffix := '!';
end;

