// 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 	2

#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 german  asciige macroma "
				   "us      asciius atari   atarist rtos    dos     cp437   amiga   windows archime "
				   "ucs1    ucs2    ucs4    upper   lower   ").Find(s.LeftString(7));
	if (n>=0)
	{
		const CharEncoding e[] =
				{ quoted, escaped, escaped, html, utf8, uppercase,lowercase,ascii_ger,ascii_ger,mac_roman,
				  ascii_us,ascii_us, atari_st,atari_st, rtos, cp_437, cp_437, iso_latin_1,iso_latin_1,iso_latin_1,
				  ucs1,ucs2,ucs4,uppercase,lowercase};
		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;
			}
	}

	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 if cmd==tKOMMA
	String	idf;

	while (NoError())
	{
		if(XXXLOG) { Log("["); Disass(stderrStream,ip,1); Log("]\n"); }
		XXXTRAP(ip>=(uchar*)myProc.Proc().Text()+myProc.Proc().Len());
		if(XXXXSAFE) globals->Validate();

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

a:		XXXXTRAP(v!=&temp_v);
		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) continue;
			goto x;							// return result: shell may print it

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

		case tRETURN:				// exit and return value
			u = &temp_u;
			ip = Value(ip,u,pAny);
			goto x; 				// return ip;

		case tGKzu:				 	// end of local context
			w = locals->Parent();
			if (w) { delete locals; locals = w; continue; }
			SetError(errGKzu);
			// resume like END:

		case tEND:					// exit and return no value
			u = NULL;				// return value: no result
			goto x;					// return ip;

		case tEOF:					// physical end of file
			u = NULL;				// return value: no result
			SetError(errEOF);		// raise error
			goto x;

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

		case tUNTIL:
			ip0 = ip;
			ip  = NumValue(ip+2,v,pAny);
			if (Error()) goto x;
			if ( v->Value() ) ip = ip0 + *(short*)(ip0) -1;
			continue;

		case tELSE:
		case tLOOP:
		case tEXIT:
		case tNEXT:
			ip += *(short*)(ip) -1;
			continue;

		case tSEMIK:	// ";" == NOP
		case tTHEN:	// IF .. THEN
		case tDO:	// DO .. LOOP
			continue;

		case tSORT:
			ip = RefValue(ip,v,pAny); if (Error()) goto x;
			v->Sort();
			continue;

		case tRSORT:
			ip = RefValue(ip,v,pAny); if (Error()) goto x;
			v->RSort();
			continue;

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

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

		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();
			}
		}	continue;

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

			ip = GetIdentifier(ip+1,idf);
		//	Log(" encoding = %s ",CString(idf.ToQuoted()));
			CharEncoding what = GetEncodingFromName(idf);
		//	Log(" encoding = %i ",(int)what);
			if (NoError()) { if (f) v->ConvertFrom(what); else v->ConvertTo(what); }
			continue;
		}

		case tDEL:
			if (*ip!=tIDF)
			{
				if (*ip==tMUL)	// DEL *		delete all variables in local context
				{
					ip++;
					locals->SetList();
					continue;
				}
				else
				if (*ip==tFILE/*||ip[1]==tTFILE||ip[1]==tDFILE*/||ip[1]==tDIR)
				{
				// note: MacOS classic Version of vipsi distinguishs between File and Dir
					ip = StrValue(ip+1,v,pAny);
					if (NoError()) DeleteFile(CString(v->Text()));
					continue;
				}
			}
			// DEL refvar
			ip = RefValue(ip,v,pAny); if (Error()) goto x;
			if (!TrapVarInUse(v))
			{
				XXXTRAP(v->Parent()==NULL);
				XXXTRAP(!v->Parent()->IsList());
				v->Parent()->DeleteItem(v->Index());
			}
			continue; 			

		case tNEW:	// NEW identifier [ = value ]
					// NEW File "path"
					// NEW Dir  "path"
			if (*ip!=tIDF)
			{
				if (*ip==tFILE/*||ip[1]==tTFILE||ip[1]==tDFILE*/)
				{
					ip = StrValue(ip+1,v,pAny); if (Error()) goto x;
					CreateFile(CString(v->Text()));
					continue;
				}
				if (*ip==tDIR)
				{
					ip = StrValue(ip+1,v,pAny); if (Error()) goto x;
					CreateDir(CString(v->Text()));
					continue;
				}
			}
		// 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()) goto x;

				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 vektor 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()) goto x;
						w = &(*w)[n-1];
					}
					while (*ip==tKOMMA);
					ip=Expect(ip,tEKzu); if (Error()) goto x;
				}
			}
			while (*ip==tDOT);

			if (*ip!=tGL) continue;

			XXXTRAP(v==&temp_v);
			if (TrapVarInUse(v)) break;
			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
			continue;

		case tSETNAME:
			if (*ip!=tIDF)
			{
				if (*ip==tDIR||*ip==tFILE/*||ip[1]==tTFILE||ip[1]==tDFILE*/)
				{
					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());
					continue;
				}
			}

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

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

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

			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
			continue;
		}

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

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

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

			while (NoError() && *ip==tKOMMA)
			{
				w = a = &args[n++];	// cmd
				ip = Value(ip+1,w,pAny);
				if(w->IsNoText()) *a = w->ToString(no,DisassString);
				else if (w!=a) *a = *w;
			}

			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;

				str result = ExecCmd(argv);
				PutNL();
				PutString(result);
				delete[] result;
				for (int i=0;i<n;i++) delete[] argv[i];
				delete[] argv;
			}
			continue;
		}

		case tINCLUDE:	// include filename
			ip = StrValue(ip,v,pAny);
			if (Error()) break;
			u = &temp_u;
			Run(v->Text(),u);			// note: run() displays error message
			v = &temp_v;
			if (Error()) ForceError(Error(),String("script ")+ToQuoted(v->Text())+" returned with error");
			if (*ip!=tEOF) continue;
			goto x;							// 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);					// note: run() displays error message
				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 with error");
			if (*ip!=tEOF) continue;
			goto x;							// return result: shell may print it
		}

		case tEVAL:		// eval textvalue
		{
			ip = StrValue(ip,v,pAny);
			if (Error()) break;
			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 );
				// note: maybe we should check that mip has reached the end of proc?
				if (NoError())
				{
					if (*ip!=tEOF) continue;
					goto x;							// 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!
			ForceError(Error(),CatStr("eval: ",ErrorStr()));
			break;
		}

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

		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()));
					continue;
				}
			}
			
			w = &temp_w;
			ip = RefValue(ip,v,pAny);
			ip = Expect(ip,tKOMMA);
			ip = RefValue(ip,w,pAny); 	if (Error()) goto x;
			if (v==w) continue;
			if (v->Contains(w)||w->Contains(v)) { SetError("variables must not contain each other"); goto x; }
			if (TrapVarInUse(v)||TrapVarInUse(w)) goto x;
			v->SwapData(*w);
			v->Name().Swap(w->Name());		
			continue;

		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);
		}	continue;

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

			ip = SetStream(ip,stdout_stream,1);

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

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

				PutNL(); PutString( idf.ToEscaped() + " = " + w->ToString(yes,DisassString) );
				break;

/*				if (w->IsNumber())
				{
					PutString( String("\n'")+idf+"' is a numeric variable" ); continue;
				}
				if (w->IsText())
				{
					PutString( String("\n'")+idf+"' is a text variable. length = "+NumString(w->Text().Len()) ); continue;
				}
				if (w->IsList())
				{
					PutString( String("\n'")+idf+"' is a list variable. item count = "+NumString(w->ListSize()) ); continue;
				}
				if (w->IsProc())
				{
					PutString ( String("\n")+TokenName(tPROC)+" "+idf+" " );
					uchar* a = w->ProcAddr(); XXXTRAP(*a!=tRKauf);
					uchar* e = SkipBrackets(a);
					Disass(io[stream],a, e-a);
					continue;
				}
				SetError("internal error");
				break;
*/			}
			else if (*ip==tSTR||*ip==tSTR2)
			{
				ip = StrValue(ip,v,pAny); if (Error()) goto x;
				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); goto x;
			}
			else if (*ip!=tEOF)
			{
				i = *ip; ip=SkipToken(ip);
			}
			else
			{
				SetError(expectedstring); goto x;
			}
			PutString( String("\n\nsyntax:\t") + TokenSyntax(i) + "\n\n" + TokenInfo(i) + "\n" );
			continue;
		}

		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( NoError() && 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
				nanosleep(&t,NULL);
				ClearError();
			}
		}	continue;

		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();

			continue;
		}

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

			for (uint i=tKOMMA,n=0;i<tKOMM;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 tNEW:		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();
			continue;

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

		case tDIR:		// dir [#stream,] "path"
		{
			ip = SetStream(ip,stdout_stream,1);
			ip = StrValue(ip,v,pAny); if (Error()) break;
			w=&temp_w;
			ReadDirectory(v->Text(),*w); if (Error()) goto x;
			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()));
				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(Using("Folder %s%s free%s%s", path, vFree, vWrProt, vEjctbl ));
				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(); 
			w->SetNumber();
			continue;
		}	

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

		case tLOG:			// Log expr [,expr[...]]
			ip = SetStream(ip,stderr_stream,1) -1;
			goto p;

		case tPUT:				// PUT [#stream,] value [,...]
			ip = SetStream(ip,stdout_stream,1) -1;
		p:	do
			{	v = &temp_v;
				ip = Value(ip+1,v,pAny);
				if (NoError())
				{
					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());
			continue;

		case tGET:				// GET [#stream,] refvar
			ip = SetStream(ip,stdin_stream,1) -1;
			do
			{	v = &temp_v; 
				ip = RefValue(ip+1,v,pAny);
				if (NoError()) 
				{
					switch(v->GetType())
					{
					case isText:
						v->Text()  = GetString();
						break;
					case isNumber:
						v->Value() = NumVal(GetString());
						break;
					case isList:
					case isProc:
					default:
						SetError("invalid variable type");
						break;
					}
				}
			}
			while (*ip==tKOMMA && NoError());
			continue;

		case tGETCHAR:
			ip = SetStream(ip,stdin_stream,1) -1;
			do
			{	v = &temp_v; 
				ip = RefValue(ip+1,v,pAny);
				if (NoError()) 
				{
					switch(v->GetType())
					{
					case isText:
						*v = GetChar();
						break;
					case isNumber:
						*v = GetChar();
						break;
					default: 
						SetError("invalid variable type");
						break;
					}
				}
			}
			while (*ip==tKOMMA && NoError());
			continue;
			
		case tREAD:
			ip = SetStream(ip,stdin_stream,1);
			ip = StrRefValue ( ip, v, pAny );
			if (NoError())
			{
				String& s = v->Text(); 
				s.MakeWritable(); 
				s.Truncate( ReadBinary ( s.Text(), s.Len() ) );
			}
			continue;
			
		case tWRITE:
			ip = SetStream(ip,stdout_stream,1);
			ip = StrValue ( ip, v, pAny );
			if (NoError())
			{
				String& s = v->Text(); 
				WriteBinary ( s.Text(), s.Len() );
			}
			continue;
			
		case tOPEN:			// Open [#stream,] filename
		{	ip = SetStream(ip,stdout_stream,1);
			ip = StrValue(ip,v,pAny);
			cstr path = FullPath(CString(v->Text()));
			if (Error()) { if (Error()==ENOENT) ClearError(); else break; }
			OpenStream( path, O_RDWR|O_CREAT );
		}	continue;

		case tOPENOUT:		// Openout [#stream,] filename
		{	ip = SetStream(ip,stdout_stream,1);
			ip = StrValue(ip,v,pAny);
			cstr path = FullPath(CString(v->Text()));
			if (Error()) { if (Error()==ENOENT) ClearError(); else break; }
			OpenStream( path, O_WRONLY|O_CREAT|O_TRUNC );
		}	continue;

		case tOPENIN:		// Openin [#stream,] filename
		{	ip = SetStream(ip,stdin_stream,1);
			ip = StrValue(ip,v,pAny);
			cstr path = FullPath(CString(v->Text()));
			if (Error()) break;
			OpenStream( path, O_RDONLY );
		}	continue;

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

		}	// switch(cmd)

	}	// while(NoError())

x:	proc = oldProc;
	return ip;
}





