// 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	3
#define	LOG 	2

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


void FreezeString ( String& v )
{
	SetError(notyetimplemented);return;		// ***TODO*** change to UCS4String

	uint qlen = v.Len();
	if (qlen<=4) return;							// compressed == uncompressed
	uptr qptr = (uptr)v.Text();

	uint zlen = (1+qlen/(1<<12)) * ((1<<12)+1);		// this _should_ be large enough for the worst case
	String z(zlen,csz1);
	uptr zptr = (uptr)z.Text();

	zlen = Freeze ( qptr, qlen, zptr, zlen );		// may set error "out of memory" for tables
	if (NoError()) v = String(zptr,zlen); 				// cleaned by operator=()
}

void MeltString ( String& v )
{
	SetError(notyetimplemented);return;		// ***TODO*** change to UCS4String

	uint qlen = v.Len();
	if (qlen<=4) return;							// compressed == uncompressed
	uptr qptr = (uptr)v.Text();

	uint zlen = MeltSize(qptr,qlen); if (Error()) return;
	String z(zlen,csz1);
	uptr zptr = (uptr)z.Text();

	zlen = Melt ( qptr, qlen, zptr, zlen );			// may set error "out of memory" for tables
	XXXTRAP(Error());
	XXXTRAP(zlen!=MeltSize(qptr,qlen));
	if (NoError()) v = String(zptr,zlen);		 		// string made clean by operator=()
}


/* ======================================================================
					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(XXXXLOG) { Log("["); Disass(stderrStream,ip,1); Log("]"); }
		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:		switch(cmd)
		{

		default:					// no instruction?  =>  assume expression & discard result
		{
			u = &temp_u;
			ip = Value(ip,u,pAcceptVoid);	// accept 'no value'; stop at pAssign
			if (u==NULL) continue;			// no result

			if (*ip<tGL || *ip>=tADD)
			{
				if (*ip!=tEOF) continue;
				ip++; goto x;				// return result: shell may print it
			}

		// assignment:
			if (u==&temp_u) SetError(expectedvarref);
			TrapVarInUse(u);
			ip0 = ip;
			ip = Value(ip+1,v,pAny);

			if(NoError()) switch (*ip0)
			{
			case tGL:		if (v!=&temp_v) *u = *v; else u->MoveData(*v); break;
			case tMIGL:		*u -= *v; break;
			case tDVGL:		*u /= *v; break;
			case tPLGL:		*u += *v; break;
			case tMUGL:		*u *= *v; break;
			case tMODGL:	*u %= *v; break;
			case tANDGL:	*u &= *v; break;
			case tORGL:		*u |= *v; break;
			case tXORGL:	*u ^= *v; break;
			case tGTGTGL:	*u >>= *v; break;
			case tLTLTGL:	*u <<= *v; break;
			case tHASHGL:	if (u->IsText()&&v->IsText()) u->Text() += v->Text();
							else						  u->AppendString(*v,DisassString);
							break;
			case tHASHHASHGL:
							if ( v!=&temp_v ) temp_v = *v;		// => no intersection; need copy anyway
							if (u->IsNoList())   u->ConvertToList();
							if (temp_v.IsList()) u->AppendList(temp_v);
							else		  	     u->AppendItem(new Var(temp_v));
							break;
			case tANDANDGL:	*u = *u && *v; break;		// pruning nimp
			case tORORGL:	*u = *u || *v; break;		// pruning nimp
			default:		NIMP();
			}
		}	continue;

		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+1,u,pAny);
			goto x; 				// return ip;

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

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

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

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

		case tUNTIL:
			ip0 = ip;
			ip  = NumValue(ip+3,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
			ip++;
			continue;

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

			ip = SetStream(ip+1,stderr_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 tGLOBALS:
		{
			ip=SetStream(ip+1,stderr_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+1,stderr_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 tBFILE:	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 tSORT:
			ip = RefValue(ip+1,v,pAny); if (Error()) goto x;
			v->Sort();
			continue;
		
		case tRSORT:
			ip = RefValue(ip+1,v,pAny); if (Error()) goto x;
			v->RSort();
			continue;
		
		case tFREEZE:
			if (ip[1]==tBFILE)
			{
				ip = StrRefValue(ip+2,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+1,v,pAny);
				if (NoError()) FreezeString(v->Text());
			}
			continue;

		case tMELT:
			if (ip[1]==tBFILE)
			{
				ip = StrRefValue(ip+2,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+1,v,pAny);
				if (NoError()) MeltString(v->Text());
			}
			continue;
		
		case tCONVERT:
		{	String s;
			ip = RefValue(ip+1,v,pAny); if (Error()) goto x;
			bool f = *ip==tFROM;
			if (!f&&*ip!=tTO) { SetError("'from' or 'to' expected"); goto x; }
			if (ip[1]==tAT)
			{
				w=&temp_w;
				ip=StrValue(ip+2,w,pUna); if (Error()) goto x;
				s = w->Text().ToLower();
			}
			else
			{
				ip = GetIdentifier(ip+1,s); if (Error()) break;
				s = s.ToLower();
			//	ip=Expect(ip+1,tIDF); if (Error()) goto x;
			//	CharSize csz = *ip++;
			//	uchar chars  = *ip++;
			//	s = String(proc->Proc(),ip,chars,csz).ToLower();
			//	ip += chars*csz;
			}
			CharEncoding what;

			if (s=="quoted") 		what=quoted; else
			if (s=="escaped")	 	what=escaped; else
			if (s=="printable") 	what=escaped; else
			if (s=="html") 			what=html; else
			if (s=="utf-8")			what=utf8; else
			if (s=="utf8")			what=utf8; else

			if (LeftString(s,3)=="iso")
			{
				int n = _x_(*(ushort*)RightString(s,2).Text());
				if (n==M2('_','1')) what=iso_latin_1; else
	//			if (n==M2('_','2')) what=iso_latin_2; else
				goto xcs;
			} 	else

			if (LeftString(s,3)=="mac")
			{
				String t=MidString(s,4);
				if (t=="roman")		what=mac_roman; else goto xcs;
			}	else

			if (s=="german") 		what=ascii_ger; else
			if (s=="ascii_ger") 	what=ascii_ger; else
			if (s=="us") 			what=ascii_us; else
			if (s=="ascii_us")		what=ascii_us; else
			if (s=="atari") 		what=atari_st; else
			if (s=="rtos")			what=rtos; else
			if (s=="dos") 			what=cp_437; else
			if (s=="cp_437")		what=cp_437; else
			if (s=="amiga") 		what=iso_latin_1; else
			if (s=="windows") 		what=iso_latin_1; else
			if (s=="archimedes") 	what=iso_latin_1; else

			{ xcs: SetError( String("conversion of ") + s + " not yet implemented." ); goto x; }

			if (f) v->ConvertFrom(what); else v->ConvertTo(what);
			continue;
		}
		
		case tDEL:
			if (ip[1]!=tIDF)
			{
				if (ip[1]==tMUL)	// DEL *		delete all variables in local context
				{
					ip+=2;
					locals->SetList();
					continue;
				}
				else 
				if (ip[1]==tBFILE||ip[1]==tTFILE||ip[1]==tDFILE||ip[1]==tDIR)
				{
				// note: MacOS classic Version of vipsi distinguishs between File and Dir
					ip = StrValue(ip+2,v,pAny);
					if (NoError()) DeleteFile(CString(v->Text()));
					continue;
				}
			}
			// DEL refvar
			ip = RefValue(ip+1,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[1]!=tIDF)
			{
				if (ip[1]==tBFILE||ip[1]==tTFILE||ip[1]==tDFILE)
				{
					ip = StrValue(ip+2,v,pAny); if (Error()) goto x;
					CreateFile(CString(v->Text()));
					continue;
				}
				if (ip[1]==tDIR)
				{
					ip = StrValue(ip+2,v,pAny); if (Error()) goto x;
					CreateDir(CString(v->Text()));
					continue;
				}
			}
		// else: resume like VAR
				
		case tVAR:	// VAR identifier [ = value ]
			w = locals;

			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[1]!=tIDF)
			{
				if (ip[1]==tDIR||ip[1]==tBFILE||ip[1]==tTFILE||ip[1]==tDFILE)
				{
					w=&temp_w;
					ip = StrValue(ip+2,v,pAssign);
					ip = Expect(ip,tGL);
					ip = StrValue(ip,w,pAny); 
					if (NoError()) RenameFileOrFolder(v->Text(),w->Text());
					continue;
				}
			}
		
			ip = RefValue(ip+1,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();
		#if 0
			if (IsLetter(idf[1]))
			{
				char*c=idf+1;
				while(*c) if (!IsLetter(*c)||!IsDigit(*c)) break;
				if ((*c==0)) 
				{
					v->SetName(idf);				// no test for unique name. allow doubles.
					continue;
				}
			}
			SetError("identifiers must only contain letters and digits");
			continue;
		#else
			v->SetName(idf);				// no test for unique name. no test for valid name.
			continue;
		#endif
						
		case tPROC:	// Proc identifier ( [identifier] [,identifier] [,*] ) = { instructions }
		{
			uchar* pip;	// ip -> proc_start
			ip = GetIdentifier(ip+1,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 ... }
			ip++;
			w = new Var("privates",isList); locals->AppendItem(w); locals = w;
			continue;

		case tINCLUDE:
			ip = StrValue(ip+1,v,pAny);
			if (NoError()) Run(v->Text(),v);
			continue;

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

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

		case tDIR:		// dir [#stream,] "path"
		{
			ip = SetStream(ip+1,stderr_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;
		}	

		case tSWAP:
			if (ip[1]!=tIDF)
			{
				if (ip[1]==tBFILE||ip[1]==tTFILE||ip[1]==tDFILE)
				{
					ip = StrValue(ip+2,v,pAny);
					ip = Expect(ip,tKOMMA);
					w=&temp_w;
					if (ip[0]==tBFILE||ip[0]==tTFILE||ip[0]==tDFILE) ip++;
					ip = StrValue(ip,w,pAny);
					if (NoError()) SwapFiles(CString(v->Text()),CString(w->Text()));
					continue;
				}
			}
			
			w = &temp_w;
			ip = RefValue(ip+1,v,pAny);
			ip = Expect(ip,tKOMMA);
			ip = RefValue(ip,w,pAny); 	if (Error()) goto x;
			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;
					
// ==== I/O streams ===========================

		case tLOG:			// Log expr [,expr[...]]
			stream = stderr_stream;
			goto p;

		case tPUT:				// PUT [#stream,] value [,...]
			ip = SetStream(ip+1,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(String("proc ")+v->Name()+" ");
						Disass(io[stream],v->ProcAddr(),v->ProcSize());
						break;
					default:	// list
						PutString ( v->ToString(no,DisassString) );
						break;
					}
				}
			}
			while (*ip==tKOMMA && NoError());
			continue;

		case tGET:				// GET [#stream,] refvar
			ip = SetStream(ip+1,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+1,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+1,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+1,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+1,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+1,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+1,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+1,stdout_stream,0);
			CloseStream();
			continue;
		
		}	// switch(cmd)

	}	// while(NoError())

x:	proc = oldProc;
	return ip;
}





