implementation module EdParse;

/* Clean syntax dependend parsing */

import StdClass,StdString,StdInt,StdChar,StdBool,StdMisc,StdFile,StdArray;
import EdTypes,EdProgramState,EdPath,EdText,EdDialogs,EdTextWindow,EdLists;
import UtilNewlinesFile;

::	SymbolId			:== Int;

::	* Input			= {	offside	:: !Bool,
						curpos	:: !Int,
						curlen	:: !Int,
						line	:: !String,
						file	:: !*File,
						linenr	:: !Int,
						charnr	:: !Int };
	// apply layout rule?, current position in current line,
	// length of current line, current line, input file, current line number,
	// current char number.

::	Symbol			= {	repr	:: !SymbolId,
						string	:: !String };
	// symbol id, symbol string
	
	EofSymID			:== 0;
	ErrorSymID			:== 1;

	BarSymID			:== 2;
	CloseSymID			:== 3;
	CloseBraceSymID		:== 4;
	CommaSymID			:== 5;
	IsSymID				:== 6;
	OpenBraceSymID		:== 7;
	OpenSymID			:== 8;
	SemiColonSymID		:== 9;
	SynonymSymID		:== 10;
	TypeSpecSymID		:== 11;
	UniqueAttrSymID		:== 12;
	IdentLowerSymID		:== 13;
	IdentUpperSymID		:== 14;
	IdentFunnySymID		:== 15;
	FromSymID			:== 16;
	ImportSymID			:== 17;
	OverloadSymID		:== 18;
	InstanceSymID		:== 19;
	ClassSymID			:== 20;

	EofSym				:== {repr = EofSymID ,string = ""};
	ErrorSym			:== {repr = ErrorSymID ,string = ""};

	BarSym				:== {repr = BarSymID, string = ""};
	CloseSym			:== {repr = CloseSymID, string = ""};
	CloseBraceSym		:== {repr = CloseBraceSymID, string = ""};
	CommaSym			:== {repr = CommaSymID, string = ""};
	IsSym				:== {repr = IsSymID, string = ""};
	OpenBraceSym		:== {repr = OpenBraceSymID, string = ""};
	OpenSym				:== {repr = OpenSymID, string = ""};
	SynonymSym			:== {repr = SynonymSymID, string = ""};
	SemiColonSym		:== {repr = SemiColonSymID, string = ""};
	TypeSpecSym			:== {repr = TypeSpecSymID, string = ""};
	UniqueAttrSym		:== {repr = UniqueAttrSymID, string = "*"};
	FromSym				:== {repr = FromSymID, string = ""};
	ImportSym			:== {repr = ImportSymID, string = ""};
	OverloadSym			:== {repr = OverloadSymID, string = ""};
	InstanceSym			:== {repr = InstanceSymID, string = ""};
	ClassSym			:== {repr = ClassSymID, string = ""};
	
	KeyWords			:== [	("from", 4, FromSym),
								("import", 6, ImportSym),
								("overload", 8, OverloadSym),
								("instance", 8, InstanceSym),
								("class", 5, ClassSym) ];
								
	IdentSymId id		:== id == IdentLowerSymID || id == IdentUpperSymID || id == IdentFunnySymID || id == UniqueAttrSymID;
	IsTypeSymId id		:== id == IdentUpperSymID || id == IdentFunnySymID;
	ConstrSymId id		:== id == IdentUpperSymID || id == IdentFunnySymID;

	DebugMask			:== 0;
	DontReuseUniqueNodesMask:== 1;
	ParallelMask		:== 2;
	NoMemoryProfileMask	:== 3;
	StrictnessMask		:== 4;
	NoTimeProfileMask	:== 5;
	VerboseMask			:== 6;
	WarningsMask		:== 7;
	SystemMask			:== 8;
		
	NrOfOptions			:== 9;

:: IdentifierPositionList = PosNil | Pos !Int !Int IdentifierPositionList;

//	Find the identifiers in the .dcl and the .icl files;

FindIdentifiersInFile :: !Bool !(List Modulename) !String !Pathname !ProgState !Files -> ((!ProgState,!List Modulename,!IdentifierPositionList), !Files);
FindIdentifiersInFile imp imports cleanid path prog files
	| ok
		= ((prog,imports1,identifier_position_list), CloseTextFile (EndInput input2) files1);
		= ((prog,imports,PosNil), files1);
	{}{
		(files1,ok,file) = OpenTextFile path files;
		(input1,sym,_,_) = ScanInput (StartInput file);
		(input2,imports1,identifier_position_list) = FindIdentifiersInInput imp cleanid imports sym input1;
	};

FindIdentifiersInInput :: !Bool !String !(List String) !Symbol !Input -> (!Input, !List String, !IdentifierPositionList);
FindIdentifiersInInput imp cleanid imports sym=:{repr} input
	| repr==FromSymID && imp
		= FindIdentifiersInInput imp cleanid importsa syma inputa; {
			(inputa,syma,importsa)	= ScanFrom imports input;
		}
	| repr==ImportSymID && imp
		= FindIdentifiersInInput imp cleanid importsb symb inputb; {
			(inputb,symb,importsb)	= ScanImport imports input;
		}
	| repr==EofSymID
		= (input,imports,PosNil);
	| IdentSymId next_symbol.repr && next_symbol.string==cleanid
		=	(input3,imports2,Pos linenr charnr positions0);
		{
			(input3,imports2,positions0)=FindIdentifiersInInput imp cleanid imports next_symbol input2;
		}		
		= FindIdentifiersInInput imp cleanid imports next_symbol input2;
	{}{
		(input2,next_symbol,linenr,charnr) = ScanInput input;
	}

//	Find the definitions in the .dcl and the .icl files;

FindDefinitionInFile :: !Bool !(List Modulename) !String !Pathname !ProgState !Files -> ((!ProgState, !List Modulename,!IdentifierPositionList), !Files);
FindDefinitionInFile imp imports cleanid path prog files
	| ok	= ((prog, imports1,positions), CloseTextFile (EndInput input`) files1);
			= ((prog, imports,PosNil), files1);
	where {
		(files1, ok, file)	= OpenTextFile path files;
		(input`,imports1,positions) = FindDefinitionInInput imp cleanid imports (StartInput file);
	};
	
FindDefinitionInInput :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
FindDefinitionInInput imp cleanid imports input
	= SkipToDefinition imp cleanid imports sym input`;
	{
		(input`,sym,_,_)		= ScanInput input;
	};

Definition1 :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
Definition1 imp cleanid imports input
	| match_from
		= SkipToDefinition imp cleanid importsa syma inputa;
		{
			(inputa,syma,importsa)	= ScanFrom imports input`;
		}
	| match_import
		= SkipToDefinition imp cleanid importsb symb inputb;
		{
			(inputb,symb,importsb)	= ScanImport imports input`;
		}
	| match_typespec	= TypeDef imp cleanid imports input`;
	| match_overload	= RuleOrInfixRuleDef imp cleanid imports input`;
	| match_instance	= ClassOrInstanceDef imp cleanid imports input`;
	| match_class		= ClassOrInstanceDef imp cleanid imports input`;
	| match_ident		= RuleOrMacroDef lnr cnr imp cleanid imports input`;
	| match_open		= InfixRuleDef imp cleanid imports input`;
	 					= SkipToDefinition imp cleanid imports sym input`;
	where {
	(input`,sym,lnr,cnr)	= ScanInput input;
	match_typespec			= sym.repr == TypeSpecSymID;
	match_from				= sym.repr == FromSymID && imp;
	match_import			= sym.repr == ImportSymID && imp;
	match_overload			= sym.repr == OverloadSymID;
	match_instance			= sym.repr == InstanceSymID;
	match_class				= sym.repr == ClassSymID;
	match_ident				= IdentSymId sym.repr && sym.string == cleanid;
	match_open				= sym.repr == OpenSymID;
	};
	
RuleOrInfixRuleDef :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
RuleOrInfixRuleDef imp cleanid imports input
	| match_ident		= RuleOrMacroDef lnr cnr imp cleanid imports input`;
	| match_open		= InfixRuleDef imp cleanid imports input`;
	 					= SkipToDefinition imp cleanid imports sym input`;
	where {
	(input`,sym,lnr,cnr)	= ScanInput input;
	match_ident				= IdentSymId sym.repr && sym.string == cleanid;
	match_open				= sym.repr == OpenSymID;
	};
	
ClassOrInstanceDef :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
ClassOrInstanceDef imp cleanid imports input
	| match_ident	= FoundDefinition lnr cnr imp imports input`;
					= SkipToDefinition imp cleanid imports sym input`;
	where  {
	(input`,sym,lnr,cnr)	= ScanInput input;
	match_ident				= IdentSymId sym.repr && sym.string == cleanid;
	};
	
RuleOrMacroDef :: !Int !Int !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
RuleOrMacroDef linenr charnr imp cleanid imports input
	| match_typespec	= FoundDefinition linenr charnr imp imports input`;
	| match_synonym		= FoundDefinition linenr charnr imp imports input`;
						= SkipToDefinition imp cleanid imports sym input`;
	where {
	(input`,sym,_,_)		= ScanInput input;
	match_typespec			= sym.repr == TypeSpecSymID;
	match_synonym			= sym.repr == SynonymSymID;
	};
	
InfixRuleDef :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
InfixRuleDef imp cleanid imports input
	| match_ident		= InfixRuleDef1 lnr cnr imp cleanid imports input`;
						= SkipToDefinition imp cleanid imports sym input`;
	where {
	(input`,sym,lnr,cnr)	= ScanInput input;
	match_ident				= IdentSymId sym.repr && sym.string == cleanid;
	};
	
InfixRuleDef1 :: !Int !Int !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
InfixRuleDef1 linenr charnr imp cleanid imports input
	| match_typespec	= FoundDefinition linenr charnr imp imports input`;
						= SkipToDefinition imp cleanid imports sym input`;
	where {
	(input`,sym,_,_)		= ScanInput input;
	match_typespec			= sym.repr == TypeSpecSymID;
	};

TypeDef :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
TypeDef imp cleanid imports input
	| match_typevar1
		= FoundDefinition lnr cnr imp imports input`;
	| match_typevar2
		= Algebraic imp cleanid imports input`;
	| sym.repr==SemiColonSymID || sym.repr==OpenBraceSymID
		= SkipToDefinition imp cleanid imports sym input`;
	| sym.repr<>EofSymID
		= TypeDef imp cleanid imports input`;
		= (input`,imports,PosNil);
	where {
		(input`,sym,lnr,cnr)	= ScanInput input;
		match_typevar2			= IsTypeSymId sym.repr;
		match_typevar1			= match_typevar2 && sym.string == cleanid;
	};
	
Algebraic :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
Algebraic imp cleanid imports input
	| sym.repr==IsSymID
		= Constructors imp cleanid imports input`;
	| sym.repr==SemiColonSymID || sym.repr==OpenBraceSymID
		= SkipToDefinition imp cleanid imports sym input`;
	| sym.repr<>EofSymID
		= Algebraic imp cleanid imports input`;
		= (input`,imports,PosNil);
	where {
		(input`,sym,_,_)	= ScanInput input;
	};

Constructors :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
Constructors imp cleanid imports input
	| match_ident1		= FoundDefinition lnr cnr imp imports input`;
	| match_ident2		= NextConstructors imp cleanid imports input`;
	| match_open		= InfixConstructors imp cleanid imports input`;
						= SkipToDefinition imp cleanid imports sym input`;
	where {
	(input`,sym,lnr,cnr)	= ScanInput input;
	match_ident2			= ConstrSymId sym.repr;
	match_ident1			= match_ident2 && sym.string == cleanid;
	match_open				= sym.repr == OpenSymID;
	};

NextConstructors :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
NextConstructors imp cleanid imports input
	| sym.repr==BarSymID
		= Constructors imp cleanid imports input1;
	| sym.repr<>SemiColonSymID && sym.repr<>OpenBraceSymID && sym.repr<>EofSymID
		= NextConstructors imp cleanid imports input1;
		= SkipToDefinition imp cleanid imports sym input1;
	where {
		(input1,sym,_,_)	= ScanInput input;
	};
	
InfixConstructors :: !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
InfixConstructors imp cleanid imports input
	| match_ident1	= FoundDefinition lnr cnr imp imports input`;
	| match_ident2	= NextConstructors imp cleanid imports input`;
					= SkipToDefinition imp cleanid imports sym input`;
	where {
	(input`,sym,lnr,cnr)	= ScanInput input;
	match_ident1			= match_ident2 && sym.string == cleanid;
	match_ident2			= ConstrSymId sym.repr;
	};

Braces :: !Int !Bool !String !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
Braces nesting imp cleanid imports input
	| match_openbrace	= Braces (inc nesting) imp cleanid imports input`;
	| match_closebrace1	= Definition1 imp cleanid imports input`;
	| match_closebrace2	= Braces (dec nesting) imp cleanid imports input`;
	| match_eof			= (input`,imports,PosNil);
						= Braces nesting imp cleanid imports input`;
	where {
	(input`,sym,_,_)	= ScanInput input;
	match_openbrace		= sym.repr == OpenBraceSymID;
	match_closebrace1	= match_closebrace2 && nesting == 1;
	match_closebrace2	= sym.repr == CloseBraceSymID;
	match_eof			= sym.repr == EofSymID;
	};
	
ScanImports	:: !(List String) !Input -> (!Input, !List String);
ScanImports imports input
	| match_from	= SkipToScanImports importsa syma inputa;
	| match_import	= SkipToScanImports importsb symb inputb;
					= SkipToScanImports imports sym input`;
	where {
	(input`,sym,_,_)		= ScanInput input;
	(inputa,syma,importsa)	= ScanFrom imports input`;
	(inputb,symb,importsb)	= ScanImport imports input`;
	match_from				= sym.repr == FromSymID;
	match_import			= sym.repr == ImportSymID;
	};
	
ScanFrom	:: !(List String) !Input -> (!Input, !Symbol, !List String);
ScanFrom imports input
	| IdentSymId sym.repr
		= ScanFromIdent (sym.string :! imports) input`;
		= (input`,sym, imports);
	{}{
		(input`,sym,_,_)	= ScanInput input;
	};

ScanFromIdent	:: !(List String) !Input -> (!Input, !Symbol, !List String);
ScanFromIdent imports input
	| sym.repr == ImportSymID
		= ScanFromIdentImport imports input`;
		= (input`, sym, imports);
	{}{
		(input`,sym,_,_)	= ScanInput input;
	};
	
ScanFromIdentImport	:: !(List String) !Input -> (!Input, !Symbol, !List String);
ScanFromIdentImport imports input
	= (input`, sym, imports);
	where {
	(input`,sym,_,_)	= ScanInput input;
	};

ScanImport	:: !(List String) !Input -> (!Input, !Symbol, !List String);
ScanImport imports input
	| match_ident	= ScanImportIdent imports` input`;
					= (input`,sym, imports);
	where {
	(input`,sym,_,_)	= ScanInput input;
	match_ident			= IdentSymId sym.repr;
	imports`			= sym.string :! imports;
	};

ScanImportIdent	:: !(List String) !Input -> (!Input, !Symbol, !List String);
ScanImportIdent imports input
	| match_comma	= ScanImportIdentComma imports input`;
					= (input`,sym, imports);
	where {
	(input`,sym,_,_)	= ScanInput input;
	match_comma			= sym.repr == CommaSymID;
	};

ScanImportIdentComma	:: !(List String) !Input -> (!Input, !Symbol, !List String);
ScanImportIdentComma imports input
	| match_ident	= ScanImportIdent imports` input`;
					= (input`,sym, imports);
	where {
	(input`,sym,_,_)	= ScanInput input;
	match_ident			= IdentSymId sym.repr;
	imports`			= sym.string :! imports;
	};
	
SkipToScanImports :: !(List String) !Symbol !Input -> (!Input, !List String);
SkipToScanImports imports sym input
	| sym.repr==EofSymID
		= (input, imports);
		= ScanImports imports input;
	
FoundDefinition :: !Int Int !Bool !(List String) !Input -> (!Input, !List String,!IdentifierPositionList);
FoundDefinition linenr charnr imp imports input
	| imp	= (input`, imports`, Pos linenr charnr PosNil);
			= (input, imports, Pos linenr charnr PosNil);
	where {
		(input`,imports`)	= ScanImports imports input;
	};

SkipToDefinition :: !Bool !String !(List String) !Symbol !Input -> (!Input, !List String,!IdentifierPositionList);
SkipToDefinition imp cleanid imports sym=:{repr} input
	| match_semicolon	= Definition1 imp cleanid imports input;
	| match_openbrace	= Braces 1 imp cleanid imports input;
	| match_eof			= (input,imports,PosNil);
						= SkipToDefinition imp cleanid imports sym` input`;
						{
							(input`,sym`,_,_)	= ScanInput input;
						}
	where {
		match_semicolon		= repr == SemiColonSymID;
		match_openbrace		= repr == OpenBraceSymID;
		match_eof			= repr == EofSymID;
	};

/* Auxilary functions which attempt to match specific symbols at the current position of the input */

FindSym :: !Int !Int !String -> (!Bool,!Int,!Symbol);
FindSym curpos curlen line
	| curpos >= curlen		= (False, curpos, ErrorSym);
	| curchar == ':'		= FindColon1 curpos` curlen line;
	| curchar == '='		= FindIs1 curpos` curlen line;
	| curchar == '*'		= FindStar1 curpos` curlen line;
	| curchar == ','		= (True, curpos`, CommaSym);
	| curchar == '{'		= (True, curpos`, OpenBraceSym);
	| curchar == '}'		= (True, curpos`, CloseBraceSym);
	| curchar == '('		= (True, curpos`, OpenSym);
	| curchar == ')'		= (True, curpos`, CloseSym);
	| curchar == '|'		= FindBar1 curpos` curlen line;
	| curchar == ';'		= (True, curpos`, SemiColonSym);
	| curchar == '_'		= FindId1 IdentLowerSymID curpos curpos` curlen line; // for the moment
	| LowerCase curchar		= FindId1 IdentLowerSymID curpos curpos` curlen line;
	| UpperCase curchar 	= FindId1 IdentUpperSymID curpos curpos` curlen line;
	| SpecialChar curchar	= FindId2 IdentFunnySymID curpos curpos` curlen line;
							= (False, curpos`, ErrorSym);
	where {
		curpos`	= inc curpos;
		curchar	= line.[curpos];
	};
						
FindColon1 :: !Int !Int !String -> (!Bool, !Int, !Symbol);
FindColon1 curpos curlen line
	| curpos >= curlen	= FindId2 IdentFunnySymID (dec curpos) curpos curlen line;
	| curchar == '='	= FindColon2 curpos` curlen line;
	| curchar == ':'	= (True, curpos`, TypeSpecSym);
						= FindId2 IdentFunnySymID (dec curpos) curpos curlen line;
	where {
		curpos`	= inc curpos;
		curchar	= line.[curpos];
	};

FindColon2 :: !Int !Int !String -> (!Bool, !Int, !Symbol);
FindColon2 curpos curlen line
	| curpos >= curlen	= FindId2 IdentFunnySymID (dec curpos) curpos curlen line;
	| curchar == '='	= (True, curpos`, SynonymSym);
						= FindId2 IdentFunnySymID (curpos-2) curpos curlen line;
	where {
		curpos`	= inc curpos;
		curchar	= line.[curpos];
	};
	
FindIs1 :: !Int !Int !String -> (!Bool, !Int, !Symbol);
FindIs1 curpos curlen line
	| curpos >= curlen		= (True, curpos, IsSym);
	| SpecialChar curchar	= FindId2 IdentFunnySymID (dec curpos) (inc curpos) curlen line;
						 	= (True, curpos, IsSym);
	where {
		curchar	= line.[curpos];
	};
	
FindBar1 :: !Int !Int !String -> (!Bool, !Int, !Symbol);
FindBar1 curpos curlen line
	| curpos >= curlen		= (True, curpos, BarSym);
	| SpecialChar curchar	= FindId2 IdentFunnySymID (dec curpos) (inc curpos) curlen line;
						 	= (True, curpos, BarSym);
	where {
		curchar	= line.[curpos];
	};
	
FindStar1 :: !Int !Int !String -> (!Bool, !Int, !Symbol);
FindStar1 curpos curlen line
	| curpos >= curlen		= (True, curpos, UniqueAttrSym);
	| SpecialChar curchar	= FindId2 IdentFunnySymID (dec curpos) (inc curpos) curlen line;
						 	= (True, curpos, UniqueAttrSym);
	where {
		curchar	= line.[curpos];
	};

FindId1 :: !SymbolId !Int !Int !Int !String -> (!Bool, !Int, !Symbol);
FindId1 symid start curpos curlen str
	| curpos >= curlen		= FindReserved symid start curpos curlen str;
	| LowerCase curchar		= FindId1 symid start curpos` curlen str;
	| UpperCase curchar		= FindId1 symid start curpos` curlen str;
	| Digit curchar			= FindId1 symid start curpos` curlen str;
	| Special curchar		= FindId1 symid start curpos` curlen str;
							= FindReserved symid start curpos curlen str;
	where {
		curchar					= str.[curpos];
		curpos`					= inc curpos;
	};

/*
	FindReserved :: !SymbolId !Int !Int !Int !String -> (!Bool, !Int, !Symbol);
	FindReserved symid start curpos curlen str
		| reserved
			= (True, curpos, sym);
			= (True, curpos, {repr=symid, string=str % (start,dec curpos)});
		where {
			(reserved,sym)	= FindKeyWord start curpos str KeyWords;
		};

	FindKeyWord :: !Int !Int !String ![(!String,!Int,!Symbol)] -> (!Bool, !Symbol);
	FindKeyWord pos stop str [(keyw,keywlen,sym) : rest]
		| issubstr
			= (True, sym)
			= FindKeyWord pos stop str rest;
		where {
			issubstr	= IsSubStr 0 keywlen keyw pos stop str;
		};
	FindKeyWord pos stop str []
		= (False,ErrorSym);
*/

FindReserved :: !SymbolId !Int !Int !Int !String -> (!Bool, !Int, !Symbol);
FindReserved symid start curpos curlen str
	| start>=curpos
		= (True,curpos,{repr=symid, string=str % (start,dec curpos)});
	| first_char=='f' && start+4==curpos && str.[start+1]=='r' && str.[start+2]=='o' && str.[start+3]=='m'
		= (True,curpos,FromSym);
	| first_char=='i' && start+6==curpos && str.[start+1]=='m' && str.[start+2]=='p' && str.[start+3]=='o'
			&& str.[start+4]=='r' && str.[start+5]=='t'
		= (True,curpos,ImportSym);
	| first_char=='o' && start+8==curpos && str.[start+1]=='v' && str.[start+2]=='e' && str.[start+3]=='r'
			&& str.[start+4]=='l' && str.[start+5]=='o' && str.[start+6]=='a' && str.[start+7]=='d'
		= (True,curpos,OverloadSym);
	| first_char=='i' && start+8==curpos && str.[start+1]=='n' && str.[start+2]=='s' && str.[start+3]=='t'
			&& str.[start+4]=='a' && str.[start+5]=='n' && str.[start+6]=='c' && str.[start+7]=='e'
		= (True,curpos,InstanceSym);
	| first_char=='c' && start+5==curpos && str.[start+1]=='l' && str.[start+2]=='a' && str.[start+3]=='s'
			&& str.[start+4]=='s'
		= (True,curpos,ClassSym);
		= (True,curpos,{repr=symid, string=str % (start,dec curpos)});
	where {
		first_char = str.[start];
	}
	
FindId2 :: !SymbolId !Int !Int !Int !String -> (!Bool, !Int, !Symbol);
FindId2 symid start curpos curlen str
	| curpos >= curlen		= (True, curpos, {repr=symid, string=str % (start,dec curpos)});
	| SpecialChar curchar	= FindId2 symid start curpos` curlen str;
							= (True, curpos, {repr=symid, string=str % (start,dec curpos)});
	where {
		curchar					= str.[curpos];
		curpos`					= inc curpos;
	};

/* Aux. functions for parsing text in a file */

OpenTextFile :: !Pathname !Files -> (!Files, !Bool, !*File);
OpenTextFile path files
	= (files`, ok, file);
	where {
	(ok,file,files`)	= fopen path FReadData files;
	};

CloseTextFile :: !*File !Files -> Files;
CloseTextFile file files
	= files`;
	where {
	(_,files`)	= fclose file files;
	};

StartInput :: !*File -> Input;
StartInput file
	= {	offside	= offside,
		curpos	= curpos,
		curlen	= curlen,
		line	= line,
		file	= file`,
		linenr	= linenr,
		charnr	= charnr };
	where {
	(_,curpos,curlen,line,file`,linenr,charnr)	= SkipLayOut1 False 0 0 0 "" file (-1) (-1);
	offside 									= ApplyLayOutRule curpos curlen line;
	};

ApplyLayOutRule :: !Int !Int !String -> Bool;
ApplyLayOutRule curpos curlen str
	| curpos >= curlen	= True;
	| curchar == ';'	= False;
						= ApplyLayOutRule (inc curpos) curlen str;
	where {
		curchar	= str.[curpos];
	};

EndInput :: !Input -> *File;
EndInput {Input | file} = file;

/*	Checks whether string at current input position matches a symbol , if so the corresponding symbol
	is returned, together with the new input skipped over the matched part, otherwise 'ErrorSym' or 'EofSym' is returned
*/

ScanInput :: !Input -> (!Input, !Symbol, !Int, !Int);
ScanInput input=:{offside,curpos,curlen,line,file,linenr,charnr}
	| eof	= (inputa, EofSym, linenr`, charnr`);
	| ok	= (inputb, sym, linenr`, charnr`);
			= ScanInput inputb;
	where {
	(eof,curposa,curlen`,line`,file`,linenr`,charnr`)
			= SkipLayOut1 offside 0 curpos curlen line file linenr charnr;
	(ok,curposb,sym)
			= FindSym curposa curlen` line`;
	inputa	= {	offside	= offside, 
				curpos	= curposa,
				curlen	= curlen`,
				line	= line`,
				file	= file`,
				linenr	= linenr`,
				charnr	= charnr` };
	inputb	= {	Input | inputa & curpos = curposb, charnr = charnr` + (curposb - curposa) };
	};

// Skips all layout (i.e. newlines, spaces and tabs), comments, string and character denotations

SkipLayOut1 ::	!Bool !Int !Int !Int !String !*File !Int !Int
				-> (!Bool, !Int, !Int, !String, !*File, !Int, !Int);
SkipLayOut1 offside nesting curpos curlen str text linenr charnr
	| curpos >= curlen	
		= SkipToNewLine offside nesting text linenr charnr;
	| WhiteSpace curchar
		= SkipLayOut1 offside nesting (inc curpos) curlen str text linenr (inc charnr);
	| curchar == '\"'
		= SkipLayOut1 offside nesting curposq curlenq str text linenr charnrq;
	| curchar=='/' && more2 && nextchar=='*'	
		= SkipLayOut1 offside (inc nesting) (curpos+2) curlen str text linenr (charnr+2);
	| curchar=='*' && more2 && nextchar=='/'
		= SkipLayOut1 offside nesting` (curpos+2) curlen str text linenr (charnr+2);
	| curchar=='/' && more2 && nextchar=='/'
		= SkipToNewLine offside nesting text linenr 0;
	| nesting > 0		
		= SkipLayOut1 offside nesting (inc curpos) curlen str text linenr (inc charnr);
		= (False, curpos, curlen, str, text, linenr, charnr);
	where {
		curchar		= str.[curpos];
		nextchar	= str.[inc curpos];
		more2		= inc curpos < curlen;
		nesting`	| nesting==0	= nesting;
									= dec nesting;
		(curposq,curlenq,charnrq)	= SkipQuote (inc curpos) curlen str (inc charnr);
	};

SkipToNewLine ::	!Bool !Int !*File !Int !Int
					-> (!Bool, !Int, !Int, !String, !*File, !Int, !Int);
SkipToNewLine offside nesting text linenr charnr
	| eof				= (True, 0, 0, "", text`, linenr, charnr);
	| newline			= SkipToNewLine offside nesting text` linenr` 0;
	| semicolon			= (False, 0, inc curlen`, ";"+++str, text`, linenr`, -1);
						= SkipLayOut1 offside nesting 0 curlen` str text` linenr` 0;
	where {
	(eof,text1)			= fend text;
	(str,text`)			= readLine text1;
	newline				= curlen` > 0 && str.[0]== '\n';
	semicolon			= nesting == 0 && offside && curlen` > 0 && NoLayOut (str.[0]);
	curlen`				= size str;
	linenr`				= inc linenr;
	
	NoLayOut :: !Char -> Bool;
	NoLayOut c = c <> ' ' && c <> '\t';
	};
	
SkipQuote :: !Int !Int !String !Int -> (!Int, !Int, !Int);
SkipQuote pos len str charnr
	| pos >= len		=  (pos,len,charnr);
	| curchar == '\"'	=  (pos`,len,charnr`);
	| curchar == '\\'	=  SkipQuote2 pos` len str charnr`;
						=  SkipQuote pos` len str charnr`;
	where {
		curchar		= str.[pos];
		pos`		= inc pos;
		charnr`		= inc charnr;
	};

SkipQuote2	:: !Int !Int !String !Int -> (!Int, !Int, !Int);
SkipQuote2 pos len str charnr
	| pos >= len	=  (pos,len,charnr);
					=  SkipQuote2 (inc pos) len str (inc charnr);
					
/* Aux. function for testing whether strings occur as a substring */

IsSubStr :: !Int !Int String !Int !Int !String -> Bool;
IsSubStr subpos substop substr pos stop str
	| subpos >= substop	= pos >= stop;
	| pos >= stop		= False;
	| subc == c			= IsSubStr (inc subpos) substop substr (inc pos) stop str;
						= False;
	where {
		subc	= substr.[subpos];
		c		= str.[pos];
	};

/* Aux. functions for testing whether characters are in certain classes */

SpecialChar	:: !Char -> Bool;
SpecialChar c =  pos < speciallen;
		where {
		pos			= FindChar c special speciallen 0;
		special		= "~@#$%^?!+-*<>\\/|&=:";
		speciallen	= size special;
		};
	
FindChar	:: !Char !String !Int !Int -> Int;
FindChar c line linelen pos
	| pos >= linelen		=  pos;
	| c ==  line.[pos]		=  pos;
							=  FindChar c line linelen (inc pos);
	
//	LowerCase	:: !Char -> Bool;
	LowerCase c	:== 'a' <= c  &&  c <= 'z' ;
		
//	UpperCase	:: !Char -> Bool;
	UpperCase c	:== 'A' <= c  &&  c <= 'Z' ;
		
//	Digit		:: !Char -> Bool;
	Digit c		:== '0' <= c  &&  c <= '9';

//	Special		:: !Char -> Bool;
	Special c	:== c == '`' || c == '_'; 


//	WhiteSpace :: !Char -> Bool;
	WhiteSpace c
	:==	c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
	
//	IdChar :: !Char -> Bool;
	IdChar c
	:== LowerCase c || UpperCase c || Digit c || Special c;

//	Find the module depencies in the .abc files
	
ParseABCDependencies :: !Pathname !Files -> (!(!Bool,!List Modulename, !List LinkObjFileName, !List LinkLibraryName ), !Files);
ParseABCDependencies path files
	| opened	=  ((True,dep_mods,linkObjFileNames, linkLibraryNames), files`);
				=  ((False,Nil,Nil,Nil), files1);
	where {
	(opened,file,files1)	= fopen path FReadData files;
	(file`, dep_mods, linkObjFileNames, linkLibraryNames)
							= Parse_lines file Nil Nil Nil;
	
	(_,files`)				= fclose file` files1;
	};
	
Parse_lines	:: !*File !(List Modulename) !(List LinkObjFileName) !(List LinkLibraryName)
			-> (!*File, !List Modulename, !List LinkObjFileName, !List LinkLibraryName );
Parse_lines file modnames linkObjFileNames linkLibraryNames
	| eof			= (file`, modnames, linkObjFileNames, linkLibraryNames);
	| end_of_info	= (file``, modnames`, linkObjFileNames`, linkLibraryNames`);
					= Parse_lines file`` modnames` linkObjFileNames` linkLibraryNames`; 
	where {
	(eof, file`)			= fend file;
	(modnames`, linkObjFileNames`, linkLibraryNames`, end_of_info)
							= Parse_line line modnames linkObjFileNames linkLibraryNames;
	(line, file``) 			= readLine file`;
	};

Parse_line	:: !String !(List Modulename) !(List LinkObjFileName) !(List LinkLibraryName)
			-> (!List Modulename, !List LinkObjFileName, !List LinkLibraryName, !Bool);
Parse_line str modnames linkObjFileNames linkLibraryNames
	# len_str		= size str;
	  start			= SkipSpaces 0 len_str str;
	  match_endinfo	= MatchS start 8 len_str ".endinfo" str;
	| match_endinfo
		=  (modnames , linkObjFileNames, linkLibraryNames, True);
	# match_depend	= MatchS start 7 len_str ".depend" str;
	  (last_q_depend, modname) = characters_between_apostrophes (start+7) len_str str;
	| match_depend && last_q_depend < len_str
		=  (modname :! modnames, linkObjFileNames, linkLibraryNames, False); 
	# match_importobj	= MatchS start 7 len_str ".impobj" str;
	  (last_q_importobj, linkObjFileName) = characters_between_apostrophes (start+7) len_str str;	
	| match_importobj && last_q_importobj < len_str
		=  (modnames , linkObjFileName :! linkObjFileNames, linkLibraryNames, False); 
	# match_importlib	= MatchS start 7 len_str ".implib" str;
	  (last_q_importlib, linkLibraryName) = characters_between_apostrophes (start+7) len_str str;	
	| match_importlib && last_q_importlib < len_str
		=  (modnames , linkObjFileNames, linkLibraryName :! linkLibraryNames, False); 
		=  (modnames , linkObjFileNames, linkLibraryNames, False);
	where {
		characters_between_apostrophes :: !Int !Int !String -> (!Int, !String);
		characters_between_apostrophes after_keyword_pos len_str str
			= (last_q, str % (inc first_q, dec last_q))
			where {
				start`			= SkipSpaces after_keyword_pos len_str str;
				first_q			= FindQuoteChar str len_str start`;
				last_q			= FindQuoteChar str len_str (inc first_q);
				};
	};
	
//
//	Extract the following info from the ABC file:
//		- Does it contain sequential code and stack layout info
//		- Is it a system file
//		- Compiler version it was generated by
//		- Compiler options it was generated with
//

GetABCCompiledInfo :: !Pathname  !Files -> (!Files, !Bool, !Bool, !Int, !ABCOptions);
GetABCCompiledInfo path files
	| opened	= (files`,sys, stack_seq,version,options);
				= (files1,False,False,-1,DefaultABCOptions);
	where {
	(opened, file, files1)	= fopen path FReadData files;
	(file`,sys, stack_seq,version,options)	= Read_Version_and_Options file;
	(_,files`)								= fclose file` files1;
	};
	
Read_Version_and_Options :: !*File -> (!*File,!Bool,!Bool,!Int,!ABCOptions);
Read_Version_and_Options file
	| eof			= (file`,False,False,-1,DefaultABCOptions);
	| end_of_info	= (file``,sys,stack_seq,version,abcOptions);
					= Read_Version_and_Options file``;
	where {
	(eof,file`)								= fend file;
	(str,file``)							= readLine file`;
	(end_of_info,sys,stack_seq,version,abcOptions)	= Find_Version_and_Options str;
	};

Find_Version_and_Options :: !String -> (!Bool,!Bool,!Bool,!Int,!ABCOptions);
Find_Version_and_Options str
	# len_str		= size str;
	  start			= SkipSpaces 0 len_str str;
	  match_endinfo	= MatchS start 8 len_str ".endinfo" str;
	| match_endinfo
		= (True,False,False,-1,DefaultABCOptions);
	# match_comp	= MatchS start 5 len_str ".comp" str;
	  start`		= SkipSpaces (start+5) len_str str;
	  match_version	= SkipDigits start` len_str str;
	  start``		= SkipSpaces match_version len_str str;
	  match_options	= SkipBits start`` len_str str;
	| match_comp && match_options - start`` == NrOfOptions
		# version		= SubStringToInt 0 start` (dec match_version) str;
		  abcOptions	= (StringToCompilerOptions start`` str);
		  stack_seq		= ProjectABCFile start`` str;
		  sys			= SystemABCFile start`` str;
		= (True,sys,stack_seq,version,abcOptions);
		= (False,False,False,-1,DefaultABCOptions);
	
StringToCompilerOptions :: !Int !String -> ABCOptions;
StringToCompilerOptions start opt
	= {	abcMemoryProfile 		= not (opt .[start+NoMemoryProfileMask] == '1'),
		abcTimeProfile			= not (opt .[start+NoTimeProfileMask] == '1'),
		abcStrictnessAnalysis	= opt .[start+StrictnessMask]	== '1',
		abcGiveWarnings			= opt .[start+WarningsMask]	== '1',
		abcBeVerbose			= opt .[start+VerboseMask]	== '1',
		abcGenerateComments		= opt .[start+DebugMask] == '1',
		abcReuseUniqueNodes		= not (opt.[start+DontReuseUniqueNodesMask] == '1') };

ProjectABCFile :: !Int !String -> Bool;
ProjectABCFile start opt =	opt .[start+ParallelMask] == '0';

SystemABCFile :: !Int !String -> Bool;
SystemABCFile start opt = opt .[start+SystemMask] == '1';
		
MatchS pos pattern_length string_length pattern string
	| pos+pattern_length>string_length
		= False;
		= MatchS2 0 pos pattern_length pattern string; 
	{
		MatchS2 :: !Int !Int !Int !String !String -> Bool;
		MatchS2 patpos strpos patlen pat str
			| patpos >= patlen
				= True;
			| pat.[patpos] == str.[strpos]
				= MatchS2 (inc patpos) (inc strpos) patlen pat str;
				= False;
	}
										
SubStringToInt :: !Int !Int !Int String -> Int;
SubStringToInt acc start stop str
	| start > stop	= acc;
	| isDigit curc	= SubStringToInt acc` (inc start) stop str;
					= acc;
	where {
	curc	= str.[start];
	acc`	= 10 * acc + toInt curc - toInt '0';
	};

SkipSpaces	:: !Int !Int !String -> Int;
SkipSpaces i len str | i >= len ||  str.[i]  <> ' '	=  i;
	                     								=  SkipSpaces (inc i) len str;
	                     										                     
SkipBits :: !Int !Int !String -> Int;
SkipBits i len str | i >= len || (c <> '0' && c <> '1')	= i;
	                    								= SkipBits (inc i) len str;
	where {
	c	= str.[i];
	};
	
SkipDigits :: !Int !Int !String -> Int;
SkipDigits i len str | i >= len || c < '0' || '9' < c	= i;
														= SkipDigits (inc i) len str;
	where {
	c	= str.[i];
	};

//
// Check whether a clipboard selection is a valid Clean module identifier
//
	                   
CleanModId	:: !String -> Bool;
CleanModId id = IsModId 0 (size id) id;
	
IsModId	:: !Int !Int !String -> Bool;
IsModId pos len id	| pos >= len			=  False;
					| LowerCase curchar || UpperCase curchar || curchar == '_'
											=  IsModId1 (inc pos) len id;
					| SpecialChar curchar	=  IsModId2 (inc pos) len id;

											=  False;
		where {
		curchar= id.[pos];
		};
		
IsModId1	:: !Int !Int !String -> Bool;
IsModId1 pos len id	| pos >= len			= True;
					| LowerCase curchar		= IsModId1 (inc pos) len id;
					| UpperCase curchar		= IsModId1 (inc pos) len id;
					| Digit curchar			= IsModId1 (inc pos) len id;
					| Special curchar		= IsModId1 (inc pos) len id;
											= False;
		where {
		curchar	= id.[pos];
		};
		
IsModId2	:: !Int !Int !String -> Bool;
IsModId2 pos len id	| pos >= len			= True;
					| SpecialChar curchar	= IsModId2 (inc pos) len id;
											= False;
		where {
		curchar	= id.[pos];
		};

//
// Checks whether string is a type specification
//
		
IsTypeSpec :: !String -> Bool;
IsTypeSpec str
	| comment < strlen	= True; 
						= cleanid < strlen && hastype < strlen;
	where {
	strlen		= size str;
	pos1		= SkipLayOutChars str strlen 0;
	comment		= HasSubStr 2 "//" strlen str pos1;
	(_,cleanid)	= FindCleanId str strlen pos1;
	hastype		= HasSubStr 2 "::" strlen str (inc cleanid);
	};
	
HasSubStr :: !Int !String !Int !String !Int -> Int;
HasSubStr substrlen substr strlen str pos
	| pos + dec substrlen >= strlen							= strlen;
	| IsSubStr 0 substrlen substr pos (pos+substrlen) str	= pos;
															= HasSubStr substrlen substr strlen str (inc pos);
								
SkipLayOutChars :: !String !Int !Int -> Int;
SkipLayOutChars str strlen pos
	| pos >= strlen							= pos;
	| curchar == ' ' || curchar == '\t'		= SkipLayOutChars str strlen (inc pos);
											= pos;
	where {
	curchar	= str.[pos];
	};

FindCleanId :: !String !Int !Int -> (!Int, !Int);
FindCleanId id idlen pos
	| pos >= idlen			= (idlen, idlen);
	| LowerCase curchar		= (pos, FindCleanId1 id idlen pos`);
	| UpperCase curchar		= (pos, FindCleanId1 id idlen pos`);
	| SpecialChar curchar	= (pos, FindCleanId1 id idlen pos`);
	| Special curchar		= (pos, FindCleanId1 id idlen pos`);
							= (idlen, idlen);
	where {
	curchar	= id.[pos];
	pos`	= inc pos;
	};
							
FindCleanId1 :: !String !Int !Int -> Int;
FindCleanId1 id idlen pos
	| pos >= idlen			= dec pos;
	| LowerCase curchar		= FindCleanId1 id idlen pos`;
	| UpperCase curchar		= FindCleanId1 id idlen pos`;
	| Digit curchar			= FindCleanId1 id idlen pos`;
	| SpecialChar curchar	= FindCleanId1 id idlen pos`;
	| Special curchar		= FindCleanId1 id idlen pos`;
							= dec pos;
	where {
	curchar	= id.[pos];
	pos`	= inc pos;
	};
	
// Checks whether string is an error message
	
Error :: !String !Int !Int -> Int;
Error str strlen pos
	| pos + 4 >= strlen
		= strlen;
	| is_error
		= pos;
		= Error str strlen (inc pos);
	where {
		is_error = (str.[pos]=='e' || str.[pos]=='E')
					&& str.[pos+1]=='r' && str.[pos+2]=='r' && str.[pos+3]=='o' && str.[pos+4]=='r';
	};

// Extracts the modulename out of a '<modulename> could not be imported' message

IsImportError :: !String -> (!Bool,!Modulename);
IsImportError str
	| error<strlen && id2 < strlen && notimport < strlen
		= (True,path);
		= (False,EmptyPathname);
	where {
		error		= Error str strlen 0;
		open		= FindOpenChar str strlen 0;
		close		= FindCloseChar str strlen (inc open);
		colon		= FindColonChar str strlen (inc close);
		layout		= SkipLayOutChars str strlen (inc colon);
		(id1,id2)	= FindCleanId str strlen layout;
		notimport	= HasSubStr (size pat) pat strlen str (inc id2);
		path		= str % (id1, id2);
		strlen		= size str;
		pat			= "could not be imported";
	};

//
// Extract module name and line number from error message.
//
		
ParseErrorMsg	:: !String -> (!Modulename, !Int);
ParseErrorMsg msg
	|	open < msglen	&& comma1 < msglen	&& comma2 < msglen	=  (path, linenr2);
	|	open < msglen	&& comma1 < msglen	&& close1 < msglen	=  (path, linenr1);
	|	open < msglen	&& close2 < msglen						=  (path, 0); 	
																=  (EmptyPathname, 0);
	where {
	path	= msg % (inc open, dec comma1);
	linenr1	= dec` (SubStringToInt 0 (inc comma1) (dec comma2) msg);
	linenr2	= dec` (SubStringToInt 0 (inc comma1) (dec close1) msg);
	msglen	= size msg;
	open	= FindOpenChar msg msglen 0;
	close1	= FindCloseChar msg msglen (inc comma1);
	close2	= FindCloseChar msg msglen (inc open);
	comma1	= FindCommaChar msg msglen (inc open);
	comma2	= FindCommaChar msg msglen (inc comma1);
	};
	
//	FindOpenChar	:: !String !Int !Int -> Int;
	FindOpenChar str len pos	:==  FindChar '[' str len pos;

//	FindCloseChar	:: !String !Int !Int -> Int;
	FindCloseChar str len pos	:==  FindChar ']' str len pos;

//	FindCommaChar	:: !String !Int !Int -> Int;
	FindCommaChar str len pos	:==  FindChar ',' str len pos;

//	FindQuoteChar	:: !String !Int !Int -> Int;
	FindQuoteChar str len pos	:== FindChar '\"' str len pos;
	
//	FindColonChar	:: !String !Int !Int -> Int;
	FindColonChar str len pos	:== FindChar ':' str len pos;

dec`	:: !Int -> Int;
dec` n | n == 0	= 0;
				= dec n;

// Make 'found id' msg in error message format

MakeFoundMsg :: !SearchKind !String !Int !String -> String;
MakeFoundMsg Definition mod linenr id
	= "Definition found in [" +++ MakeDefPathname (RemovePath mod) +++ "," +++ toString linenr +++ "]: " +++ id +++ "\n";
MakeFoundMsg Implementation mod linenr id
	= "Implementation found in [" +++ MakeImpPathname (RemovePath mod) +++ "," +++ toString linenr +++ "]: " +++ id +++ "\n";
MakeFoundMsg Identifier mod linenr id
	= "Identifier found in [" +++ RemovePath mod +++ "," +++ toString linenr +++ "]: " +++ id +++ "\n";
