implementation module EdTextFind;

import StdClass,StdInt,StdChar,StdString,StdBool,StdArray, StdList;

import EdText,EdConstants,EdTextConstants,EdTypes, EdLists;
     
IsOpenBracket c		:== c == '(' || c == '{' || c == '[';
IsCloseBracket c	:== c == ')' || c == '}' || c == ']';
Corresponds a b		:== a == '(' && b == ')' || a == '[' && b == ']' ||
						a == '{' && b == '}';
							
EmptyReplacedAll	:== {len=0,org="",repl="",ln_cns=Nil};

//
//	Text_Balance returns the Selection of the smallest piece of balanced text surrounding
//	the cursor or the current selection.
//

Text_Balance :: !Int !Int !Int !Int !Text -> (!Bool,!PartTSel);
Text_Balance bln bcn eln ecn text
	| not down || not up || not (Corresponds upb downb)
		= (False,	{l1=0,c1=0,l2=0,c2=0});
		= (True, 	{l1=lu,c1=cu,l2=eln + ld,c2=inc cd});
	where {
		(down,ld,cd,downb)= BalanceDownText eln ecn text;
		(up  ,lu,cu,upb  )= BalanceUpText   bln bcn text;
	};

BalanceDownText	:: !Int !Int !Text -> (!Bool,!Int,!Int,Char);
BalanceDownText ln cn (block:!blocks)
	| ln < LinesPerBlock	=  BalanceDownBlock ln cn block blocks;
							=  BalanceDownText (ln - LinesPerBlock) cn blocks;

BalanceDownBlock :: !Int !Int !Block !Text -> (!Bool,!Int,!Int,Char);
BalanceDownBlock 0 cn (line:!lines) blocks
	=  BalanceDown [] 0 cn line` lines blocks;
	where {
		line`= RemoveChars cn line;
	};
BalanceDownBlock ln cn (line:!lines) blocks
	=  BalanceDownBlock (dec ln) cn lines blocks;

BalanceDown	:: ![Char] !Int !Int !TLine !Block !Text -> (!Bool,!Int,!Int,Char);
BalanceDown ps ln cn (str:!strs) lines blocks
	| fnd	= (True,ln,cn + ci,brak);
			= BalanceDown ps` ln (cn + len) strs lines blocks;
	where {
	(fnd,ps`,ci,brak)	= BalanceDownString ps 0 len str;
	len					= size str;
	};
BalanceDown ps ln cn Nil (line:!lines) blocks
	=  BalanceDown ps (inc ln) 0 line lines blocks;
BalanceDown ps ln cn Nil Nil ((line:!lines):!blocks)
	=  BalanceDown ps (inc ln) 0 line lines blocks;
BalanceDown ps ln cn Nil Nil Nil
	=  (False,0,0,' ');

BalanceDownString :: ![Char] !Int !Int !String -> (!Bool,![Char],!Int,Char);
BalanceDownString ps ci len str
	| ci >= len			=  (False,ps,len,' ');
	| close && ps == []	=  (True,[],ci,char);
	| close	&& Corresponds (hd ps) char
				=  BalanceDownString (tl ps) (inc ci) len str;
	| close		=  (False,ps,len,' ');
	| IsOpenBracket char=  BalanceDownString [char:ps] (inc ci) len str;
						=  BalanceDownString ps       (inc ci) len str;
	where {
	close	= IsCloseBracket char;
	char	= str.[ci];
	};

BalanceUpText :: !Int !Int !Text -> (!Bool,!Int,!Int,Char);
BalanceUpText ln cn text
	| fnd	= (True,ln,dec (cn - ci),brak);
			= BalanceUp ps (dec ln) text;
	where {
	(fnd,ps,ci,brak)= BalanceUpLine [] 0 (TakeChars cn (Text_GetLine ln text) Nil);
	};

BalanceUp :: ![Char] !Int !Text -> (!Bool,!Int,!Int,Char);
BalanceUp ps ln text
	| ln < 0	= (False,0,0,' ');
	| fnd		= (True,ln,cn,brak);
				= BalanceUp ps` (dec ln) text;
	where {
	(fnd,ps`,ci,brak)	= BalanceUpLine ps 0 (Reverse line);
	line				= Text_GetLine ln text;
	cn					= Line_NrChars line  - ci;
	};

BalanceUpLine :: ![Char] !Int !TLine -> (!Bool,![Char],!Int,Char);
BalanceUpLine ps ci Nil =  (False,ps,ci,' ');
BalanceUpLine ps ci (str:!strs)
	| fnd	= (True,[],dec (ci + (len - ci`)),brak);
			= BalanceUpLine ps` (ci + len) strs;
	where {
	(fnd,ps`,ci`,brak)	= BalanceUpString ps (dec len) str;
	len					= size str;
	};

BalanceUpString	:: ![Char] !Int !String -> (!Bool,![Char],!Int,Char);
BalanceUpString ps ci str
	| ci < 0				= (False,ps,0,' ');
	| open && ps == []		= (True,[],ci,char);
	| open	&& Corresponds char (hd ps)
							= BalanceUpString (tl ps)	(dec ci) str;
	| open					= (False,ps,0,' ');
	| IsCloseBracket char	= BalanceUpString [char:ps]	(dec ci) str;
							= BalanceUpString ps		(dec ci) str;
	where {
	open	= IsOpenBracket char;
	char	= str.[ci];
	};

RemoveChars	:: !Int !TLine -> TLine;
RemoveChars 0 line						= line;
RemoveChars i (str:!strs)	| i >= len	= RemoveChars (i - len) strs;
										= str % (i, dec len) :! strs;
	where {
	len= size str;
	};

TakeChars :: !Int !TLine !TLine -> TLine;   // Returns the TLine reversed!!!
TakeChars 0 line res					=  res;
TakeChars i (str:!strs) res	| i >= len	= TakeChars (i - len) strs (str:!res);
										= str % (0, dec i) :! res;
	where {
	len= size str;
	};
	
//
//	Text_UndoReplaceAll undoes a Replace All
//	It returns the undo info (to undo the Undo Replace All), the text-selection of the lastly undone
//	string and the new text
//

Text_UndoReplaceAll :: !ReplacedAll !Text -> (!Bool,!ReplacedAll,!PartTSel,!Text);
Text_UndoReplaceAll {len,org,repl,ln_cns} text
	= (True,replacedall`,tsel`,text`);
	where {
	(l1`,c1`,_,new,text`)	= UndoReplaceAll 0 len 0 0 repl ln_cns Nil text;
	lengthrepl				= size repl;
	tsel`					= {l1=l1`,c1=c1`,l2=l1`,c2=c1`+lengthrepl};
	replacedall`			= {	len		= size repl,
									org		= repl,
									repl	= org,
									ln_cns	= Reverse new };
	};

UndoReplaceAll ::	!Int !Int !Int !Int !String !(List Replaced) !(List Replaced) !Text
					-> (!Int,!Int,!List Replaced,!List Replaced,!Text);
UndoReplaceAll linenr len l1 c1 repl Nil new text
	= (l1,c1,Nil,new,text);
UndoReplaceAll linenr len l1 c1 repl replace new Nil
	= (l1,c1,replace,new,Nil);
UndoReplaceAll linenr len l1 c1 repl replace=:({Replaced | lnr,cnr}:!_) new text=:(block:!blocks)
	= (l1`,c1`,replace`,new`,block` :! blocks`);
	where {
	(l1`,c1`,replace`,new`,blocks`)
			= UndoReplaceAll last len l11 c11 repl replace1 new1 blocks;
	(l11,c11,replace1,new1,block`)
			= UndoReplaceAllInBlock linenr last len l1 c1 repl replace new block;
	last	= linenr + LinesPerBlock;
	};
	
UndoReplaceAllInBlock ::	!Int !Int !Int !Int !Int !String !(List Replaced) !(List Replaced) !Block
							-> (!Int,!Int,!List Replaced,!List Replaced,!Block);
UndoReplaceAllInBlock first last len l1 c1 repl Nil new block
	= (l1,c1,Nil,new,block);
UndoReplaceAllInBlock first last len l1 c1 repl replace new Nil
	= (l1,c1,replace,new,Nil);
UndoReplaceAllInBlock first last len l1 c1 repl replace=:({Replaced | lnr,cnr}:!_) new block=:(line:!lines)
	| first <= lnr && lnr < last
		= (l1`,c1`,replace`,new`,line`:!lines`);
		= (l1,c1,replace,new,block);
	where {
	(l1`,c1`,replace`,new`,lines`)
		= UndoReplaceAllInBlock (inc first) last len l11 c11 repl replace1 new1 lines;
	(l11,c11,replace1,new1,line`)
		= UndoReplaceAllInLine 0 0 first len l1 c1 repl replace new line;
	};
	
UndoReplaceAllInLine ::	!Int !Int !Int !Int !Int !Int !String !(List Replaced) !(List Replaced) !TLine
						-> (!Int,!Int,!List Replaced,!List Replaced,!TLine);
UndoReplaceAllInLine prevlen corr ln len l1 c1 repl Nil new line
	= (l1,c1,Nil,new,line);
UndoReplaceAllInLine prevlen corr ln len l1 c1 repl replace new Nil
	= (l1,c1,replace,new,Nil);
UndoReplaceAllInLine prevlen corr ln len l1 c1 repl replace=:({Replaced | lnr,cnr}:!restreplace) new line=:(TabStr:!strs)
	| ln <> lnr	= (l1,c1,replace,new,line);
				= (l1`,c1`,replace`,new`,TabStr:!strs`);
	where {
	(l1`,c1`,replace`,new`,strs`)
		= UndoReplaceAllInLine (inc prevlen) corr ln len l1 c1 repl replace new strs;
	};
UndoReplaceAllInLine prevlen corr ln len l1 c1 repl replace=:({Replaced | lnr,cnr}:!restreplace) new line=:(str:!strs)
	| ln <> lnr	= (l1,c1,replace,new,line);
				= (l1`,c1`,replace`,new`,str`:!strs`);
	where {
	(l1`,c1`,replace`,new`,strs`)
			= UndoReplaceAllInLine prevlen` corr` ln len lnr c11 repl replace1 new1 strs;
	(c11,replace1,new1,str`)
			= UndoReplaceAllInString prevlen corr ln len c1 repl replace new str;
	corr`	= corr + size str` - slen;
	prevlen`= prevlen + slen;
	slen	= size str;
	};
	
UndoReplaceAllInString ::	!Int !Int !Int !Int !Int !String !(List Replaced) !(List Replaced) !String
							-> (!Int,!List Replaced,!List Replaced,!String);
UndoReplaceAllInString prevlen corr ln len c1 repl Nil new str
	= (c1,Nil,new,str);
UndoReplaceAllInString prevlen corr ln len c1 repl replaced=:({Replaced | lnr,cnr}:!rest) new str
	| ln <> lnr	|| cbegin >= lengthstr
		= (c1,replaced,new,str);
		= UndoReplaceAllInString prevlen corr` ln len c1` repl rest new` str`;
	where {
	cbegin		= cnr - prevlen + corr;
	cend		= cbegin + len;
	corr`		= corr + diff;
	c1`			= corr + cnr;
	new`		= {Replaced | lnr = lnr, cnr = c1`} :! new;
	str`		= MySlice str 0 (dec cbegin) +++ repl +++ MySlice str cend (dec lengthstr);
	diff		= lengthrepl - len;
	lengthstr	= size str;
	lengthrepl	= size repl;
	};

//
//	Text_ReplaceAll replaces all ocurrences of 'find' by 'replace'
//	It returns the undo info (to undo the replace all), the text-selection of the lastly replaced
//	string and the new text
//

Text_ReplaceAll :: !FindInfo !Text -> (!Bool,!ReplacedAll,!PartTSel,!Text);
Text_ReplaceAll {find,replace,ignore,matchw} text
	| find == "" || NoGoodFind find || NoGoodFind replace
		= (False,EmptyReplacedAll,{l1=0,c1=0,l2=0,c2=0},text);
		= (found,{len= size replace,org=replace,repl=find,ln_cns=Reverse replaced},
				{l1=ln,c1=cn,l2=ln,c2=cn + size replace},text`);
	where {
	(found,ln,cn,replaced,text`)= ReplaceAll False 0 0 0 find replace Nil text ignore matchw;
	};

ReplaceAll :: !Bool !Int !Int !Int !String !String !(List Replaced) !Text !Bool !Bool
	           -> (!Bool,!Int,!Int,!List Replaced,!Text);
ReplaceAll found li lnr cnr find replace replaced Nil i m
	= (found,lnr,cnr,replaced,Nil);
ReplaceAll found li lnr cnr find replace replaced (block:!blocks) i m
	= (found`,lnr`,cnr`,replaced`,block`:!rest);
	where {
	(found`,lnr`,cnr`,replaced`,rest)
		= ReplaceAll found1 li` lnr1 cnr1 find replace replaced1 blocks i m;
	(found1,lnr1,cnr1,replaced1,block`)
		= ReplaceAllInBlock found li lnr cnr find replace replaced block i m;
	li`	= li + LinesPerBlock;
	};

ReplaceAllInBlock :: !Bool !Int !Int !Int !String !String !(List Replaced) !Block !Bool !Bool
	                  -> (!Bool,!Int,!Int,!List Replaced,!Block);
ReplaceAllInBlock found li lnr cnr find replace replaced Nil i m
	= (found,lnr,cnr,replaced,Nil);
ReplaceAllInBlock found li lnr cnr find replace replaced (line:!lines) i m
	= (found`,lnr`,cnr`,replaced`,line`:!rest);
	where {
	(found`,lnr`,cnr`,replaced`,rest)
			= ReplaceAllInBlock found2 (inc li) lnr1 cnr1 find replace replaced1 lines i m;
	(found1,cnr1,replaced1,line`)
			= ReplaceAllInLine False 0 cnr li find replace replaced line i m;
	lnr1 | found1
			= li;
			= lnr;
	found2	= found || found1;
	};

ReplaceAllInLine ::	!Bool !Int !Int !Int !String !String !(List Replaced) !TLine !Bool !Bool
					-> (!Bool,!Int,!List Replaced,!TLine);
ReplaceAllInLine found ci cnr ln find replace replaced Nil i m =  (found,cnr,replaced,Nil);
ReplaceAllInLine found ci cnr ln find replace replaced (TabStr:!strs) i m
	= (found`,cnr`,replaced`,(TabStr:!rest));
	where {
	(found`,cnr`,replaced`,rest)
		= ReplaceAllInLine found (inc ci) cnr ln find replace replaced strs i m;
	};
ReplaceAllInLine found ci cnr ln find replace replaced (str:!strs) i m
	= (found`,cnr`,replaced`,str`:!rest);
	where {
	(found`,cnr`,replaced`,rest)
			= ReplaceAllInLine found2 ci` cnr2 ln find replace replaced1 strs i m;
	(found1,cnr1,replaced1,str`)
			= ReplaceAllInString False 0 len 0 ln ci find replace replaced str i m;
	ci`		= ci + len`;
	cnr2 | found1
			= ci + cnr1;
			= cnr;
	found2	= found || found1;
	len		= size str;
	len`	= size str`;
	};

ReplaceAllInString :: !Bool !Int !Int !Int !Int !Int !String !String !(List Replaced) !String !Bool !Bool
	                   -> (!Bool,!Int,!List Replaced,!String);
ReplaceAllInString found ci len cnr ln cn find replace replaced str i m
	| len < flen
		= (found,cnr,replaced,str);
	| StringMatch ci flen find str i m	
		= ReplaceAllInString True ci` len` ci ln cn` find replace replaced` str` i m;
		= ReplaceAllInString found (inc ci) (dec len) cnr ln (inc cn) find replace replaced str i m;
	where {
	ci`			= ci + rlen;
	cn`			= cn + rlen;
	len`		= len - flen;
	replaced`	= {Replaced | lnr=ln,cnr=cn}:!replaced;
	str`		= MySlice str 0 (dec ci) +++ replace +++ MySlice str (ci + flen) (dec slen)  ;
	rlen		= size replace;
	flen		= size find;
	slen		= size str;
	};

//
//	Text_Find finds the next or previous occurrence of 'find'
//	It returns the text-selection of the found string
//

Text_Find :: !Int !Int !Int !FindInfo !Text -> (!Bool,!PartTSel);
Text_Find nrlines lnr cnr {find,ignore,backw,wrapa,matchw} text
	| find == "" || NoGoodFind find	= (False,{l1=0,c1=0,l2=0,c2=0});
	| backw								= (found1,{l1=l1,c1=c1,l2=l1,c2=c1 + flen});
										= (found2,{l1=l2,c1=c2,l2=l2,c2=c2 + flen});
	where {
	(found1,l1,c1)	= FindPrevious nrlines lnr cnr find ignore wrapa matchw text;
	(found2,l2,c2)	= FindNext lnr lnr cnr find ignore wrapa matchw text text;
	flen			= size find;
	};

FindPrevious :: !Int !Int !Int !String !Bool !Bool !Bool !Text -> (!Bool,!Int,!Int);
FindPrevious nrlines lnr cnr find i w m text
	| fnd	= (True,lnr,dec (cnr - ci));
			= FindUp nrlines (dec lnr) flen find i w m text;
	where {
	(fnd,ci)	= FindUpLine 0 flen find i m (TakeChars cnr (Text_GetLine lnr text) Nil);
	flen		= size find;
	};

FindUp	:: !Int !Int !Int !String !Bool !Bool !Bool !Text -> (!Bool,!Int,!Int);
FindUp nrlines ln flen find i wrap m text
	| ln < 0 && wrap	= FindUp nrlines (dec nrlines) flen find i False m text;
	| ln < 0			= (False,0,0);
	| fnd				= (True,ln,cn);
						= FindUp nrlines (dec ln) flen find i wrap m text;
	where {
	(fnd,ci)	= FindUpLine 0 flen find i m (Reverse line);
	line		= Text_GetLine ln text;
	cn			= Line_NrChars line - ci;
	};

FindUpLine :: !Int !Int !String !Bool !Bool !TLine -> (!Bool,!Int);
FindUpLine ci flen find i m Nil =  (False,ci);
FindUpLine ci flen find i m (str:!strs)
	| flen > len || not fnd	= FindUpLine (ci + len) flen find i m strs;
							= (True,dec (ci + (len - ci`)));
	where {
	(fnd,ci`)	= FindUpString (len - flen) flen find i m str;
	len			= size str;
	};

FindUpString :: !Int !Int !String !Bool !Bool !String -> (!Bool,!Int);
FindUpString ci flen find i m str
	| ci < 0							= (False,ci);
	| StringMatch ci flen find str i m	=  (True,ci);
										=  FindUpString (dec ci) flen find i m str;

FindNext :: !Int !Int !Int !String !Bool !Bool !Bool !Text !Text -> (!Bool,!Int,!Int);
FindNext li lnr cnr find i w m (block:!blocks) text
	| lnr < LinesPerBlock	= FindInBlock li lnr cnr find i w m block blocks text;
							= FindNext li (lnr - LinesPerBlock) cnr find i w m blocks text;

FindInBlock :: !Int !Int !Int !String !Bool !Bool !Bool !Block !Text !Text
	            -> (!Bool,!Int,!Int);
FindInBlock li 0 cnr find i w m (line:!lines) blocks text
	=  FindInLine li cnr cnr find i w m line lines blocks text;
FindInBlock li lnr cnr find i w m (line:!lines) blocks text
	=  FindInBlock li (dec lnr) cnr find i w m lines blocks text;

FindInLine	:: !Int !Int !Int !String !Bool !Bool !Bool !TLine !Block !Text !Text
	           -> (!Bool,!Int,!Int);
FindInLine li ci cnr find i w m (str:!strs) lines blocks text
	| cnr < len	= Find li ci find i w m (str % (cnr,dec len):!strs) lines blocks text;
				= FindInLine li ci (cnr - len) find i w m strs lines blocks text;
	where {
	len= size str;
	};

Find :: !Int !Int !String !Bool !Bool !Bool !TLine !Block !Text !Text -> (!Bool,!Int,!Int);
Find li ci find i w m (str:!strs) lines blocks text
	| found
		= (True,li,ci + cnr);
		= Find li (ci + len) find i w m strs lines blocks text;
	where {
		(found,cnr)	= FindInString 0 (size find) len find i m str;
		len			= size str;
	};
Find li ci find i w m Nil (line:!lines) blocks text
	= Find (inc li) 0 find i w m line lines blocks text;
Find li ci find i w m Nil Nil ((line:!lines):!blocks) text
	= Find (inc li) 0 find i w m line lines blocks text;	
Find li ci find i wrap m Nil Nil Nil ((line:!lines):!blocks)
	| wrap
		= Find 0 0 find i False m line lines blocks EmptyText;
		= (False,0,0);

FindInString :: !Int !Int !Int !String !Bool !Bool !String -> (!Bool,!Int);
FindInString ci flen len find i m str
	| len < flen						= (False,0);
	| StringMatch ci flen find str i m	= (True,ci);
										=  FindInString (inc ci) flen (dec len) find i m str;


//	Text_Replace replaces the string found on the given pos with 'replace'

Text_Replace :: !Int !Int !FindInfo !Text -> (!Bool,!Text);
Text_Replace l1 c1 {find,replace} text
	| NoGoodFind replace	= (False,text);
							= (True,Text_SetLine l1 line` text);
	where {
	line`= ReplaceInLine c1 (size find) replace (Text_GetLine l1 text);
	};

ReplaceInLine :: !Int !Int !String !TLine -> TLine;
ReplaceInLine ci flen replace (str:!rest)
	| ci < len	= str` :! rest;
				= str :! ReplaceInLine (ci - len) flen replace rest;
	where {
	str`	= MySlice str 0 (dec ci)  +++  replace +++  MySlice str (ci + flen) (dec len)  ;
	len		= size str;
	};

//	StringMatch checks if the string 'find' occurs in 'string' at position 'si'

StringMatch	:: !Int !Int !String !String !Bool !Bool -> Bool;
StringMatch si flen find string ignore match
//	| match && (NonOutlineChar (dec si) string || NonOutlineChar (si + flen) string)
//		= False;
	| si+flen>size string
		= False;
	| match
		= if ignore
			(IgnoreCaseStringMatch 0 si flen find string && found_word (dec si) (si+flen) string)
			(NormalStringMatch 0 si flen find string && found_word (dec si) (si+flen) string);
	| ignore
		= IgnoreCaseStringMatch 0 si flen find string;
		= NormalStringMatch 0 si flen find string;

found_word before_index after_index string
	= (before_index<0 || not_both_ident_or_funny_chars (string.[before_index]) (string.[inc before_index]))
	&& (after_index>= size string || not_both_ident_or_funny_chars (string.[dec after_index]) (string.[after_index]));
	{
		not_both_ident_or_funny_chars char1 char2
			| (IsFunnyChar char1 && IsFunnyChar char2) || (IsIdentChar char1 && IsIdentChar char2)
				= False;
				= True;
	}

IgnoreCaseStringMatch :: !Int !Int !Int !String !String -> Bool;
IgnoreCaseStringMatch fi si flen find string
	| fi >= flen
		= True;
	| IgnoreCaseEqualChar (find.[fi]) (string.[si])
		= False;
		= IgnoreCaseStringMatch (inc fi) (inc si) flen find string;

NormalStringMatch :: !Int !Int !Int !String !String -> Bool;
NormalStringMatch fi si flen find string
	| fi >= flen					= True;
	| find.[fi]  <>  string.[si]	= False;
									= NormalStringMatch (inc fi) (inc si) flen find string;

IgnoreCaseEqualChar	:: !Char !Char -> Bool;
IgnoreCaseEqualChar c1 c2
	| c1 == c2								= False;
	| c1 >= FirstSmall && c1 <= LastSmall	= toInt c1 - CaseOffset <> toInt c2 ;
	| c2 >= FirstSmall && c2 <= LastSmall	= toInt c1 <> toInt c2  - CaseOffset ;
											= True;

//
//	Functions to change the current line
//

AddCharBefore :: !Char !TLine -> TLine;
AddCharBefore char Nil								= toString char:!Nil;
AddCharBefore char bef=:(str:!rest)	| str == TabStr	= charst :! bef;
													= (str +++ charst) :! rest;
	where {
	charst= toString char;
	};

AddStringBefore	:: !String !TLine -> TLine;
AddStringBefore str Nil		=  Reverse (Line_MakeLine str);
AddStringBefore str before	=  AddLineBefore (Line_MakeLine str) before;

AddLineBefore :: !TLine !TLine -> TLine;
AddLineBefore (str:!Nil) before=:(first:!rest)
	| str == TabStr || first == TabStr	= str:!before;
											= (first +++ str) :!rest;
AddLineBefore (str:!rest) before				= AddLineBefore rest (str:!before);

AddWordBefore :: !String !TLine -> TLine;
AddWordBefore str line = AddWordBack (dec (size str)) str line;

AddWordBack :: !Int !String !TLine -> TLine;
AddWordBack i str line
	| i < 0			= line;
	| tabpos < 0	= line`;
	| tabpos == i	= TabStr :! AddWordBack i` str line;
					= str % (inc tabpos, i) :! TabStr :! AddWordBack i` str line;
	where {
	tabpos	= FindTabB i str;
	i`		= dec tabpos;
	line`	= case line of
				{	Nil			-> str % (0, i) :! Nil;
					TabStr:!rest-> str % (0, i) :! line;
					str`:!rest	-> (str` +++ str % (0, i)) :! rest;
				};
	};
	
FindTabB :: !Int !String -> Int;
FindTabB pos str
	| pos < 0				= -1;
	| str.[pos] == TabChar	= pos;
							= FindTabB (dec pos) str;

RemoveCharBefore :: !TLine -> (!Char,!TLine);
RemoveCharBefore (str:!rest)
	| last == 0	= (char, rest);
				= (char, str % (0, dec last) :! rest);
	where {
	char	= str.[last];
	last	= dec (size str);
	};

RemoveWordBefore :: !TLine -> (!String,!TLine);
RemoveWordBefore line=:(str:!rest)
	| c == ' ' || c == TabChar		= RemoveLayOutBack str last rest "";
	| no_id_char && last == 0		= (str, rest);
	| no_id_char					= (str % (last, last), str % (0, dec last) :! rest);
	| more == ""					= (word, rest);
									= (word, more :! rest);
	where {
		(word,more)			= RemoveWordBack pred str last;
		pred	| funny_id	= FunnyId;
							= NormalId;
		funny_id			= FunnyId last str;
		normal_id			= NormalId last str;
		no_id_char			= not (funny_id || normal_id);
		c					= str.[last];
		last				= dec (size str);
	};

RemoveWordBack :: !(Int String -> Bool) !String !Int -> (!String, !String);
RemoveWordBack pred str i
	| i < 0			= (str, "");
	| pred i str	= RemoveWordBack pred str (dec i);
					= (str % (inc i, dec (size str)), str % (0, i));
					
RemoveLayOutBack :: !String !Int !TLine !String -> (!String, !TLine);
RemoveLayOutBack str i line acc
	| i < 0						= line`;
	| c == ' '					= RemoveLayOutBack str (dec i) line acc;
	| c == TabChar				= line`;
								= (acc +++ (str % (inc i, dec (size str))), str % (0, i) :! line);
	where {
	c		= str.[i];
	line`	= case line of
				{	Nil			-> (acc +++ str, Nil);
					str`:!rest	-> RemoveLayOutBack str` (dec (size str`)) rest (acc +++ str);
				}
	};

AddCharAfter :: !Char !TLine -> TLine;
AddCharAfter char Nil								= toString char :! Nil;
AddCharAfter char bef=:(str:!rest)	| str == TabStr	= charst :! bef;
													= (charst +++ str) :! rest;
	where {
	charst= toString char;
	};
	
AddStringAfter :: !String !TLine -> TLine;
AddStringAfter str Nil		=  Line_MakeLine str;
AddStringAfter str after	=  AddLineAfter (Reverse (Line_MakeLine str)) after;

AddLineAfter :: !TLine !TLine -> TLine;
AddLineAfter (str:!Nil) after=:(first:!rest)
	| str == TabStr || first == TabStr	= str:!after;
											= (str +++ first) :!rest;
AddLineAfter (str:!rest) after				= AddLineAfter rest (str:!after);

	AddWordAfter :: !String !TLine -> TLine;
	AddWordAfter str line = AddWordFront 0 (size str) str line;

	AddWordFront :: !Int !Int !String !TLine -> TLine;
	AddWordFront i strlen str line
		| i >= strlen		= line;
		| tabpos >= strlen	= line`;
		| tabpos == i		= TabStr :! AddWordFront i` strlen str line;
							= str % (i, dec tabpos) :! TabStr :! AddWordFront i` strlen str line;
		where {
		tabpos	= FindTabF i strlen str;
		i`		= inc tabpos;
		line`	= case line of
					{	Nil			-> str % (i, dec strlen) :! Nil;
						TabStr:!rest-> str % (i, dec strlen) :! line;
						str`:!rest	-> (str % (i, dec strlen) +++ str`) :! rest;
					};
		};

FindTabF :: !Int !Int !String -> Int;
FindTabF pos strlen str
	| pos >= strlen			= strlen;
	| str.[pos] == TabChar	= pos;
							= FindTabF (inc pos) strlen str;

RemoveCharAfter	:: !TLine -> (!Char,!TLine);
RemoveCharAfter (str:!rest)	| last == 0	= (char, rest);
										= (char, str % (1, last) :! rest);
	where {
	char	= str.[0];
	last	= dec (size str);
	};

RemoveWordAfter :: !TLine -> (!String,!TLine);
RemoveWordAfter line=:(str:!rest)
	| c == ' ' || c == TabChar	= RemoveLayOutFront str 0 len rest "";
	| no_id_char && len == 1	= (str, rest);
	| no_id_char				= (str % (0, 0), str % (1, dec len) :! rest);
	| more == ""				= (word, rest);
								= (word, more :! rest);
	where {
	(word,more)			= RemoveWordFront pred str 1 len;
	pred	| funny_id	= FunnyId;
						= NormalId;
	funny_id			= FunnyId 0 str;
	normal_id			= NormalId 0 str;
	no_id_char			= not (funny_id || normal_id);
	c					= str.[0];

	len					= size str;
	};

RemoveWordFront :: !(Int String -> Bool) !String !Int !Int -> (!String, !String);
RemoveWordFront pred str i len
	| i >= len		= (str, "");
	| pred i str	= RemoveWordFront pred str (inc i) len;
					= (str % (0, dec i), str % (i, dec len));
					
RemoveLayOutFront :: !String !Int !Int !TLine !String -> (!String, !TLine);
RemoveLayOutFront str i len line acc
	| i >= len		= line`;
	| c == ' '		= RemoveLayOutFront str (inc i) len line acc;
	| c == TabChar	= line`;
					= (acc +++ str % (0, dec i), str % (i, dec len) :! line);
	where {
	c		= str.[i];
	line`	= case line of
				{	Nil			-> (acc +++ str, Nil);
					str`:!rest	-> RemoveLayOutFront str` 0 (size str`) rest (acc +++ str);
				}
	};

/*	NoGoodFind checks if the string passed to it contains non-printable characters */

NoGoodFind	:: !String -> Bool;
NoGoodFind ""  =  False;
NoGoodFind str =  NonPrintablesInString (dec (size str)) str;

NonPrintablesInString :: !Int !String -> Bool;
NonPrintablesInString 0 str
		= char < FirstPrintable || char > LastPrintable ;
	where {
	char= str.[0];
	};
NonPrintablesInString id str
	| char < FirstPrintable || char > LastPrintable
		= True;
		= NonPrintablesInString (dec id) str;
	where {
	char= str.[id];
	};

// FunnyId checks whether the string contains a funny identifier char on the indicated pos

FunnyId :: !Int !String -> Bool;
FunnyId i str
	| i < 0 || i >= size str
		= False;
		= IsFunnyChar c;
	where {
		c = str.[i];
	};

IsFunnyChar c
	=	c == '~' || c == '@' || c == '#' || c == '$' || c == '%' || c == '^' || c == '?'  ||
		c == '!' || c == '+' || c == '-' || c == '*' || c == '<' || c == '>' || c == '\\' ||
		c == '/' || c == '|' || c == '&' || c == ':';

	
// NormalId checks whether the string contains a normal identifier char on the indicated pos
	
NormalId :: !Int !String -> Bool;
NormalId i str
	| i < 0 || i >= size str
		= False;
		= IsIdentChar c;
	where {
		c = str.[i];
	};

IsIdentChar c
	= 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' || c == '_' || c == '`';

// Misc. function(s)

MySlice	:: !String !Int !Int -> String;
MySlice str b e
	| b > e	= "";
	        = str % (b, e);

