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.
}

{$O-}
{$I "Pascal.i"}
{$I "Include/StringLib.i"}
{$I "Include/Exec.i"}

	Procedure Error(s : string);
	    external;
	Procedure NextSymbol;
	    external;
	Procedure Abort;
	    external;
	Procedure PushLongD0;
	    external;
	Procedure PushLongD1;
	    External;
	Procedure PopLongD1;
	    external;
	Procedure PopLongD0;
	    External;

Procedure NewSpell;
var
    TempPtr : SpellRecPtr;
begin
    New(TempPtr);
    TempPtr^.Previous := CurrentSpellRec;
    CurrentSpellRec := TempPtr;
    CurrentSpellRec^.First := SpellPtr;
end;

Procedure BackUpSpell(Position : Integer);
var
    TempPtr : SpellRecPtr;
begin
    while Position < CurrentSpellRec^.First do begin
	TempPtr := CurrentSpellRec^.Previous;
	Dispose(CurrentSpellRec);
	CurrentSpellRec := TempPtr;
    end;
    SpellPtr := Position;
end;

Function EnterSpell(S : String) : String;
var
    Length : Integer;
    Result : String;
begin
    Length := strlen(S) + 1;
    if (Length + SpellPtr) - CurrentSpellRec^.First > Spell_Max then
	NewSpell;
    Result := Adr(CurrentSpellRec^.Data[SpellPtr - CurrentSpellRec^.First]);
    strcpy(Result, S);
    SpellPtr := SpellPtr + Length;
    EnterSpell := Result;
end;

Function SimpleType(testtype : TypePtr) : Boolean;

{
	If a variable passes this test, it is held in a register
during processing.  If not, the address of the variable is held in
the register.  This is the main reason why type conversions don't
work across all types of the same size.
}

begin
    SimpleType := (TestType^.Size <= 4) and
		  (TestType^.Size <> 3) and
		  (TestType^.Object <> ob_record) and
		  (TestType^.Object <> ob_array);
end;

Function BaseType(orgtype : TypePtr): TypePtr;

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

begin
    while (orgtype^.Object = ob_subrange) or (orgtype^.Object = ob_synonym) do
	orgtype := orgtype^.SubType;
    basetype := orgtype;
end;

Function HigherType(typea, typeb : TypePtr): TypePtr;

{
	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.
}

begin
    if (TypeA = RealType) or (TypeB = RealType) then
	HigherType := RealType;
    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 : TypePtr; other : TypePtr; reg : Short);

{
	This routine extends reg as necessary to make the 'from'
type equivalent to 'other'.
}

var
    totype : TypePtr;
begin
    from := basetype(from);
    other := basetype(other);
    totype := highertype(from, other);
    if from = totype then
	return;
    if totype = realtype then begin
	if from = bytetype then
	    writeln(OutFile, "\tand.l\t#255,d",reg)
	else if from = shorttype then
	    writeln(OutFile, "\text.l\td",reg);
	if reg = 0 then
	    PushLongD1
	else begin
	    PushLongD0;
	    writeln(OutFile, "\tmove.l\td1,d0");
	end;
	writeln(OutFile, "\tmove.l\t_p%MathBase,a6");
	writeln(OutFile, "\tjsr\t-36(a6)");		{ _LVOSPFlt }
	if reg = 0 then
	    PopLongD1
	else begin
	    writeln(OutFile, "\tmove.l\td0,d1");
	    PopLongD0;
	end;
	from := RealType;
    end else if totype = inttype then begin
	if from = shorttype then
	    writeln(OutFile, "\text.l\td", reg)
	else if from = bytetype then
	    writeln(OutFile, "\tand.l\t#255,d", reg);
	from := inttype;
    end else if totype = shorttype then begin
	if from = bytetype then
	    writeln(OutFile, "\tand.w\t#255,d", reg);
	from := shorttype;
    end;
end;

Procedure NewBlock;
var
    CB : BlockPtr;
    i  : Short;
begin
    New(CB);
    CB^.FirstType := Nil;
    for i := 0 to Hash_Size do
	CB^.Table[i] := Nil;
    if CurrentBlock = Nil then
	CB^.Level := 0
    else
	CB^.Level := Succ(CurrentBlock^.Level);
    CB^.Previous := CurrentBlock;
    CurrentBlock := CB;
end;

Procedure KillIDList(ID : IDPtr);
var
    TempID : IDPtr;
begin
    while ID <> Nil do begin
	if (ID^.Object = proc) or (ID^.Object = func) then
	    KillIDList(ID^.Param);
	TempID := ID^.Next;
	Dispose(ID);
	ID := TempID;
    end;
end;

Procedure KillBlock;
var
    CB : BlockPtr;
    ID : IDPtr;
    TP : TypePtr;
    i  : Integer;

    Procedure KillTypeList(TP : TypePtr);
    var
	TempType : TypePtr;
    begin
	while TP <> nil do begin
	    if TP^.Object = ob_record then
		KillIDList(TP^.Ref);
	    TempType := TP^.Next;
	    Dispose(TP);
	    TP := TempType;
	end;
    end;

begin
    CB := CurrentBlock;
    CurrentBlock := CurrentBlock^.Previous;
    for i := 0 to Hash_Size do
	KillIDList(CB^.Table[i]);
    KillTypeList(CB^.FirstType);
end;

Function Match(sym : Symbols): 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;

{
Function Hash(s : String) : Short;
var
    c : Char;
    i : Short;
    result : Integer;
begin
    result := strlen(s);
    i := 0;
    while s[i] <> Chr(0) do begin
	c := toupper(s[i]);
	result := ((result * 13 + Ord(c)) and $07ff);
	i := Succ(i);
    end;
    Hash := Result and Hash_Size;
end;
}

Procedure EnterID(EntryBlock : BlockPtr; ID : IDPtr);
var
    HVal : Short;
begin
    ID^.Level := EntryBlock^.Level;
    HVal := Hash(ID^.Name) and Hash_Size;
    ID^.Next := EntryBlock^.Table[HVal];
    EntryBlock^.Table[HVal] := ID;
end;

Function EnterStandard( st_Name : String;
			st_Object : IDObject;
			st_Type : TypePtr;
			st_Storage : IDStorage;
			st_Offset  : Integer)	: IDPtr;
var
    ID : IDPtr;
begin
    new(ID);
    ID^.Next := Nil;
    ID^.Name := EnterSpell(st_Name);
    ID^.Object := st_Object;
    ID^.VType := st_Type;
    ID^.Param := Nil;
    ID^.Storage := st_Storage;
    ID^.Offset := st_Offset;
    EnterID(CurrentBlock, ID);
    EnterStandard := ID;
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 : TypePtr) : 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  : IDPtr;
begin
    TypeA := BaseType(TypeA);
    TypeB := BaseType(TypeB);

    if TypeA = TypeB then
	TypeCmp := True;
    if (TypeA = BadType) or (TypeB = BadType) then
	TypeCmp := True;
    if TypeA^.Object <> TypeB^.Object then
	typecmp := false;
    if TypeA^.Object = ob_array then begin
	if (TypeA^.Upper - TypeA^.Lower) <>
	   (TypeB^.Upper - TypeB^.Lower) then
	    typecmp := false;
	TypeCmp := TypeCmp(TypeA^.Subtype, TypeB^.SubType);
    end;
    if TypeA^.Object = ob_pointer then
	TypeCmp := TypeCmp(TypeA^.SubType, TypeB^.SubType);
    if TypeA^.Object = ob_file then
	TypeCmp := TypeCmp(TypeA^.SubType, TypeB^.Subtype);
    TypeCmp := false;
end;

Function NumberType(testtype : TypePtr) : 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 : TypePtr) : Boolean;

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

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;

Function AddType(at_Object : TypeObject;
		 at_SubType: TypePtr;
		 at_Ref    : Address;
		 at_Upper,
		 at_Lower,
		 at_Size   : Integer) : TypePtr;

{
	Adds a type to the id array.
}

var
    TP	: TypePtr;
begin
    New(TP);
    TP^.Object := at_Object;
    TP^.SubType := at_SubType;
    TP^.Ref := at_Ref;
    TP^.Upper := at_Upper;
    TP^.Lower := at_Lower;
    TP^.Size  := at_Size;
    TP^.Next  := CurrentBlock^.FirstType;
    CurrentBlock^.FirstType := TP;
    AddType := TP;
end;

Function FindID(idname : string): IDPtr;
{ Find the most local reference to a variable }
var
    ID	: IDPtr;
    CB  : BlockPtr;
    HVal : Short;
begin
    CB := CurrentBlock;
    HVal := Hash(idname) and Hash_Size;
    while CB <> nil do begin
	ID := CB^.Table[HVal];
	while ID <> nil do begin
	    if strieq(idname, ID^.Name) then
		FindID := ID;
	    ID := ID^.Next;
	end;
	CB := CB^.Previous;
    end;
    FindID := Nil;
end;

Function CheckID(idname : string): IDPtr;

{
	This is like the above, but only checks the current block.
}

var
    ID : IDPtr;
begin
    ID := CurrentBlock^.Table[Hash(idname) and Hash_Size];
    while ID <> nil do begin
	if strieq(idname, ID^.Name) then
	    CheckID := ID;
	ID := ID^.Next;
    end;
    CheckID := Nil;
end;

Function CheckIDList(S : String; ID : IDPtr) : Boolean;
begin
    while ID <> nil do begin
	if strieq(S, ID^.Name) then
	    CheckIDList := True;
	ID := ID^.Next;
    end;
    CheckIDList := False;
end;

Function FindField(idname : string; RecType : TypePtr) : IDPtr;

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

}

var
    ID	: IDPtr;
begin
    ID := RecType^.Ref;
    while ID <> Nil do begin
	if strieq(idname, ID^.Name) then
	    FindField := ID;
	ID := ID^.Next;
    end;
    FindField := Nil;
end;

Function FindWithField(Str : String) : IDPtr;
var
    CurrentWith : WithRecPtr;
    ID : IDPtr;
begin
    CurrentWith := FirstWith;
    while CurrentWith <> Nil do begin
	ID := FindField(Str, CurrentWith^.RecType);
	if ID <> Nil then begin
	    LastWith := CurrentWith;
	    FindWithField := ID;
	end;
	CurrentWith := CurrentWith^.Previous;
    end;
    FindWithField := Nil;
end;

Function IsVariable(ID : IDPtr) : Boolean;

{
	Returns true if index is a variable.
}

begin
    case ID^.Object of
	local,
	refarg,
	valarg,
	global,
	typed_const,
	field	: IsVariable := True;
    else
	IsVariable := False;
    end;
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;

{
Procedure WriteTabs(Tabs : Short);
var
   I : Short;
begin
    I := 0;
    while I < Tabs do begin
	Write(' ');
	I := Succ(I);
    end;
end;

Procedure WriteID(ID : IDPtr; Tabs : Short; Primary : Boolean);
    forward;

Procedure WriteType(TP : TypePtr; Tabs : Short; Primary : Boolean);
var
    ID : IDPtr;
begin
    if CheckBreak() then
	Abort;
    case TP^.Object of
	ob_array : begin
		     write('Array [', TP^.lower, '..', TP^.upper, '] of ');
		     WriteType(TP^.SubType, Tabs, True);
		   end;
	ob_record : begin
			Write('Record');
			if not Primary then
			    return
			else
			    Writeln;
			ID := TP^.Ref;
			while ID <> Nil do begin
			    WriteID(ID, Tabs + 4, False);
			    ID := ID^.Next;
			end;
			WriteTabs(Tabs);
			Write('END');
		    end;
	ob_ordinal : begin
			if TP = IntType then
			    Write('Integer')
			else if TP = ShortType then
			    Write('Short')
			else if TP = BoolType then
			    Write('Boolean')
			else if TP = CharType then
			    Write('Char')
			else if TP = ByteType then
			    Write('Byte')
			else if TP = BadType then
			    Write('Universal')
			else
			    Write('Enumerated');
		    end;
	ob_pointer : begin
			write('^');
			WriteType(TP^.SubType, Tabs, Primary);
		    end;
	ob_file   : begin
			if TP = TextType then
			    Write('Text')
			else begin
			    write('File of ');
			    WriteType(TP^.SubType,Tabs, True);
			end;
		    end;
	ob_real   : Write('Real');
	ob_subrange : begin
			Write(TP^.Lower, ' .. ', TP^.Upper, ' of ');
			WriteType(TP^.SubType, Tabs, True);
		      end;
    end;
end;
			    
procedure WriteID(ID : IDPtr; Tabs : Short; Primary : Boolean);
var
    TempID : IDPtr;
begin
    if CheckBreak() then
	Abort;
    WriteTabs(Tabs);
    case ID^.Object of
	global,
	local	: write('VAR ');
	refarg	: write('REF ');
	valarg	: write('VAL ');
	typed_const : write('IVAR ');
	proc,
	stanproc: write('Procedure ');
	stanfunc,
	func	: write('Function  ');
	obtype	: write('TYPE ');
	constant : write('CONST ');
    end;
    if ID^.Name = nil then
	Write('""')
    else
	Write(ID^.Name);
    if (ID^.Object = proc) or (ID^.Object = func) then begin
	TempID := ID^.Param;
	write('(');
	while TempID <> nil do begin
	    WriteID(TempID, Tabs + 4, True);
	    TempID := TempID^.Next;
	end;
	write(')');
    end;
    if (ID^.Object <> proc) and (ID^.Object <> stanproc) then begin
	if (ID^.Object = constant) or (ID^.Object = refarg) or
	   (ID^.Object = valarg) or (ID^.Object = local) or
	   (ID^.Object = field) then
	    Write(' (', ID^.Offset, ') ');
	Write(' : ');
	WriteType(ID^.VType, Tabs, Primary)
    end;
    writeln(';');
end;

Procedure Decompose;
var
    CB : BlockPtr;
    ID : IDPtr;
    TP : TypePtr;
    i  : Integer;
begin
    Writeln("\nCurrent contents of the symbol table:");
    CB := CurrentBlock;
    while CB <> nil do begin
	Writeln("\nLevel ", CB^.Level, "\n");
	Writeln("Identifiers\n");
	for i := 0 to Hash_Size do begin
	    ID := CB^.Table[i];
	    Writeln('Hash ', i);
	    while ID <> nil do begin
		WriteID(ID, 0, True);
		ID := ID^.Next;
	    end;
	end;
	Writeln("\nTypes\n");
	TP := CB^.FirstType;
	while TP <> nil do begin
	    WriteType(TP, 0, True);
	    Writeln;
	    TP := TP^.Next;
	end;
	CB := CB^.Previous;
    end;
end;
}

Function CompareProcs(Proc1, Proc2 : IDPtr) : Boolean;
var
    ID1, ID2 : IDPtr;
begin
    if Proc1^.Object <> Proc2^.Object then
	CompareProcs := False;
    if Proc1^.Object = func then
	if not TypeCmp(Proc1^.VType, Proc2^.VType) then
	    CompareProcs := False;
    ID1 := Proc1^.Param;
    ID2 := Proc2^.Param;
    while (ID1 <> Nil) and (ID2 <> Nil) do begin
	if not TypeCmp(ID1^.VType, ID2^.VType) then
	    CompareProcs := False;
	ID1 := ID1^.Next;
	ID2 := ID2^.Next;
    end;
    CompareProcs := ID1 = ID2;
end;
