-- Copyright (c) 1990 Regents of the University of California.
-- All rights reserved.
--
--    The primary authors of ayacc were David Taback and Deepak Tolani.
--    Enhancements were made by Ronald J. Schmalz.
--
--    Send requests for ayacc information to ayacc-info@ics.uci.edu
--    Send bug reports for ayacc to ayacc-bugs@ics.uci.edu
--
-- Redistribution and use in source and binary forms are permitted
-- provided that the above copyright notice and this paragraph are
-- duplicated in all such forms and that any documentation,
-- advertising materials, and other materials related to such
-- distribution and use acknowledge that the software was developed
-- by the University of California, Irvine.  The name of the
-- University may not be used to endorse or promote products derived
-- from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
-- IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
-- WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

-- Module       : symbol_table_body.ada
-- Component of : ayacc
-- Version      : 1.2
-- Date         : 11/21/86  12:37:53
-- SCCS File    : disk21~/rschm/hasee/sccs/ayacc/sccs/sxsymbol_table_body.ada

-- $Header: symbol_table_body.a,v 0.1 86/04/01 15:13:55 ada Exp $ 
-- $Log:	symbol_table_body.a,v $
-- Revision 0.1  86/04/01  15:13:55  ada
--  This version fixes some minor bugs with empty grammars 
--  and $$ expansion. It also uses vads5.1b enhancements 
--  such as pragma inline. 
-- 
-- 
-- Revision 0.0  86/02/19  18:54:02  ada
-- 
-- These files comprise the initial version of Ayacc
-- designed and implemented by David Taback and Deepak Tolani.
-- Ayacc has been compiled and tested under the Verdix Ada compiler
-- version 4.06 on a vax 11/750 running Unix 4.2BSD.
--  

package body Symbol_Table is 

  SCCS_ID : constant String := "@(#) symbol_table_body.ada, Version 1.2";


    
    Next_Free_Terminal     : Grammar_Symbol :=  0;
    Next_Free_Nonterminal  : Grammar_Symbol := -1;

    Start_Symbol_Pos, End_Symbol_Pos : Grammar_Symbol;

    type String_Pointer is access String;

    type Table_Entry(ID : Grammar_Symbol);
    type Entry_Pointer is access Table_Entry;

    type Table_Entry(ID :Grammar_Symbol) is
	record
	    Name : String_Pointer;
	    Next : Entry_Pointer;
	    case ID is
		when 0..Grammar_Symbol'Last =>   -- Terminal
		    Prec  : Precedence;
		    Assoc : Associativity;
		when others =>                   -- Nonterminal
		    null;
	    end case;
	end record;

    Hash_Table_Size : constant := 613;    -- A large prime number

    type Hash_Index is range 0..Hash_Table_Size-1;

    Hash_Table : array(Hash_Index) of Entry_Pointer;


    --
    --    Create a 'dynamic' array for looking up an entry
    --    for a given grammar_symbol.
    --


    Block_Size : constant Grammar_Symbol := 1500;
    type Lookup_Array is 
	array(Grammar_Symbol range 0..Block_Size-1) of Entry_Pointer;
    type Block;
    type Block_Pointer is access Block;
    type Block is
	record
	    Lookup : Lookup_Array;
	    Next   : Block_Pointer := null;
	end record;


    -- have separate blocks for terminals and nonterminals.

    Terminal_Blocks, Nonterminal_Blocks : Block_Pointer := new Block;

    procedure Make_Lookup_Table_Entry
	(ID        : in Grammar_Symbol;
	 Entry_Ptr : in Entry_Pointer) is

	ID_Block     : Block_Pointer;
	Block_Number : Integer;

    begin
	if ID >= 0  then
	    ID_Block := Terminal_Blocks;
	else
	    ID_Block := Nonterminal_Blocks;
	end if;
	Block_Number := Integer (abs ID / Block_Size);

	for I in 1..Block_Number loop
	    if ID_Block.Next = null then
		ID_Block.Next := new Block;
	    end if;
	    ID_Block := ID_Block.Next;
	end loop;
	ID_Block.Lookup((abs ID) mod Block_Size) := Entry_Ptr;
    end Make_Lookup_Table_Entry;

    function Get_Lookup_Table_Entry(ID: Grammar_Symbol) return Entry_Pointer is
	ID_Block : Block_Pointer;
    begin
	if ID >= 0 then
	    ID_Block := Terminal_Blocks;
	else
	    ID_Block := Nonterminal_Blocks;
	end if;
	for I in 1.. abs ID / Block_Size loop
	    ID_Block := ID_Block.Next;
	end loop;
	return ID_Block.Lookup((abs ID) mod Block_Size);
    end Get_Lookup_Table_Entry;



    -- some day someone should put in a good hash function.
    function Hash_Value (S : String) return Hash_Index is
	H   : Integer;
	Mid : Integer;
    begin
	Mid := (S'First + S'Last) / 2;
	H := ((Character'Pos(S(S'First)) +
	       Character'Pos(S(Mid)) +
	       Character'Pos(S(S'Last)))
	       * S'Length * 16 ) mod Hash_Table_Size;
	return Hash_Index(H);
    end Hash_Value;



    function Insert_Identifier (Name: in String) return Grammar_Symbol is

	Index : Hash_Index;
	Entry_Ptr : Entry_Pointer;

    begin

	Index := Hash_Value(Name);

	Entry_Ptr := Hash_Table(Index);

	if Entry_Ptr = null then

	    Entry_Ptr           :=  new Table_Entry(Next_Free_Nonterminal);
	    Entry_Ptr.Name      :=  new String(1..Name'Length);
	    Entry_Ptr.Name.all  :=  Name;
	    Hash_Table(Index)   :=  Entry_Ptr;

	    Make_Lookup_Table_Entry(Next_Free_Nonterminal, Entry_Ptr);
	    Next_Free_Nonterminal  :=  Next_Free_Nonterminal - 1;
	
	else

	    loop
		if Entry_Ptr.Name.all = Name then
		    return Entry_Ptr.ID;
		end if;
		if Entry_Ptr.Next = null then
		    exit;
		end if;
		Entry_Ptr := Entry_Ptr.Next;
	    end loop;

	    Entry_Ptr.Next         := new Table_Entry(Next_Free_Nonterminal);
	    Entry_Ptr              := Entry_Ptr.Next;
	    Entry_Ptr.Name         :=  new String(1..Name'Length);
	    Entry_Ptr.Name.all     :=  Name;

	    Make_Lookup_Table_Entry(Next_Free_Nonterminal, Entry_Ptr);
	    Next_Free_Nonterminal  :=  Next_Free_Nonterminal - 1;

	end if;

	return Next_Free_Nonterminal + 1;

    end Insert_Identifier;

    function Insert_Terminal
	(Name  :  String;
	 Prec  :  Precedence := 0; 
	 Assoc :  Associativity := Undefined) return Grammar_Symbol is

	Index : Hash_Index;
	Entry_Ptr : Entry_Pointer;

    begin

	Index := Hash_Value(Name);

	Entry_Ptr := Hash_Table(Index);

	if Entry_Ptr = null then

	    Entry_Ptr           :=  new Table_Entry(Next_Free_Terminal);
	    Entry_Ptr.Name      :=  new String(1..Name'Length);
	    Entry_Ptr.Name.all  :=  Name;
	    Entry_Ptr.Assoc     :=  Assoc;
	    Entry_Ptr.Prec      :=  Prec;
	    Hash_Table(Index)   :=  Entry_Ptr;

	    Make_Lookup_Table_Entry(Next_Free_Terminal, Entry_Ptr);
	    Next_Free_Terminal  :=  Next_Free_Terminal + 1;
	
	else

	    loop
		if Entry_Ptr.Name.all = Name then
		    if Entry_Ptr.ID < 0 then    -- Look out for nonterminals
			raise Illegal_Entry;
		    end if;
		    if Prec /= 0 then
			if Entry_Ptr.Prec /= 0 then
			    raise Redefined_Precedence_Error;
			end if;
			Entry_Ptr.Prec  := Prec;
			Entry_Ptr.Assoc := Assoc;
		    end if;
		    return Entry_Ptr.ID;
		end if;
		if Entry_Ptr.Next = null then
		    exit;
		end if;
		Entry_Ptr := Entry_Ptr.Next;
	    end loop;

	    Entry_Ptr.Next      := new Table_Entry(Next_Free_Terminal);
	    Entry_Ptr           := Entry_Ptr.Next;
	    Entry_Ptr.Name      :=  new String(1..Name'Length);
	    Entry_Ptr.Name.all  :=  Name;
	    Entry_Ptr.Assoc     :=  Assoc;
	    Entry_Ptr.Prec      :=  Prec;
	    
	    Make_Lookup_Table_Entry(Next_Free_Terminal, Entry_Ptr);
	    Next_Free_Terminal  :=  Next_Free_Terminal + 1;

	end if;

	return Next_Free_Terminal - 1;

    end Insert_Terminal;

    function Get_Associativity (ID: Grammar_Symbol) return Associativity is
    begin
	return Get_Lookup_Table_Entry(ID).Assoc;
    end;

    function Get_Precedence    (ID: Grammar_Symbol) return Precedence is
    begin
	return Get_Lookup_Table_Entry(ID).Prec;
    end;

    function Get_Symbol_Name   (ID: Grammar_Symbol) return String is
    begin
	return Get_Lookup_Table_Entry(ID).Name.all;
    end;


    function First_Symbol (Kind: Symbol_Type) return Grammar_Symbol is
    begin
	if Kind = Terminal then
	    return 0;
	else
	    return Next_Free_Nonterminal + 1;
	end if;
    end;

    function Last_Symbol (Kind: Symbol_Type) return Grammar_Symbol is
    begin
	if Kind = Terminal then
	    return Next_Free_Terminal - 1;
	else
	    return -1;
	end if;
    end;

    function First_Terminal  return Grammar_Symbol is
    begin
	return 0;
    end;

    function Last_Terminal   return Grammar_Symbol is
    begin
	return Next_Free_Terminal - 1;
    end;

    function Start_Symbol return Grammar_Symbol is
    begin
	return Start_Symbol_Pos;
    end;

    function End_Symbol   return Grammar_Symbol is
    begin
	return End_Symbol_Pos;
    end;

    function Is_Terminal    (ID: Grammar_Symbol) return Boolean is
    begin
	return ID >= 0;
    end;

    function Is_Nonterminal (ID: Grammar_Symbol) return Boolean is
    begin
	return ID < 0;
    end;

begin
    End_Symbol_Pos   := Insert_Terminal("END_OF_INPUT");
    Start_Symbol_Pos := Insert_Identifier("$accept");
    -- declare a dummy symbol to insert the "error" token.
    declare
	Dummy_Sym : Grammar_Symbol;
    begin
	Dummy_Sym := Insert_Terminal("ERROR");
    end;
end Symbol_Table;
