/*
		Ronny Wichers Schreur
		University of Nijmegen
*/


#pragma segment scanner

# include	<stdio.h>
# include	<string.h>
# include	<ctype.h>
# include	<limits.h>

# undef H

# include	"compiledefines.h"
# include	"types.t"

#if defined (applec) || (defined (__MWERKS__) && !defined (_WINDOWS_)) || defined (__MRC__)
# define __ppc__
#endif

# include	"system.h"
# include	"syntaxtr.t"
# include	"comsupport.h"
# include	"scanner.h"
# include	"sizes.h"

# if (defined (__MWERKS__) || defined (__MRC__) || (defined (GNU_C) && defined (_MAC_))) && !defined _WINDOWS_ /* && !defined (MAKE_MPW_TOOL) */
# define CACHE_DCL_FILES
# define CACHE_INLINE_FILES
# else
# undef CACHE_DCL_FILES
# undef CACHE_INLINE_FILES
# endif

char **ReservedWords;

static IdentP
NewIdent (TableKind tableKind, char *name)
{
	IdentP	ident;

	ident	= CompAllocType (struct ident);
		
	ident->ident_table	= tableKind;
	ident->ident_name	= name;

	ident->ident_next		= NULL;
	ident->ident_environ	= NULL;
	ident->ident_symbol		= NULL;
	ident->ident_local_defs = NULL;
	ident->ident_mark 		= 0;

	return (ident);
} /* NewIdent */

#define CompAllocString(size) ((char*)CompAlloc(size))

static	char *
AllocString (char *string, short length)
{
	int		i;
	char	*s, *newString;
	
	s	= newString	= CompAllocString (length+1);

	for (i = 0; i < length; i++)
		*s++	= *string++;
	*s	= '\0';
	
	return (newString);
} /* AllocString */

# define	kIdentStringTableSizeBits	10
# define	kIdentStringTableSize		((1 << kIdentStringTableSizeBits) - 1)

static	IdentStringP *gIdentStringTable;

static IdentStringP
StringInTable (char *string, short length)
{
	int			i;
	unsigned	long		hash;
	IdentStringP identString, *identStringPtr;
	char		*s;

	hash	= 0;
	s		= string;
	for (i = 0; i < length; i++)
	{
        hash <<= 2;
        hash  += *s++;
	}

    /*  Compute (hash % kIdentStringTableSize) */
    while (hash >= (kIdentStringTableSize<<1))
        hash   = (hash & kIdentStringTableSize) + (hash >> kIdentStringTableSizeBits);
    if (hash >= kIdentStringTableSize)
        hash   -= kIdentStringTableSize;

	identStringPtr = &gIdentStringTable [hash];

	while ((identString = *identStringPtr) != NIL)
	{
		int		compare;

		compare	= strncmp (identString->string, string, length);

		if (compare == 0 && (compare = ((unsigned char *)identString->string) [length]) == 0)
			/* found it */
			break;
		else if (compare > 0)
			identStringPtr	= &identString->left;
		else /* if (compare < 0) */
			identStringPtr	= &identString->right;
	}

	if (identString == NIL)
	{
		identString	= CompAllocType (struct ident_string);

		identString->left	= NIL;
		identString->right	= NIL;
		identString->ident	= NIL;

		identString->string	= AllocString (string, length);

		*identStringPtr	= identString;
	}

	return (identString);
} /* StringInTable */

IdentP
PutIdentStringInTable (IdentStringP identString, TableKind tableKind)
{
	IdentP		ident;

	for (ident = identString->ident; ident != NIL; ident = ident->ident_next)
		if (ident->ident_table == tableKind)
			break;

	if (ident == NIL)
	{
		ident	= NewIdent (tableKind, identString->string);
		
		ident->ident_next	= identString->ident;

		identString->ident	= ident;
	}

	return (ident);
} /* PutIdentStringInTable */

IdentP
PutStringInHashTable (char *string, TableKind tableKind)
{
	IdentStringP	identString;

	identString	= StringInTable (string, strlen (string));

	return (PutIdentStringInTable (identString, tableKind));
} /* PutStringInHashTable */

STRUCT (keyWordInfo, KeyWordInfo)
{
	char	*name;
	Token	token;
};

static	void
PutKeyWordInTable (KeyWordInfoP keyWord)
{
	IdentStringP identString;
	IdentP ident;

	identString	= StringInTable (keyWord->name, strlen (keyWord->name));

	ident	= NewIdent (KeyWordTable, identString->string);
	
	ident->ident_next		= identString->ident;
	ident->ident_environ	= NIL;
	ident->ident_symbol		= (struct symbol *) keyWord->token;

	identString->ident		= ident;
} /* PutKeyWordInTable */

IdentP
RetrieveFromSymbolTable (char *string)
{
	char *s;
	unsigned long hash;
	IdentStringP identString;
	IdentP ident;

	hash	= 0;
	for (s = string; *s != '\0'; s++)
	{
        hash <<= 2;
        hash  += *s;
	}

    /*  Compute (hash % 1023) */
    while (hash >= 2046)
        hash   = (hash & 1023) + (hash >> 10);
    if (hash >= 1023)
        hash   -= 1023;

	identString	= gIdentStringTable [hash];

	while (identString != NIL)
	{
		int		compare;

		compare	= strcmp (identString->string, string);

		if (compare == 0)
			/* found it */
			break;
		else if (compare > 0)
			identString	= identString->left;
		else /* if (compare < 0) */
			identString	= identString->right;
	}
	
	if (identString != NIL)
	{
		for (ident = identString->ident; ident != NIL; ident = ident->ident_next)
			if (ident->ident_table == SymbolIdTable)
				break;
	}
	else
		ident	= NIL;

	return (ident);
} /* RetrieveFromSymbolTable */

/*
	+-----------------------------------------------------------------------+
	| ReadInlineCode scans all the imported SYSTEM modules and stores the	|
	| the encountered inline instructions in the symbol table.				|
	+-----------------------------------------------------------------------+
*/
	
char NextLine[LineLength];

/* has a command been read? */

static char *IsCommand (char *com, char *p)
{
	while (*com++ == *p++)
          if (*com == '\0')
               return (p);
    return ((char *) NIL);
}

/* scan a file for .inline-.end command pairs */

char    *InlineCodeBuffer;
unsigned InlineBufferIndex, InlineBufferStart;

#ifdef CACHE_INLINE_FILES

struct inline_cache_list {
	struct inline_cache_list *	icache_next;
	struct file_block *			icache_file_blocks;
#if defined (__MWERKS__) || defined (THINK_C) || defined (__MRC__)
	char						icache_file_name[];
#else
	char						icache_file_name[0];
#endif
};

#define BUFFER_SIZE 1024

struct file_block {
	int					file_block_size;
	struct file_block *	file_block_next;
	char				file_block_data[BUFFER_SIZE];
};

struct file_block **next_file_block_l;

static int reading_from_cache=0;

static struct inline_cache_list * inline_cache=NULL;

static File inline_file;

static int chars_left_in_buffer;
static int end_of_file;
static char *buffer_p;

static int open_inline_file_for_block_reading (char *file_name)
{
	struct inline_cache_list **icache_elem_p,*new_icache_elem;
	int file_name_length;

	chars_left_in_buffer=0;
	end_of_file=0;
	reading_from_cache=0;

	for (icache_elem_p=&inline_cache; *icache_elem_p;
		icache_elem_p=&(*icache_elem_p)->icache_next)
	{
		if (!strcmp ((*icache_elem_p)->icache_file_name,file_name)){
			reading_from_cache=1;

			next_file_block_l=&(*icache_elem_p)->icache_file_blocks;
			return 1;
		}
	}
		
	inline_file = FOpen (file_name, abcFile, "r");
	if (inline_file==NULL)
		return 0;
	
#if defined (THINK_C) || defined (POWER)
	setvbuf (inline_file,NULL,_IOFBF,8192);
#endif	

	file_name_length=strlen (file_name);

	new_icache_elem=(struct inline_cache_list*)Alloc (1,sizeof (struct inline_cache_list)+file_name_length+1);

	strcpy (new_icache_elem->icache_file_name,file_name);
	new_icache_elem->icache_next=NULL;
	new_icache_elem->icache_file_blocks=NULL;
	*icache_elem_p=new_icache_elem;

	next_file_block_l=&new_icache_elem->icache_file_blocks;
	
	return 1;
}

static int get_line_from_inline_file (char *line_buffer,int line_length)
{
	char *line_buffer_p;
	
	line_buffer_p=line_buffer;
	
	for (;;){		
		while (chars_left_in_buffer>0){
			char c;
			
			c=*buffer_p++;
			--chars_left_in_buffer;

			if (c=='\r')
				c='\n';
			
			if (line_length>1){
				--line_length;
				*line_buffer_p++=c;
			}
			
			if (c=='\n'){
				*line_buffer_p=0;
				return 1;
			}
		}
		
		if (!reading_from_cache){
			struct file_block *file_block;

			if (end_of_file){
				*line_buffer_p=0;
				return line_buffer!=line_buffer_p;
			}
			
			file_block=(struct file_block*)Alloc (1,sizeof (struct file_block));
			
			chars_left_in_buffer=FRead (file_block->file_block_data,1,BUFFER_SIZE,inline_file);
			buffer_p=file_block->file_block_data;
			
			file_block->file_block_size=chars_left_in_buffer;
			file_block->file_block_next=NULL;
			
			end_of_file = chars_left_in_buffer!=BUFFER_SIZE;
			
			*next_file_block_l=file_block;
			next_file_block_l=&file_block->file_block_next;
		} else {
			struct file_block *file_block;
			
			file_block=*next_file_block_l;
		
			if (file_block==NULL){
				*line_buffer_p=0;
				return line_buffer!=line_buffer_p;
			}
			
			chars_left_in_buffer=file_block->file_block_size;
			buffer_p=file_block->file_block_data;
			
			if (chars_left_in_buffer==0){
				*line_buffer_p=0;
				return line_buffer!=line_buffer_p;
			}
			
			next_file_block_l=&file_block->file_block_next;
		}
	};
}

extern void clear_inline_cache (void);
void clear_inline_cache (void)
{
	struct inline_cache_list *icache_elem,*next_icache_elem;

	icache_elem=inline_cache;
	inline_cache=NULL;

	while (icache_elem!=NULL){
		struct file_block *icache_file_blocks,*next_icache_file_block;

		next_icache_elem=icache_elem->icache_next;
		icache_file_blocks=icache_elem->icache_file_blocks;
		icache_elem->icache_file_blocks=NULL;
		Free (icache_elem);

		while (icache_file_blocks!=NULL){
			next_icache_file_block=icache_file_blocks->file_block_next;
			Free (icache_file_blocks);
			icache_file_blocks=next_icache_file_block;
		}

		icache_elem=next_icache_elem;
	}
}
#endif

void ScanInlineFile (char *fname)
{
	register char *tail, *instr, *importingModule, *importingExtension;
	IdentP instrid;
	int nrinstr;
#ifndef CACHE_INLINE_FILES
	File f;
#endif

	importingModule		= CurrentModule;
	importingExtension	= CurrentExt;
    
	CurrentModule = fname;
	CurrentExt    = GetFileExtension (abcFile);
          
#ifdef CACHE_INLINE_FILES
	if (!open_inline_file_for_block_reading (fname))
#else
	if (! (f = FOpen (fname, abcFile, "r")))
#endif
	{	CurrentModule = importingModule;
		CurrentExt    = importingExtension;

		return;
	}
#ifndef CACHE_INLINE_FILES	
#	if defined (THINK_C) || defined (POWER)
		setvbuf ((void*) f, NULL, _IOFBF, 8192);
#	endif
#endif
		
	CurrentLine		= 0;
	CurrentPhase	= NULL;
		
	for (;;){
#ifdef CACHE_INLINE_FILES
		if (!get_line_from_inline_file (NextLine,LineLength))
#else
		if (! FGetS (NextLine, LineLength, f))
#endif
			break;

		for (tail = NextLine; isspace (*tail); tail++)
			;

		/* if not at .inline reenter loop from top */
		if ((tail = IsCommand (".inline", tail)) == NIL)
			continue;

		/* get the function name */
		while (*tail == ' ' || *tail == '\t')
			tail++;

		/* terminate it with a '\0' */
		for (instr = tail; ! isspace (*tail); tail++)
			;
		if (instr == tail)
			continue;

		*tail = '\0';
		if (! (instrid = RetrieveFromSymbolTable (instr)))
			continue;
		if (instrid->ident_environ!=importingModule)
			continue;
		if ((instrid->ident_mark & INLINE_MASK) != 0)
		{
			StaticMessage (True, "%s", "multiple .inline directives", instr);
			continue;
		}
		instrid->ident_mark |= INLINE_MASK;
	
		/* Open the buffer for the next instructions */
		InlineBufferIndex = InlineBufferStart;

		for (nrinstr = 0; nrinstr <= MaxInlineInstr;){
#ifdef CACHE_INLINE_FILES
			if (!get_line_from_inline_file (NextLine,LineLength)){
#else
			if (! FGetS (NextLine, LineLength, f)){
#endif
				StaticMessage (False, "%s", "%s no .end found in this file", instrid->ident_name,fname);

				break;
			}
			for (tail = NextLine; *tail == ' ' || *tail == '\t'; tail++)
				;
			if (IsCommand (".end", tail))
            	break;
			
			if (*tail != '\n' && *tail != '\0'){
				instr = NextLine;
				/* Copy this instruction into the buffer */

				do
				{	if (InlineBufferIndex < InlineBuffSize-2)
						InlineCodeBuffer [InlineBufferIndex++] = *instr++;
					else
						DoFatalError ("too many inline instructions");
				} while (*instr != '\n' && *instr != '\0');

				/* close the instruction with a newline character */
				InlineCodeBuffer [InlineBufferIndex++] = '\n';
				nrinstr++;
			}
		}

		if (nrinstr > MaxInlineInstr){
			StaticMessage (False, "%s", "%s file contains too many instructions", instrid->ident_name,fname);
		}
		
		/* save the list of inline instructions */
/*		if (InlineBufferIndex != InlineBufferStart){ */
			instrid->ident_instructions = &InlineCodeBuffer [InlineBufferStart];
			InlineBufferStart     = InlineBufferIndex+1;

			/* close the list with the NULL character */
			InlineCodeBuffer [InlineBufferIndex] = '\0';
/*		} */
	}

#ifdef CACHE_INLINE_FILES
	if (!reading_from_cache)
		FClose (inline_file);
#else
	FClose (f);
#endif

	CurrentModule = importingModule;
	CurrentExt    = importingExtension;
}

void
ScanInitIdentStringTable (void)
{
	int		i;

	/*
		RWS +++ clean up symbols
	*/
	ReservedWords = (char **) CompAlloc ((unsigned long) NumberOfKeywords * SizeOf (char *));
	ReservedWords [(int) errorsym]			= "Erroneous";
	ReservedWords [(int) barsym]			= "|";
	ReservedWords [(int) strictsym]			= "!";
	ReservedWords [(int) opensym]			= "(";
	ReservedWords [(int) closesym]			= ")";
	ReservedWords [(int) opensquaresym]		= "[";
	ReservedWords [(int) closesquaresym]	= "]";
	ReservedWords [(int) colonsym]			= ":";
	ReservedWords [(int) typesym]			= "::";
	ReservedWords [(int) semicolonsym]		= ";";
	ReservedWords [(int) commasym]			= ",";
	ReservedWords [(int) dotsym]			= ".";
	ReservedWords [(int) openbracesym]		= "{";
	ReservedWords [(int) closebracesym]		= "}";
	ReservedWords [(int) arrowsym]			= "->";
	ReservedWords [(int) abstypesym]		= "AbsType";

	ReservedWords [(int) arraysym]			= "{ }";
	ReservedWords [(int) strictarraysym]	= "{ ! }";
	ReservedWords [(int) unboxedarraysym]	= "{ # }";

	ReservedWords [(int) atsym]				= "at";
	ReservedWords [(int) boolsym]			= "Bool";
	ReservedWords [(int) charsym]			= "Char";
	ReservedWords [(int) codesym]			= "code";
	ReservedWords [(int) defsym]			= "definition";
	ReservedWords [(int) falsesym]			= "False";
	ReservedWords [(int) filesym]			= "File";
	ReservedWords [(int) allsym]			= "All";
	ReservedWords [(int) fromsym]			= "from";
/* RWS ... hack */
	ReservedWords [(int) ifsym]				= "if ";
/* ... RWS */
	ReservedWords [(int) impsym]			= "implementation";
	ReservedWords [(int) importsym]			= "import";
	ReservedWords [(int) intsym]			= "Int";
	ReservedWords [(int) macrosym]			= "macro";
	ReservedWords [(int) modulesym]			= "module";
	ReservedWords [(int) procidsym]			= "ProcId";
	ReservedWords [(int) redidsym]			= "RedId";
	ReservedWords [(int) realsym]			= "Real";
	ReservedWords [(int) rulesym]			= "rule";
/* */
	ReservedWords [(int) stringsym]			= "_STRING";
/* */
	ReservedWords [(int) systemsym]			= "system";
	ReservedWords [(int) truesym]			= "True";
	ReservedWords [(int) typedefsym]		= "type";
	ReservedWords [(int) applysym]			= "=>";
	ReservedWords [(int) uniquesym]			= "*";
	ReservedWords [(int) worldsym]			= "World";

	gIdentStringTable	= (struct ident_string**)CompAlloc (kIdentStringTableSize * sizeof (struct ident_string));
	for (i = 0; i < kIdentStringTableSize; i++)
		gIdentStringTable [i]	= NIL;
}

static KeyWordInfoS gKeyWords [] =
{
	{ "export",			kTokenExport				},
	{ "import",			kTokenImport				},
	{ "from",			kTokenFrom					},
	{ "definition",		kTokenDefinition			},
	{ "implementation",	kTokenImplementation		},
	{ "system",			kTokenSystem				},
	{ "module",			kTokenModule				},
	{ "let",			kTokenLet					},
	{ "in",				kTokenIn					},
	{ "case",			kTokenCase					},
	{ "of",				kTokenOf					},
	{ "if",				kTokenIf					},
	{ "with",			kTokenWith					},
	{ "where",			kTokenWhere					},
	{ "code",			kTokenCode					},
	{ "True",			kTokenTrue					},
	{ "False",			kTokenFalse					},	
/*	{ "overload",		kTokenOverload				}, */
	{ "instance",		kTokenInstance				},
	{ "default",		kTokenDefault				},
	{ "class",			kTokenClass					},
	{ "infix",			kTokenInfix					},
	{ "infixl",			kTokenInfixL				},
	{ "infixr",			kTokenInfixR				},
	{ "\\",				'\\'						},
	{ "\\\\",			kTokenDoubleBackSlash		},
	{ "#",				'#'							},
	{ "#!",				kTokenHashExclamationMark	},
	{ "=",				'='							},
	{ "|",				'|'							},
	{ ".",				'.'							},
	{ "!",				'!'							},
	{ "&",				'&'							},
	{ "..",				kTokenDotDot				},
	{ "=:",				kTokenEqualColon			},
#ifndef H
	{ ":",				':'							},
#endif
	{ ":==",			kTokenColonDoubleEqual		},
	{ "=>",				kTokenDoubleRightArrow		},
	{ "<-",				kTokenLeftArrow				},
	{ "<-:",			kTokenLeftArrowColon		},
	{ "->",				kTokenRightArrow			}
#ifdef H
	,{ "data",			kTokenData					}
	,{ "type",			kTokenType					}
	,{ "@",				kTokenAtSign				}
	,{ "then",			kTokenThen					}
	,{ "else",			kTokenElse					}
	,{ "interface",		kTokenInterface				}
#endif
};

# define	ArraySize(array)	((unsigned) (sizeof (array) / sizeof (array[0])))

void
ScanInitialise (void)
{
	int i;
#ifndef CLEAN2
	gCharTypeTable	= (unsigned char*)CompAlloc (256 * sizeof (unsigned char)),
	InitialiseCharTypeTable (gCharTypeTable);

	gStateNormalTable	= (ScanState*)CompAlloc (256 * sizeof (ScanState)),
	InitialiseStateNormalTable (gStateNormalTable);

	gStateInstructionsTable	= (ScanState*)CompAlloc (256 * sizeof (ScanState)),
	InitialiseStateInstructionTable (gStateInstructionsTable);

	ScanSetMode (kScanModeNormal);

	gInputBuffer	= (unsigned char*)CompAlloc (kInputBufferSize);
#endif

	ScanInitIdentStringTable();

	for (i = 0; i < ArraySize (gKeyWords); i++)
		PutKeyWordInTable (&gKeyWords [i]);
} /* ScanInitialise */

void
InitScanner (void)
{
	InlineCodeBuffer  = (char*)CompAlloc (InlineBuffSize);
	InlineBufferStart = 0;
} /* InitScanner */
