// utf-8

/*	Copyright  (c)	Günter Woigk   2001-2003
  					mailto:kio@little-bat.de

	This file is free software

 	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.  

 	Permission to use, copy, modify, distribute, and sell this software and
 	its documentation for any purpose is hereby granted without fee, provided
 	that the above copyright notice appear in all copies and that both that
 	copyright notice and this permission notice appear in supporting
 	documentation, and that the name of the copyright holder not be used
 	in advertising or publicity pertaining to distribution of the software
 	without specific, written prior permission. The copyright holder makes no
 	representations about the suitability of this software for any purpose.
 	It is provided "as is" without express or implied warranty.

 	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 	PERFORMANCE OF THIS SOFTWARE.

	2003-08-23 kio	changes for eliminated Var type 'isVoid'
*/

#define SAFE	2
#define	LOG 	1

#include <math.h>
#include "config.h"
#include "VScript.h"
#include "compress/freeze.h"
#include "token.h"
#include "SystemIO.h"
#include "unix/os_utilities.h"




/* ----	skip matching brackets -------------------------------
*/
static uchar* SkipBrackets ( uchar*ip )
{
	if (Error()) return ip;
	XXXTRAP(*ip!=tEKauf&&*ip!=tGKauf&&*ip!=tRKauf);
	uchar c = *ip==tEKauf ? tEKzu : *ip==tRKauf ? tRKzu : tGKzu;

	for ( ip++; *ip!=tEOF; )
	{
		if (*ip==c) return ip+1;
		if (*ip==tEKauf||*ip==tRKauf||*ip==tGKauf) { ip=SkipBrackets(ip); if (Error()) return ip; }
		else ip=SkipToken(ip);
	}

	SetError(errEOF);
	return ip;
}


/* ----	compress string ----------------------------------------
		using the freeze algorithm
	note: freeze is character based and after compression you cannot tell
		from the compressed data whether it was csz1, csz2 or csz4.
		also it seems to be no good idea to extend the algorithm to remember the
		uncompressed character size, since it typically works on arbitrary binary data.
		it is up to the caller to know how to interpret the data.
		as a solution we simply keep the character size of the string
	problem: csz2|4 strings provide a buffer size multiple of 2|4
		additional bytes must be handled and must not be passed to melt()
	solution: compressed buffer cannot start with reference-to-template but
		does always start with verbatim-data which starts with byte %111xxxxx
		so we start with padding 0 .. 3 bytes before the compressed data
		for security reason we use %11001111 which would start a reference-to-template
		with offset of approx. -2 MB (in case the "initial data header" idea is realized)
*/
void FreezeString ( String& v )
{
	CharSize csz = v.Csz();

	if( csz>csz1 )
	{
		csz = v.ReqCsz();
		v.ResizeCsz(csz);
	}

	size_t qlen = v.Len() * csz;
	if (qlen<=4) return;							// compressed == uncompressed
	size_t zlen = (1+qlen/(1<<12)) * ((1<<12)+1);	// this _should_ be large enough for the worst case

	if (csz>csz1)
	{
		String s(v.Len(),csz);
		ptr z = s.Text();

		for( int i=0; i<csz; i++ )
		{
			for ( ptr q=v.Text()+i, e=z+s.Len(); z<e; ) { *z++=*q; q+=csz; }
		}

		v = s;
	}

	uptr qptr = (uptr)v.Text();
	uptr zptr = new uchar[zlen]; if(!zptr) { SetError(outofmemory); return; }

	zlen = Freeze ( qptr, qlen, zptr, zlen );		// may set error "out of memory" for tables

	if (NoError())
	{
		XXXTRAP( (*zptr|0x1F)!=0xFF );				// compressed data does not start with verbatim-date !!!
		XXTRAP (  *zptr      ==0xCF );				// compressed data starts with reference-to-template with offset -2 MB !!
		int d = (-zlen) & (csz-1);
		XXXTRAP( (zlen+csz-1)/csz*csz!=(zlen+d) );
		v = emptyString;
		v = String((zlen+d)/csz,csz);
		XXXTRAP(v.Csz()!=csz);
		*(long*)(v.Text()) = 0xCFCFCFCF;			// 4x %11001111
		memcpy(v.Text()+d,zptr,zlen);
	}

	delete[] zptr;
}


void MeltString ( String& v )
{
	CharSize csz = v.Csz();
	if (csz>csz1)
	{
		csz = v.ReqCsz();
		if (csz<v.Csz())
		{
			Log("MeltString(): warning: csz seems increased");
			v.ResizeCsz(csz);
		}
	}

	size_t qlen = v.Len() * csz;
	if (qlen<=4) return;							// compressed == uncompressed
	uptr qptr = (uptr)v.Text();
	while( qlen && *qptr==0xCF ) { qptr++; qlen--; }
	size_t zlen = MeltSize(qptr,qlen); if (Error()) return;
	if (zlen%csz) { SetError(corrupteddata); return; }

	String z(zlen/csz,csz);
	uptr zptr = (uptr)z.Text();

	zlen = Melt ( qptr, qlen, zptr, zlen );			// may set error "out of memory" for tables

	if (NoError())
	{
		XXXTRAP(zlen!=MeltSize(qptr,qlen));

		if (csz==csz1)
		{
			v = z;
		}
		else
		{
			v = emptyString;
			v = String(zlen/csz,csz);

			ptr q = z.Text();

			for( int i=0; i<csz; i++ )
			{
				for ( ptr z=v.Text()+i, e=q+v.Len(); q<e; ) { *z=*q++; z+=csz; }
			}
		}
	}
}


CharEncoding GetEncodingFromName ( String conv_name )
{
	String s = conv_name.ToLower();
	for (int i=0;(i=s.Find('_',i))>=0;) s=s.LeftString(i)+s.MidString(i+1);

	//				xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx
	int n = String("quoted  escaped printab html    utf8    upperca lowerca macroma us      asciius "
				   "german  asciige atari   atarist rtos    dos     cp437   amiga   windows archime "
				   "ucs1    ucs2    ucs4    upper   lower   url     urlall  mime").Find(s.LeftString(7));
	if (n>=0)
	{
		const CharEncoding e[] =
				{ quoted, escaped, escaped, html, utf8, uppercase, lowercase, mac_roman, ascii_us, ascii_us,
				  ascii_ger, ascii_ger, atari_st,atari_st, rtos, cp_437, cp_437, iso_latin_1, iso_latin_1, iso_latin_1,
				  ucs1, ucs2, ucs4, uppercase, lowercase, url, url_all, mime };
		return e[n/8];
	}

	if (LeftString(s,3)=="iso"||LeftString(s,5)=="latin")
	{
		if (_no_dec_digit(s[s.Len()-2]))
			switch(s[s.Len()-1])
			{
			case '1':	return iso_latin_1;
			}
	}

	if( (s.Len()==4&&LeftString(s,3)=="tab") || (s.Len()==5&&LeftString(s,4)=="tabs") )
	{
		UCS4Char c = s[s.Len()-1];
		if (c>='1'&&c<='9') return CharEncoding( c -'1' +tab1 );
	}

	SetError( String("unknown conversion: ") + conv_name );
	return utf8;
}



/* ======================================================================
					E X E C U T E   S C R I P T
====================================================================== */


/* ----	execute instructions ---------------------
		execute instructions until:
			error	-> Error() = error
			"}"		-> Error() = errGKzu
			tEOF	-> Error() = errEOF		((unexpected physical end of file)
			END		-> Error() = ok  		&&	return value v = void
			RETURN	-> Error() = ok		&&	return value v = valid

		returns incremeted ip
		some command feature automatic multi-arguments separated by tKOMMA
		if instruction starts with a non-instruction, an assignment is assumed
*/


uchar* VScript::Execute ( Var& myProc, Var*& u, uchar* ip )
{	Var  temp_v, *v;	// primary scratch
	Var  temp_w, *w;	// secondary scratch
	Var& temp_u = *u;	// result cell of caller

	TRAP( ptr(uchar(0x10)*uchar(0x10))!=ptr(0x100)  );

	XXTRAP(!u);
	XXTRAP(myProc.IsNoProc());
	if (ip==NULL) ip = (uchar*)myProc.Proc().Text();
	XXTRAP(ip<(uchar*)myProc.Proc().Text());

	Var* oldProc = proc; proc = &myProc;

	uchar*	ip0;
	uchar	cmd=tKOMMA;				// current instruction
	uchar	lastcmd;				// previous instruction for cmd tKOMMA
	String	idf;
	uint	mystream;

// exit etc. from do loop must purge private context before leaving/restarting loop
	Var*	do_locals[10];			// dropping out of a do loop => restore locals*
	ushort	do_error_idx[10];		// dropping out of a do loop => restore error_idx
	uint	do_idx = 0;				// index of last entry _in_ _use_ !!
	do_locals[0] = NULL;			// => no need to test for do_idx later

// error handling:
	uptr	error_handlers[10];		// handler: ptr behind tELSE.offs or behind tTHEN.offs
	ushort	error_do_idx[10];		// dropping out of a do loop => restore do_locals_idx
	Var*	error_locals[10];		// dropping out of a private context => restore locals*
	uint	error_idx = 0;			// index of last entry _in_ _use_ !!
	error_handlers[0] = NULL;		// => no need to test for error_handler_idx==0 later

#define	LOOP		  goto l_loop	/* test for error and resume */
#define	LOOP_QUICK	  goto l_quick	/* no test for error, just resume */
#define LOOP_KOMMA	  goto l_komma	/* no test for error, redo last command with next argument */
#define	CATCH_ERROR	  goto l_catch	/* catch error and resume (or return) */
#define	EXIT		  goto l_exit	/* exit ok or error, return no value */
#define	RETURN_RESULT goto l_return	/* exit ok or error, return value u */


// ==== restart loop with error check ======================
l_loop:

// ====	handle error =======================================
	if (Error())
	{
l_catch:XXXTRAP(NoError());

		if (Error()==errEOF)	// don't catch errEOF set by 'end'
		{						// => return end result instead
			if (locals==globals)// script bottom level reached?
				ClearError();	// => clear error; else preserve errEOF
			u = &end_result;	// set result for caller
			RETURN_RESULT;		// return result
		}

		XXXLog("**Error!**");

		if (error_idx==0)		// no handler ?
		{
// ==== exit from Execute() ================================
l_exit:		u = NULL;
l_return:	proc = oldProc;
			return ip;
		}

		XXXLog("**caught!**");

		ip = error_handlers[error_idx];
		do_idx = error_do_idx[error_idx];
		v = error_locals[error_idx];
		error_idx--;

		if (v!=locals) 			// private block pending ?
		{
			while (locals->Parent()!=v) locals=locals->Parent();// find outmost private block
			delete locals; locals = v;							// purge privates
		}

		XXXTRAP(!locals);
		XXXTRAP(locals->IsNoList());
		idf = "errorstr";
		v = locals->FindItem(idf);
		if (!v) { v = new Var(idf,isText); locals->AppendItem(v); }
		*v = ErrorStr();
		ClearError();
	}


// ==== restart loop without error check ===================
l_quick:
	if(XXXLOG) { Log("["); Disass(stderrStream,ip,1); Log("]\n"); }
	XXXTRAP(ip>=(uchar*)myProc.Proc().Text()+myProc.Proc().Len());

	v = &temp_v;				// setup result pointer to temp cell
	lastcmd = cmd;				// remember last command for tKOMMA instruction
	cmd = *ip++;				// get next command


// ==== restart loop with last command =====================
l_komma:
	XXXTRAP(Error());
	XXXXTRAP(v!=&temp_v);
	if(XXXXSAFE) { globals->Validate(); if(globals!=locals) locals->Validate(); }
	XXXTRAP(ip>(uchar*)myProc.Proc().Text()+myProc.Proc().Len());
	XXXTRAP((ip==(uchar*)myProc.Proc().Text()+myProc.Proc().Len())&&(cmd!=tEOF&&cmd!=tGKzu));

	switch(cmd)
	{

	default:					// no instruction?  =>  assume expression & discard result
		u = &temp_u;
		ip = Value(ip-1,u,pAcceptVoid);	// accept 'no value'; stop at pAssign
		if (*ip!=tEOF) LOOP;	// discard result and resume
								// at tEOF there can be no error handler
		RETURN_RESULT;			// return result: shell may print it

	case tKOMMA:				// repeat last command
		if (lastcmd<tSEMIK) { cmd = lastcmd; LOOP_KOMMA; }
		SetError("unexpected ','");
		CATCH_ERROR;

	case tGKzu:				 	// end of local context
		w = locals->Parent();
		if (w) { delete locals; locals = w; LOOP_QUICK; }	// end of privates
	//	SetError(errGKzu);		// hopefully end of proc
		EXIT;					// return no value

	case tEND:					// end => end of script
	// outside proc => easy => just 'return' value | novalue  &&  noerror
		if( locals == globals ) goto tr;

	// from within proc:
	// 1) result will be stored 'anywhere', likely not in the caller's result cell
	// 2) just returning won't quit the script
	// 3) setting error will quit script _except_ if errors are caught with try … then
	// => set error 'errEOF' which must not be caught in error handler
	// => store result in 'this->end_result'
	// note: returning 0 instead of <noresult> is precisely what would happen in main() and so it's ok here too
	//       we will fall through multiple recursions of Value() and Execute();
	//		 so errEOF should be handled correctly after each call to Execute() anywhere.
		u = &end_result; *u = 0.0;
		if ( *ip<tNEW /* pure instructions */ && *ip!=tGKzu && (tEOF>=tNEW||*ip!=tEOF) )
		{
			v = u; ip = Value(ip,v,pAny);
			if (Error()) CATCH_ERROR;
			if (v!=u) u->MoveData(*v);
		}
		SetError(errEOF);
		RETURN_RESULT;

	case tRETURN:				// exit and return value | novalue
tr:		if ( *ip>=tNEW /* pure instructions */ || *ip==tGKzu || (tEOF<tNEW&&*ip==tEOF) ) EXIT;
		u = &temp_u;
		ip = Value(ip,u,pAny);
		if (NoError()) RETURN_RESULT; else CATCH_ERROR;

	case tEOF:					// end of file
		EXIT;					// return no value

	case tTRY:					// try .. else .. then
		error_idx++;
		if( error_idx==NELEM(error_handlers) )
		{
			SetError("error handler nesting exceeded internal limit");
			error_idx--; CATCH_ERROR;
		}
		error_handlers[error_idx] = (ip-1) + *(short*)(ip);	// -> behind else or then
		error_do_idx[error_idx]   = do_idx;
		error_locals[error_idx]   = locals;
		ip += 2;
		LOOP_QUICK;

	case tIF:					// IF expr instr [ ELIF expr instr ] [ ELSE instr ] THEN
		ip0 = ip;
		ip  = NumValue(ip+2,v,pAny);
		if (Error()) CATCH_ERROR;
		if ( !v->Value() ) ip = ip0 + *(short*)(ip0) -1;
		LOOP_QUICK;

	case tELSE:					// if .. else .. then
		if(error_handlers[error_idx]==ip+2) // try .. else .. then
		{
			XXXTRAP(locals!=error_locals[error_idx]);
			XXXTRAP(do_idx!=error_do_idx[error_idx]);
			error_idx--; // pop handler
		}
	case tELIF:					// elif == tELIF.dist->then  (( + tIF.dist->else/then ))
	case tLOOP:					// DO .. LOOP.offs
		ip += *(short*)(ip) -1;
		LOOP_QUICK;

	case tUNTIL:				// DO ... UNTIL.offs expr instr LOOP.offs
		ip0 = ip;
		ip  = NumValue(ip+2,v,pAny);
		if (Error()) CATCH_ERROR;
		if (!v->Value()) LOOP_QUICK;
		ip = ip0;
		goto te;

	case tWHILE:				// DO .. WHILE.offs expr instr LOOP.offs
		ip0 = ip;
		ip  = NumValue(ip+2,v,pAny);
		if (Error()) CATCH_ERROR;
		if (v->Value()) LOOP_QUICK;
		ip = ip0;
		goto te;

	case tEXIT:					// DO .. EXIT.offs .. LOOP.offs
te:		error_idx = do_error_idx[do_idx];
		v = do_locals[do_idx];
		do_idx--;
te1:	ip += *(short*)(ip) -1;
		if (v==locals) LOOP_QUICK;							// no private block pending
		while (locals->Parent()!=v) locals=locals->Parent();// find outmost private block
		delete locals; locals = v;							// purge privates
		LOOP_QUICK;

	case tNEXT:					// DO .. NEXT.offs .. LOOP.offs
		error_idx = do_error_idx[do_idx];
		v = do_locals[do_idx];
		goto te1;

	case tTHEN:		// IF .. THEN
		if(error_handlers[error_idx]==ip) 	// try .. then
		{
			XXXTRAP(locals!=error_locals[error_idx]);
			XXXTRAP(do_idx!=error_do_idx[error_idx]);
			error_idx--; // pop handler
		}
	case tSEMIK:	// ";" == NOP
		LOOP_QUICK;

	case tDO:		// DO .. LOOP
		do_idx++;
		if (do_idx==NELEM(do_locals))
		{
			SetError("do loop nesting exceeded internal limit");
			do_idx--; CATCH_ERROR;
		}
		do_locals[do_idx] = locals;
		do_error_idx[do_idx] = error_idx;
		LOOP_QUICK;

	case tSORT:
		ip = RefValue(ip,v,pAny);
		if(NoError()) v->Sort();
		LOOP;

	case tRSORT:
		ip = RefValue(ip,v,pAny);
		if(NoError()) v->RSort();
		LOOP;

	case tFREEZE:
		if (*ip==tFILE)
		{
			ip = StrValue(ip+1,v,pAny);	if (Error()) CATCH_ERROR;
			LoadFile(v->Text(),temp_w);	if (Error()) CATCH_ERROR;
			FreezeString(temp_w.Text());if (Error()) CATCH_ERROR;
			SaveFile(v->Text(),temp_w);
			temp_w.SetNumber();
		}
		else
		{
			ip = StrRefValue(ip,v,pAny);
			if (NoError()) FreezeString(v->Text());
		}
		LOOP;

	case tMELT:
		if (*ip==tFILE)
		{
			ip = StrValue(ip+1,v,pAny);	if (Error()) CATCH_ERROR;
			LoadFile(v->Text(),temp_w);	if (Error()) CATCH_ERROR;
			MeltString(temp_w.Text());	if (Error()) CATCH_ERROR;
			SaveFile(v->Text(),temp_w);
			temp_w.SetNumber();
		}
		else
		{
			ip = StrRefValue(ip,v,pAny);
			if (NoError()) MeltString(v->Text());
		}
		LOOP;

	case tSPLIT:	// split text_var [ ,text | ,number ]
	{
		ip = RefValue(ip,v,pAny);
		if(*ip==tKOMMA)
		{
			w=&temp_w; ip=Value(ip+1,w,pAny);
			if (w->IsNumber()) { temp_w = String(w->LongValue()); w=&temp_w; } else
			if (w->IsNoText()) SetError(expectedstring);
			if(NoError()) v->SplitLines(w->Text());
		}
		else
		{
			if(NoError()) v->SplitLines();
		}
	}	LOOP;

	case tCONVERT:	//  convert  ref_var  to|from  idf
	{
		ip = RefValue(ip,v,pAny); if (Error()) CATCH_ERROR;
		bool f = *ip==tFROM;
		if (!f&&*ip!=tTO) { SetError(syntaxerror, "'from' or 'to' expected"); CATCH_ERROR; }

		ip = GetIdentifier(ip+1,idf);
		CharEncoding what = GetEncodingFromName(idf);
		if (NoError()) { if (f) v->ConvertFrom(what); else v->ConvertTo(what); }
		LOOP;
	}

	case tREPLACE:	// replace text_var, oldtext, newtext
	{
		u = &temp_u;
		w = &temp_w;
		ip=StrRefValue(ip,v,pAny);	// v = target text
		ip=Expect(ip,tKOMMA);
		ip=StrValue(ip,w,pAny);		// w = search text
		ip=Expect(ip,tKOMMA);
		ip=StrValue(ip,u,pAny);		// u = replacement text
		if (NoError()) v->Text().Replace( w->Text(), u->Text() );
		LOOP;
	}

	case tDEL:
		if (*ip!=tIDF)
		{
			if (*ip==tMUL)	// DEL *		delete all variables in local context
			{
				ip++;
				locals->SetList();
				LOOP_QUICK;
			}
			else
			if (*ip==tFILE||ip[1]==tDIR)
			{
				ip = StrValue(ip+1,v,pAny);
				if (NoError()) DeleteFile(CString(v->Text()));
				LOOP;
			}
		}
		// DEL refvar
		ip = RefValue(ip,v,pAny);
		if (Error()||TrapVarInUse(v)) CATCH_ERROR;
		XXXTRAP(v->Parent()==NULL);
		XXXTRAP(!v->Parent()->IsList());
		v->Parent()->DeleteItem(v->Index());
		LOOP_QUICK;

	case tNEW:	// NEW identifier [ = value ]
				// NEW File "path"
				// NEW Dir  "path"
		if (*ip!=tIDF)
		{
			if (*ip==tFILE)
			{
				ip = StrValue(ip+1,v,pAny);
				if (NoError()) CreateFile(CString(v->Text()));
				LOOP;
			}
			if (*ip==tDIR)
			{
				ip = StrValue(ip+1,v,pAny);
				if (NoError()) CreateDir(CString(v->Text()));
				LOOP;
			}
		}
	// else: resume like VAR

	case tVAR:	// VAR identifier [ = value ]
		w = locals;
		ip--;

		do		// valid identifier:  anton.berta[a,b][c].cäsar[d]
		{
		// handle identifier
			ip = GetIdentifier(ip+1,idf);
			if (Error()) CATCH_ERROR;

			if (w->IsList()) v = w->FindItem(idf); else { w->SetList(); v=NULL; }
			if (!v) { v = new Var(idf,isNumber); w->AppendItem(v); }
			w = v;

		// handle optional list indexes
			while (*ip==tEKauf)
			{
				do
				{
					if (!w->IsList()) w->SetList();
					v = &temp_v;
					ip = NumValue(ip+1,v,pAny);
					long n = (long)v->Value(); if (n<=0) SetError(indexoutofrange);
					if(Error()) CATCH_ERROR;
					w = &(*w)[n-1];
				}
				while (*ip==tKOMMA);
				ip=Expect(ip,tEKzu); if (Error()) CATCH_ERROR;
			}
		}
		while (*ip==tDOT);

		XXXTRAP(Error());
		if (*ip!=tGL) LOOP_QUICK;		// variable created ok

		XXXTRAP(v==&temp_v);
		if (TrapVarInUse(v)) CATCH_ERROR;
		w=&temp_w;
		ip = Value(ip+1,w,pAny);
		if (w!=&temp_w) *v = *w; 		// reference => copy contents from source variable
		else v->MoveData(*w);			// value     => move contents from temp variable
		LOOP;

	case tSETNAME:
		if (*ip!=tIDF)
		{
			if (*ip==tDIR||*ip==tFILE)
			{
				w=&temp_w;
				ip = StrValue(ip+1,v,pAssign);
				ip = Expect(ip,tGL);
				ip = StrValue(ip,w,pAny);
				if (NoError()) RenameFileOrFolder(v->Text(),w->Text());
				LOOP;
			}
		}

		ip = RefValue(ip,v,pAssign);
		ip = Expect(ip,tGL);
		w  = &temp_w;
		ip = StrValue(ip,w,pAny);
		if (Error()) CATCH_ERROR;
		v->SetName(w->Text());			// no test for unique name. no test for valid name.
		XXXTRAP(Error());
		LOOP_QUICK;

	case tPROC:	// Proc identifier ( [identifier] [,identifier] [,*] ) = { instructions }
	{
		uchar* pip;	// ip -> proc_start
		ip = GetIdentifier(ip,idf); if (Error()) CATCH_ERROR;
		pip = ip;

		ip=Expect(ip,tRKauf); if(Error()) CATCH_ERROR;
		if (*ip!=tRKzu) for(;;ip++)
		{
			if (*ip==tMUL) { ip++; break; }
			if(*ip!=tIDF) { SetError(expectedidentifier); CATCH_ERROR; }	// only accept real idfs, no @text constructs!
			else ip += 3 + ip[2]*ip[1];			// manually skip identifier
			if (*ip!=tKOMMA) break;
		}
		ip=Expect(ip,tRKzu);
		if (*ip!=tGKauf) ip=Expect(ip,tGKauf);	// force error
		ip=SkipBrackets(ip);
		if (Error()) CATCH_ERROR;

		w = locals->FindItem(idf);
		if (w) w->SetProc(); else { w=new Var(idf,isProc); locals->AppendItem(w); }
		w->Proc() = String(proc->Proc(),pip,ip-pip,csz1);		// shared substring of current proc
		LOOP_QUICK;
	}

	case tGKauf:					//  { ... private context ... }
		w = new Var("privates",isList); locals->AppendItem(w); locals = w;
		LOOP_QUICK;

	case tSYS:		// sys [#n,] cmd_path, text_arguments, ...
	{				// sys [#n,] cmd_path, list_argument
		mystream = stdout_stream;
		ip = GetStream(ip,mystream,1);

		int n = 0;
		Var args(isList), *a;
		w = a = &args[n++];		// cmd
		ip = StrValue(ip,w,pAny);
		if(w!=a) *a = *w;

		if ( NoError() && *ip==tKOMMA )
		{
			w = &temp_w;
			ip = Value(ip+1,w,pAny);
			if(w->IsList())
			{
				for (uint i=0;i<w->ListSize();i++)
				{
					a = &(*w)[i];
					args[n++] = a->IsText() ? *a : a->ToString(no,DisassString);
				}
			}
			else
			{
				a = &args[n++];
				for(;;)
				{
					if (w->IsNoText()) *a = w->ToString(no,DisassString);
					else if (w!=a) *a = *w;
					if (Error() || *ip!=tKOMMA) break;
					w = a = &args[n++];
					ip = Value(ip+1,w,pAny);
				}
			}
		}

		if(NoError())
		{
			str* argv = new str[n+1];
			for (int i=0;i<n;i++) argv[i] = NewCopy(args[i].Text().CString());
			argv[n] = NULL;

			SetStream(stderr_stream);	// colorize stderr output for error_stream;
										// ***TODO*** redirect stderr to stderr_stream
			str result = ExecCmd(argv);
			if (Error()) ForceError( Error(),
				String("executing ")+ToQuoted(argv[0])+" returned error:\n"+ErrorStr() );
			PushError();
				SetStream(mystream);
				PutString(result);
				delete[] result;
				for (int i=0;i<n;i++) delete[] argv[i];
				delete[] argv;
			PopError();
		}
		LOOP;
	}

	case tINCLUDE:	// include filename
		ip = StrValue(ip,v,pAny);
		if (Error()) CATCH_ERROR;
		u = &temp_u;					// u := result
		idf = v->Text();
		Run(idf,u);
		if (Error())
		{
			ForceError( Error(),
				String("script ")+ToQuoted(idf)+" returned error:\n"+ErrorStr() );
			CATCH_ERROR;
		}
		if (*ip!=tEOF) LOOP_QUICK;
		RETURN_RESULT;					// return result: shell may print it

	case tCALL:		// call filename, argument, ...
	{
		ip = StrValue(ip,v,pAny);		// v = filename
		idf = v->Text();

		Var* mylocals = new Var("locals",isList);

		while (NoError() && *ip==tKOMMA)
		{
			Var* a = new Var;			// next arg
			mylocals->AppendItem(a);
			w = a;
			ip = Value(ip+1,w,pAny);
			if(w!=a) *a = *w;
		}

		if (NoError())
		{
			v = globals; w = locals; globals = locals = mylocals;
			u = &temp_u;
			Run(idf,u);
			if (u&&mylocals->Contains(u)) { temp_u=*u; u=&temp_u; }
			locals = w; globals = v;
		}

		delete mylocals;
		if (Error())
		{
			ForceError( Error(),
				String("script ")+ToQuoted(idf)+" returned error:\n"+ErrorStr() );
			CATCH_ERROR;
		}
		if (*ip!=tEOF) LOOP_QUICK;
		RETURN_RESULT;					// return result: shell may print it
	}

	case tEVAL:		// eval textvalue
	{
		ip = StrValue(ip,v,pAny);
		if (Error()) CATCH_ERROR;
		Var myproc("eval",isProc);
		long idx_io=0;
		myproc.Proc() = Tokenize( v->Text(), idx_io );
		if (NoError())
		{
			u = &temp_u;
			uchar* mip = (uptr)myproc.Proc().Text();
			mip = Execute( myproc, u, mip );

			if (NoError())
			{
				if (*ip!=tEOF) LOOP_QUICK;
				RETURN_RESULT;			// return result: shell may print it
			}
		}
		// error:
		// note: adding row# and col# seems superfluxous.
		// when the err msg is displayed, row# and col# of eval will be printed
		// printing v->Text() may be useful, but it may be longish and contain ctrl codes!
		if (Error()!=errEOF) ForceError(Error(),CatStr("eval: ",ErrorStr()));
		CATCH_ERROR;
	}

	case tCD:
		ip = StrValue(ip,v,pAny);
		if (NoError()) ChangeWorkingDir ( CString(v->Text()) );
		LOOP;

	case tSWAP:
		if (*ip!=tIDF)
		{
			if (*ip==tFILE)
			{
				ip = StrValue(ip+1,v,pAny);
				ip = Expect(ip,tKOMMA);
				w=&temp_w;
				if (ip[0]==tFILE) ip++;
				ip = StrValue(ip,w,pAny);
				if (NoError()) SwapFiles(CString(v->Text()),CString(w->Text()));
				LOOP;
			}
		}

		w = &temp_w;
		ip = RefValue(ip,v,pAny);
		ip = Expect(ip,tKOMMA);
		ip = RefValue(ip,w,pAny); 	if (Error()) CATCH_ERROR;
		if (v==w) LOOP_QUICK;
		if (v->Contains(w)||w->Contains(v)) { SetError("variables must not contain each other"); CATCH_ERROR; }
		if (TrapVarInUse(v)||TrapVarInUse(w)) CATCH_ERROR;
		v->SwapData(*w);
		v->Name().Swap(w->Name());
		XXXTRAP(Error());
		LOOP_QUICK;

	case tFILE:		// file "name" = text
	{	w = &temp_w;
		cstr mode = "w";
		ip = StrValue(ip,v,pAssign);
		if (*ip==tHASHGL) { ip++; mode="a"; } else ip = Expect(ip,tGL);
		ip = StrValue(ip,w,pAny);
		if (NoError()) SaveFile(v->Text(),*w,mode);
	}	LOOP;

	case tINFO:	// INFO idf | token | @"idf|token" | "idf|token"
	{	int   i;

		mystream = stdout_stream;
		ip = GetStream(ip,mystream,1);

		if (*ip==tIDF||*ip==tAT)
		{
			ip = GetIdentifier(ip,idf); if (Error()) CATCH_ERROR;

	ii:		w = FindVar(idf);
			if (!w) { SetError( idf.ToEscaped() + " does not exist" ); CATCH_ERROR; }

			SetStream(mystream);
			PutNL(); PutString( idf.ToEscaped() + " = " + w->ToString(yes,DisassString) );
			LOOP;
		}
		else if (*ip==tSTR||*ip==tSTR2)
		{
			ip = StrValue(ip,v,pAny); if (Error()) CATCH_ERROR;
			idf = v->Text();
			for(i=tokens;--i>=0;) { if (idf==TokenName(i)) break; }
			if (i<0) goto ii;
		}
		else if( /* *ip>=tNUM&& ((note: tNUM=0)) */ *ip<=tBYTE )
		{
			SetError(expectedidentifier); CATCH_ERROR;
		}
		else if (*ip!=tEOF&&*ip!=tIF&&*ip!=tDO&&*ip!=tTRY)
		{
			i = *ip; ip=SkipToken(ip);
		}
		else
		{
			SetError(expectedstring); CATCH_ERROR;
		}
		SetStream(mystream);
		PutString( String("\n\nsyntax:\t") + TokenSyntax(i) + "\n\n" + TokenInfo(i) + "\n" );
		LOOP;
	}

	case tWAIT:		// wait seconds
	// note: sleep is preempted if a signal is sent to the process
	// not quite shure about _handled_ signals, so currently just ignored here
		ip = NumValue(ip,v,pAny); if(Error()) CATCH_ERROR;
		if( v->Value()>0.0 )
		{
			double i = v->Value(); if (i>1000000000) i=1000000000;
			double j = modf(i,&i) * 1000000000;
			timespec t;
			t.tv_sec  = long(i);
			t.tv_nsec = long(j);
			if (t.tv_nsec>999999999) t.tv_nsec=999999999;	// security only
		#if defined(_SOLARIS)
			/* hog cpu */
			/* nanosleep is declared but linker doesn't find it */
		#else
			nanosleep(&t,NULL);
		#endif
			ClearError();
		}
		LOOP_QUICK;

	case tGLOBALS:
	{
		ip=SetStream(ip,stdout_stream,0);
		PutNL();
		Var* _v;

		for ( _v=locals; _v; _v=_v->Parent() )
		{
			if (_v==globals) break;

			PutString("local data:\n");
			for (uint i=0,n=0;i<_v->ListSize();i++)
			{
				uint m = (*_v)[i].Name().Len();
				if ( ( n+=2+m ) <= 80 ) PutSpaces(2); else { n=m; PutNL(); }
				PutString((*_v)[i].Name());
			}
			PutNL();
		}

		if ( _v==proc )		// !! korrektur: == statt = richtig?
		{
			_v=_v->Parent();
			PutString("object data:\n");
			for (uint i=0,n=0;i<_v->ListSize();i++)
			{
				uint m = (*_v)[i].Name().Len();
				if(!m) continue;	// skip eof token (empty name)
				if ( ( n+=2+m ) <= 80 ) PutSpaces(2); else { n=m; PutNL(); }
				PutString((*_v)[i].Name());
			}
			PutNL();
		}

		_v = globals;
		PutString("global data:\n");
		for (uint i=0,n=0;i<_v->ListSize();i++)
		{
			uint m = (*_v)[i].Name().Len();
			if ( ( n+=2+m ) <= 80 ) PutSpaces(2); else { n=m; PutNL(); }
			PutString((*_v)[i].Name());
		}
		PutNL();

		LOOP;
	}

	case tWORDS:
		ip=SetStream(ip,stdout_stream,0);
		PutNL();PutString(SpaceString(77,'_'));

		for (uint i=tKOMMA,n=0;i<tEOF;i++)
		{
			switch(i)
			{
				case tKOMMA:	n=99; PutString("\nseparators:"); break;
				case tPLPL:		n=99; PutString("\nmonadic operators:"); break;
				case tGL:		n=99; PutString("\nassignment operators:"); break;
				case tADD:		n=99; PutString("\nother diadic operators:"); break;
				case tQMARK:	n=99; PutString("\ntriadic operators:"); break;
				case tFROM:		i=tEC;	// skip special separators 'from' and 'to'
				case tEC:		n=99; PutString("\nconstants:"); break;
				case tFILE:		n=99; PutString("\nfunctions:"); break;
				case tEVAL:		n=99; PutString("\ninstructions:"); break;
				case tPROC:		n=99; PutString("\nflow control:"); break;
			}

			uint m = TokenName(i).Len();
			if ( (n+=2+m)<=80 ) PutSpaces(2); else { n=m+4; PutNL(); PutSpaces(4); }
			PutString(TokenName(i));
		}
		PutNL();
		LOOP;

	case tPWD:		// PWD
		ip = SetStream(ip,stdout_stream,0);
		if (NoError()) PutString( WorkingDirStr() );
		LOOP;

	case tDIR:		// dir [#stream,] "path"
	{
		mystream = stdout_stream;
		ip = GetStream(ip,mystream,1);
		ip = StrValue(ip,v,pAny); if (Error()) CATCH_ERROR;
		SetStream(mystream);
		w=&temp_w;
		ReadDirectory(v->Text(),*w); if (Error()) CATCH_ERROR;
		w->Sort();
		bool root = v->Text().Len()==0;

		if (root)	// ""  =>  "root directory"
		{
			cstr hname = HostName();
			cstr uname = GetUser();
			cstr gname = GetGroup();
			cstr ctime = DateStr(CurrentTime());
			cstr btime = DateStr(BootTime());
			cstr uptim = TimeStr(UpTime());
			double sl[3]; SysLoad(sl);
			cstr sload = Using("%.2f,%.2f,%.2f",sl[0],sl[1],sl[2]);
			PutNL();
			PutNL();PutString(Using("Host: %s; User: %s(%s); SysLoad: %s", hname, uname, gname, sload));
			PutNL();PutString(Using("Time: %s; Booted: %s (%s);", ctime, btime, uptim));
			PutNL();
			PutNL();PutString("volume name                     flags      free  modified");
		}
		else	// normales directory listing
		{
			cstr path    = FullPath(CString(v->Text())); if(Error()) CATCH_ERROR;
			cstr pattern = "";
			if (ClassifyFile(path)!=s_dir) { ClearError(); pattern = FileNameFromPath(path); path = DirectoryPathFromPath(path); }
			long vFlags  = VolumeFlags(path);
			cstr vWrProt = vFlags&1 ? ", writeprotected" : "";
			cstr vEjctbl = vFlags&2 ? ", removable" : "";
		//	bool vLoaded = vFlags&4;
			cstr vFree   = SpeakingNumber( VolumeFree(path) );
			if (LastChar(path)==dirsep) path = LeftStr(path,strlen(path)-1);
			path = Using ( "%s%c%s%s", FileNameFromPath(path), dirsep, pattern, SpaceStr(40) );
			((char*)path)[42]=0;

			PutNL();
			PutNL();PutString( String( Using("Folder %s%s free%s%s", path, vFree, vWrProt, vEjctbl ), iso_latin_1 ) );
			PutNL();
			PutNL();PutString("file or folder name             flags    length  modified");
		}
			PutNL();PutString("------------------------------- ---- ----------  -------------------");

		for (uint i=0;i<w->ListSize();i++)
		{
			String s;
			Var& _v = (*w)[i];
			PutNL();
			s = _v[0].Text().ToEscaped();
			PutString(LeftString(s+SpaceString(32),32));					// name
			PutString(_v[1].Text());										// flags
			s = root ? SpeakingNumber(_v[20].Value()) : NumStr(_v[2].LongValue());
			XXXTRAP(s.Len()>10);
			PutSpaces(11-s.Len()); PutString(s);							// length (right justified)
			PutSpaces(2); PutString(DateStr(_v[4].LongValue()));			// modified
		}
		PutNL();
		LOOP;
	}

// ==== I/O streams ===========================

	case tLOG:				// put [#stream,] text [,...]
		mystream = stderr_stream;
		goto p;

	case tPUT:				// put [#stream,] text [,...]
		mystream = stdout_stream;
	p:	ip = GetStream(ip,mystream,1) -1;
		do
		{	v = &temp_v;
			ip = Value(ip+1,v,pAny);
			if (NoError())
			{
				SetStream(mystream);
				switch (v->GetType())
				{
				case isNumber:
					PutString( NumString(v->Value()) );
					break;
				case isText:
					PutString(v->Text());
					break;
				case isProc:
					PutString(DisassString(v->Proc()));
					break;
				default:	// list
					PutString ( v->ToString(no,DisassString) );
					break;
				}
			}
		}
		while (*ip==tKOMMA && NoError());
		LOOP;

	case tGET:				// get [#stream,] textvar [, ...]
		mystream = stdin_stream;
		ip = GetStream(ip,mystream,1) -1;
		do
		{	v = &temp_v;
			ip = RefValue(ip+1,v,pAny);
			SetStream(mystream);
			*v = GetString();
		}
		while (*ip==tKOMMA && NoError());
		LOOP;

	case tGETCHAR:			// getchar [#stream,] textvar [, ...]
		mystream = stdin_stream;
		ip = GetStream(ip,mystream,1) -1;
		do
		{	v = &temp_v;
			ip = RefValue(ip+1,v,pAny);
			SetStream(mystream);
			*v = GetChar();
		}
		while (*ip==tKOMMA && NoError());
		LOOP;

	case tREAD:				// read [#stream,] textvar
		mystream = stdin_stream;
		ip = GetStream(ip,mystream,1);
		ip = StrRefValue ( ip, v, pAny );
		if (NoError())
		{
			String& s = v->Text();
			s.MakeWritable();
			SetStream(mystream);
			s.Truncate( ReadBinary ( s.Text(), s.Len() ) );
		}
		LOOP;

	case tWRITE:			// write [#stream,], text
		mystream = stdout_stream;
		ip = GetStream(ip,mystream,1);
		ip = StrValue ( ip, v, pAny );
		if (NoError())
		{
			String& s = v->Text();
			SetStream(mystream);
			WriteBinary ( s.Text(), s.Len() );
		}
		LOOP;

	case tOPEN:				// open [#stream,] filename
		mystream = stdout_stream;
		ip = GetStream(ip,mystream,1);
		ip = StrValue(ip,v,pAny);
		SetStream(mystream);
		OpenStream( v->Text(), O_RDWR|O_CREAT );
		LOOP;

	case tOPENOUT:			// openout [#stream,] filename
		mystream = stdout_stream;
		ip = GetStream(ip,mystream,1);
		ip = StrValue(ip,v,pAny);
		SetStream(mystream);
		OpenStream( v->Text(), O_WRONLY|O_CREAT|O_TRUNC );
		LOOP;

	case tOPENIN:			// openin [#stream,] filename
		mystream = stdin_stream;
		ip = GetStream(ip,mystream,1);
		ip = StrValue(ip,v,pAny);
		SetStream(mystream);
		OpenStream( v->Text(), O_RDONLY );
		LOOP;

	case tCLOSE:			// close [#stream]
		ip = SetStream(ip,stdout_stream,0);
		CloseStream();
		LOOP;

	}	// switch(cmd)

// don't 'break' from the switch statement
// use LOOP, QUICK, CATCH_ERROR or EXIT
	IERR();
}





