#
# File: textstorage.k3
# Stores text constants.
#
# Copyright 2005, University of Nijmegen.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# CVS ID: "$Id: textstorage.k3,v 1.1 2005/10/03 14:58:49 marcs Exp $"
#
MODULE textstorage =
	NAME_TO_ID{}
	MAX_NAME_HASH
        ID_TO_NAME{}
	ID_COUNT.

DEFINES ID,
	FUNCTION get number of ids (INT>),
	FUNCTION convert id to name (>ID, TEXT>),
	TEST convert name to id (>TEXT, ID>),
	ACTION create new id (>TEXT, ID>),
        ACTION get id (>TEXT, ID>),
        ACTION log hash stats.

USES io, id.

ID = INT.

#
# ID creation and handling
#
ID_COUNT = INT.
ID_TO_NAME = TEXT.
MAX_NAME_HASH = INT.
NAME_TO_ID = NAMELIST.
NAMELIST :: TEXT ID NAMELIST;
            nil.

PRELUDE init textstorage:
  [ 0 -> ID_COUNT ],
    [ 2048 -> MAX_NAME_HASH ],
    init name to id list (MAX_NAME_HASH).

ACTION init name to id list (>INT):
  [ nil -> NAME_TO_ID{INT} ],
    ( [ INT -> 0 ];
      init name to id list (INT - 1)).

FUNCTION hash wrapper (>TEXT, >INT1, INT2>):
#  convert INT1 to TEXT1,
#    write ("hashing " + TEXT + " with limit " + TEXT1 + "\n"),
#    hash (TEXT, INT1, INT2),
#    convert INT2 to TEXT2,
#    write ("\tresult is " + TEXT2 + "\n").
  hash (TEXT, INT1, INT2).

FUNCTION assert valid (>ID):
  lesseq (0, ID), less (ID, ID_COUNT);
  bindec (ID, TEXT),
    abort ("assert valid id: " + TEXT + "failed").

FUNCTION convert id to name (>ID, TEXT>):
  assert valid (ID),
    [ ID_TO_NAME{ID} -> TEXT ].

TEST convert name to id (>TEXT, ID>):
  hash wrapper (TEXT, MAX_NAME_HASH, INT),
    [ NAME_TO_ID{INT} -> NAMELIST ],
    get from namelist (TEXT, NAMELIST, ID).

TEST get from namelist (>TEXT, >NAMELIST, ID>):
  [ NAMELIST -> TEXT1 ID1 NAMELIST1 ],
    ( [ TEXT = TEXT1 ], [ ID1 -> ID ];
      get from namelist (TEXT, NAMELIST1, ID)  
    );
  [NAMELIST -> nil], -;
  ?.

ACTION get id (>TEXT, ID>):
  convert name to id (TEXT, ID);
  create new id (TEXT, ID), initialize identification info (ID).

#
# create new id will create a new ID value for a given TEXT. It does *NOT*
# check for duplicate ID values. Use convert name to id for that. On the
# other hand, this is not a problem as you should use get id (above) which
# does the magic.
#
ACTION create new id (>TEXT, ID>):
  [ID_COUNT -> ID],
    [ID_COUNT + 1 -> ID_COUNT],
    [TEXT -> ID_TO_NAME{ID}],
    hash wrapper (TEXT, MAX_NAME_HASH, INT),
    [NAME_TO_ID{INT} -> NAMELIST],
    append TEXT with ID to NAMELIST,
    [NAMELIST -> NAME_TO_ID{INT}].

FUNCTION get number of ids (INT>):
  [ ID_COUNT -> INT ].

FUNCTION append >TEXT with >ID to >NAMELIST>:
  [ NAMELIST -> nil ],
    [ TEXT ID nil -> NAMELIST ];
  [ NAMELIST -> TEXT1 ID1 NAMELIST1 ],
    append TEXT with ID to NAMELIST1,
    [ TEXT1 ID1 NAMELIST1 -> NAMELIST ];
  ?.

FUNCTION length (>NAMELIST, INT>):
  [ NAMELIST -> nil], [0 -> INT ];
  [ NAMELIST -> TEXT ID NAMELIST ],
    length (NAMELIST, INT),
    [ INT + 1 -> INT ];
  ?.

#
# Hash table statistics:
#
ACTION log hash stats:
  calculate hash stats (INT1, INT2, INT3),
    log ("\nID hash table statistics:"),
    convert MAX_NAME_HASH to TEXT1,
    convert ID_COUNT to TEXT2,
    log ("There are " + TEXT2 + " IDs and the hash table is " + TEXT1 + " entries long"),
    convert INT1 to TEXT,
    log ("Nr of actually used entries in the hash table: " + TEXT),
    convert INT2 to TEXT1,
    convert INT3 to TEXT2,
    log ("Longest hash entry is #" + TEXT2 + " with " + TEXT1 + " entries.\n").

#
# Calculate the hash statistics:
# INT1 is the nr of used entries (= not-nil entries)
# INT2 is the number of elements in the longest entry
# INT3 is the index of the longest entry
#
FUNCTION calculate hash stats (INT1>, INT2>, INT3>):
  calculate hash stats (0, 0|INT1, 0|INT2, 0|INT3).

FUNCTION calculate hash stats (>INT1, >INT2>, >INT3>, >INT4>):
  [INT1 = MAX_NAME_HASH];
  [NAME_TO_ID{INT1} -> NAMELIST], length (NAMELIST, INT5),
    ( greater (INT5, INT3),              # we found a longer entry
        [INT5 -> INT3],
        [INT1 -> INT4],
        [INT2 + 1 -> INT2],              # longer entry always means >0 list el.
        calculate hash stats (INT1 + 1, INT2, INT3, INT4);
      greater (INT5, 0),                 # not a longer entry, but possibly a populated entry
        [INT2 + 1 -> INT2],
        calculate hash stats (INT1 + 1, INT2, INT3, INT4);	# no entries here, just go on
      calculate hash stats (INT1 + 1, INT2, INT3, INT4)
    ).
