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

	2002-04-28 kio	changed Stream io[] to Stream* io[] to allow use of subclasses of Stream
					added OpenStream() and CloseStream()
	2003-07... kio	changes to use UCS4String library
*/


#define SAFE 3
#define LOG  1

#include "config.h"
#include <iostream>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/ioctl.h>
#include <termios.h>

#include "unix/file_utilities.h"
#include "unix/os_utilities.h"
#include "var/var.h"
#include "compress/freeze.h"
#include "prefs/prefs.h"

#include "VScript.h"
#include "SystemIO.h"
#include "token.h"
#include "Stream.h"
#include "version.h"



// dummy stream with neither input nor output
// we use no_io instead of NULL for unused slots in io[]
// so that all slots in io[] can be valid streams
// eliminating the need for frequent checks for NULL
static Stream no_io;
	


/* ----	setup new VScript instance -------------------------------------
*/
VScript::VScript ( )
{
	XXXTRAP(tokens>256);
	XXXTRAP(NELEM(io)!=max_streams);
	XXXTRAP(NELEM(std_io)!=max_streams);
	XXXTRAP(no_io.IsOpenIn());
	XXXTRAP(no_io.IsOpenOut());
	ClearError();

// stream:
	std_io[0]	= stdinStream;
	std_io[1]	= stdoutStream;
	std_io[2]	= stderrStream;
	for (int i=3;i<max_streams;i++) std_io[i] = &no_io;
	memcpy ( io, std_io, sizeof(io) );
	stream		= 2;

// variables:
	globals	= new Var("globals",isList);
	locals	= globals;
	proc	= NULL;
//	end_result = 0.0;

// misc:
	run_type = rt_none;		// not yet started
	prefs 	 = NULL;

	last_color = color[0] = color[1] = color[2] = VT_normal;
}


/* ----	destructor --------------------------------------------------------------
		delete dynamically allocated memory
*/
VScript::~VScript()
{
	delete globals;
	for (int i=0;i<max_streams;i++) if (io[i]!=std_io[i]) delete io[i];
}


/* ----	select stream ---------------------------------------------------
		parse script for '#' stream identifier and set 'stream'
		if no stream identifier follows, the supplied dflt_stream is used
		optionally skip ',' after '#'
		in:		dflt_stream must be valid
		out:	'stream' always set to valid stream
		used in: 'open', 'put', 'get', 'log', 'close', 'pwd' et.al.
*/
uchar* VScript::SetStream ( uchar* ip, uint dflt_stream, bool expectkomma )
{
	XXTRAP( dflt_stream>=max_streams );
	stream = dflt_stream;

	if (*ip==tHASH)
	{
		Var temp; Var*v=&temp;
		ip = NumValue(ip+1,v,pUna);
		uint n = (uint)v->Value();
		if ( n>=max_streams )
		{ SetError( indexoutofrange, Using("maximum stream number is %i",max_streams-1) ); }
		if (expectkomma) ip = Expect(ip,tKOMMA);
		if (NoError()) stream = n;
	}

	if ( io[stream]->IsTTY() && stream<=2 &&
		 last_color!=color[stream] && run_type!=rt_cgi )
	{
		io[stream]->SetAttr((last_color=color[stream]));
	}

	return ip;
}

uchar* VScript::GetStream ( uchar* ip, uint& /*dflt*/stream, bool expectkomma )
{
	XXTRAP( stream>=max_streams );

	if (*ip==tHASH)
	{
		Var temp; Var*v=&temp;
		ip = NumValue(ip+1,v,pUna);
		uint n = (uint)v->Value();
		if ( n>=max_streams )
			SetError( indexoutofrange, Using("maximum stream number is %i",max_streams-1) );
		if (expectkomma) ip = Expect(ip,tKOMMA);
		if (NoError()) stream = n;
	}

	return ip;
}

void VScript::SetStream ( uint mystream )
{
	stream = mystream;
	if ( io[mystream]->IsTTY() && mystream<=2 &&
		 last_color!=color[mystream] && run_type!=rt_cgi )
	{
		io[mystream]->SetAttr((last_color=color[mystream]));
	}
}


/* ----	close current stream -------------------------------------
		close file, delete Stream
		restore standard stream for io[0..2]  ((0..2 are never really closed))
		note: closing a stream may fail, but the stream is deleted in any case.
			  flush output before closing if this risk is a problem.
*/	
void VScript::CloseStream ( )
{
	if(Error()) return;
	uint n = stream;
	XXTRAP(n>=max_streams);

// dont close the default stream:
	if (io[n]==std_io[n]) { SetError(streamnotopen); return; }

// close stream
	delete io[n];
	io[n]=std_io[n];
}


/* ----	open current stream ---------------------------------------
		open current stream which must be unused
*/
void VScript::OpenStream ( cString& fname, int flags )
{
	if(Error()) return;
	uint n = stream;			// speed
	XXTRAP(n>=max_streams);

// don't open an already open stream:
	if (io[n]!=std_io[n]) { SetError(streamopen); return; }

// open stream
	io[n] = new Stream(fname,flags);
	if (Error()) { PushError(); CloseStream(); PopError(); }
}


/* ----	check whether var can be deleted --------------------------------
		the following vektor variables must not be deleted or assigned a new value:
		- globals & parents
		- locals & parents
		- proc & parents
		call this test before deleting a vektor variable
		or before assigning a new value.
		raise error if variable is in use.
*/
bool VScript::TrapVarInUse ( Var* v )
{
	if (v->IsList())
	{
		if (v==globals) goto x;
		if (locals!=globals && v->Contains(locals) ) goto x;
		if ( v->Contains(proc) ) goto x;
		return no;
	}
	if (v!=proc) return no;

x:	SetError( variableinuse,String("Variable ")+ToQuoted(v->Name())+" is in use" );
	return yes;
}



void VScript::GetCGIArguments()
{	str gm,ga,gw,gn;

	if( (gm=getenv("REQUEST_METHOD")) )
	{
		if( SameStr(gm,"GET") )
		{
			ga=getenv("QUERY_STRING");
			if (ga&&*ga) ga = DupStr(ga); else ga=NULL;	// 'GET' arguments
		}
		else if ( SameStr(gm,"POST") )
		{
			ga=getenv("CONTENT_LENGTH");
			uint n=0;
			if (ga) while( _is_dec_digit(*ga) ) { n = n*10+*ga-'0'; ga++; }
			if (*ga) return;		// bogus
			if (n) { ga = QString(n); read ( 0, ga, n ); } else ga=NULL;
		}
		else
		{
			return; 		// unknown/bogus request method
		}

		run_type = rt_cgi;	// remember the fact

	#if 1
	// merge stdout and stderr => error messages go into cgi output
		if (dup2(1/*stdout*/,2/*stderr*/)<0)
		{
			str s = Using("GetCGIArguments(): dup2() failed: %s",ErrorStr());
			fputs (s,stdout);
			fputs (s,stderr);
		}
	#endif

		while(ga)
		{
			gw = ga;									// next word
			ga = strchr(ga,'&'); if (ga) *ga++ = 0;		// remaining arguments

			gn = gw;									// current argument name
			gw = strchr(gw,'='); 						// current argument value
			if (gw) *gw++ = 0; else { gw=gn; gn=""; }

			for( char c=1,*q=gw,*z=gw; c; z++,q++ )
			{
				c=*q; if(c=='+') c=' ';
				if( c=='%' && _is_hex_digit(q[1]) && _is_hex_digit(q[2]) )
				{ c = _digit_value(q[1])*16 + _digit_value(q[2]); q+=2; }
				*z=c;
			}

			globals->AppendItem(new Var(gn,gw));
		}
	}
}


static void PutPadded ( Stream*s, cstr t )
{
	String z(t);
	s->PutNL();
	s->SetAttr( VT_black, VT_yellow|VT_bright );
	s->PutString( z+SpaceString(79-z.Len()) );
	s->ClearAttr();
}

void ShowHelp(Stream*s)
{
	ClearError();
	int cols = s->GetType()==s_tty ? TerminalCols(s->GetFD()) : 80; if(cols>80) cols=80;
	XXTRAP(Error());	// errno

	s->ClearAttr();

	PutPadded( s, 		"vipsi - VIP Script Interpreter "_VERSION_" (c) 2001-2003 kio@little-bat.de" 	);
	PutPadded( s, 		"development release. please set your terminal to utf-8 and vt100." 			);
	PutPadded( s, 		"see <http://vipsi.sourceforge.net> for latest documentation." 					);
	s->PutNL();
	PutPadded( s, 		"        vipsi is waiting for your commands." 									);
	PutPadded( s, 		"        type an expression to calculate the result." 							);
	PutPadded( s, Using("        type «%s» for a list of words.",			 CString(TokenName(tWORDS))));
	PutPadded( s, Using("        type «%s \"foo\"» for info on word «foo».", CString(TokenName(tINFO)))	);
	PutPadded( s,		"        use «pd_up» and «pd_dn» for history." 									);
	PutPadded( s,		"        type ctrl-D to quit." 													);
	if (verbose)
	PutPadded( s, Using("        vipsi knows &i words.", int(tokens)) 									);
	s->PutNL();
	s->PutNL();

	if (verbose)
	{
		s->PutString("Terminal Test\n");

		s->ClearAttr();
		s->PutString("normal ");
		s->PutString(VT_Bold(" bold "));
		s->PutString(VT_Inverse(" inverse "));
		s->PutString(VT_Underline(" underline "));
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->SetBold(1); s->PutString(" bold "); s->SetBold(0);
		s->SetInverse(1); s->PutString(" inverse "); s->SetInverse(0);
		s->SetUnderline(1); s->PutString(" underline "); s->SetUnderline(0);
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->SetAttr(VT_bold); s->PutString(" bold "); s->ClearAttr();
		s->SetAttr(VT_inverse); s->PutString(" inverse "); s->ClearAttr();
		s->SetAttr(VT_underline); s->PutString(" underline "); s->ClearAttr();
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->SetAttr(VT_bold); s->PutString(" bold ");
		s->SetAttr(VT_inverse); s->PutString(" inverse ");
		s->SetAttr(VT_underline); s->PutString(" underline ");
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->PutString(VT_Bold(VT_Inverse(VT_Underline(" bold+inverse+underline "))));
		s->PutString(" normal ");
		s->PutString(VT_Bold(VT_Inverse(" bold+inverse ")));
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->PutString(VT_Inverse(VT_Underline(VT_Bold(" inverse+underline+bold "))));
		s->PutString(" normal ");
		s->PutString(VT_Underline(VT_Inverse(" underline+inverse ")));
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->PutString(VT_Underline(VT_Bold(VT_Inverse(" underline+bold+inverse "))));
		s->PutString(" normal ");
		s->PutString(VT_Bold(VT_Underline(" bold+underline ")));
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->PutString(VT_Bold(VT_FGColor(" bold+green ",VT_green)));
		s->PutString(" normal ");
		s->PutString(VT_Inverse(VT_Underline(" inverse+underline ")));
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->PutString(VT_FGColor(" red ",VT_red));
		s->PutString(" normal ");
		s->PutString(VT_FGColor(" blue ",VT_blue));
		s->PutString(VT_FGColor(" bright blue ",VT_blue|VT_bright));
		s->PutString(VT_FGColor(" blue ",VT_blue));
		s->ClearAttr();
		s->PutString("normal ");
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->SetAttr(VT_bold|VT_red|VT_bright,VT_yellow|VT_bright);
		s->PutString("bold bright red on bright yellow ");
		s->ClearAttr();
		s->PutString(" normal ");
		s->PutNL();

		s->ClearAttr();
		s->PutString("normal ");
		s->SetAttr(VT_underline|VT_bold|VT_blue,VT_white|VT_bright);
		s->PutString("bold underlined blue on bright white ");
		s->ClearAttr();
		s->PutString(" normal ");
		s->PutNL();

		s->PutNL();
	}

	if (Error()) Log("%s",ErrorStr());

	XXTRAP(Error());
}


/* ----	parse script file --------------------------
		may be called recursively to include files
		returned Var* v may be set to NULL for 'no value'
*/
void VScript::Run ( cString& filename, Var*& v )
{	ptr	 ibu = NULL;		// input file buffer
	long ilen = 0;			// input file length
	FILE* ifile = NULL;		// input file
	uint row=0,col=0;		// error position
	long type;				// file type
	cstr fname = FullPath(filename.CString());

	if (Error()) return;
	XXTRAP( run_type==rt_none);
	XXTRAP(!prefs);

// load script
	ifile = fopen(fname,"r");
	if (!ifile) { ForceError( Using( "open %s: %s", Quoted(fname), ErrorStr() ) ); goto x; }
	type = ClassifyFile(ifile);

// dir:
	if(type==s_dir)
	{
		SetError( Using( "can't include a directory: %s", Quoted(fname) ) ); goto x;
	}

// tty, pipe, unkn: read until eof
	if (type!=s_file)
	{
		if (verbose) Log( "include pipe/tty/unkn %s\n", Quoted(fname) );
		SetError( Using("can't include non-file: %s", Quoted(fname) ) ); goto x;
	}

// file:
	{
		if (verbose) Log( "include file %s\n", Quoted(fname) );
		ilen = FileLength(ifile); if (ilen==0) goto x;
		ibu  = new char[ilen+1]; if(!ibu) { SetError(outofmemory); goto x; }
		long n = fread(ibu,ilen,1,ifile);
		if (n!=1) { ForceError( Using( "read %s: %s", Quoted(fname), ErrorStr() ) ); goto x; }
		ibu[ilen]   = 0;		// end stop
		fclose(ifile); ifile=0;

	// convert line breaks to nl
		{
			ptr z = ibu;
			ptr e = ibu+ilen;
			while (z<e)
			{
				uchar c = *z++;
				if (c>13) continue;
				if (c!=0&&c!=10&&c!=13) continue;
				z[-1] = nl;
				if (c==0) continue;
			//	if (z==e) continue;			not necc.: *e=0
				if (c+*z!=23) continue;

				// DOS text file detected: cr+lf -> nl  =>  text size shrinks, text moves
				*--z=c;
				for ( ptr q=z; q<e; )
				{
					c = *q++;
					if (c!=10&&c!=13) { *z++ = c; continue; }	// move char
					q += c+*q==23; *z++ = nl;					// replace line break
				}
				e = z;
			}
			ilen = z-ibu;
		}

	// tokenize script
		Var myproc(filename,isProc);
		long idx_io=0;
		uchar* ip;
		myproc.Proc() = Tokenize ( String((cUTF8CharPtr)ibu,ilen), idx_io );

	// execute script
		if (NoError())
		{
			ip = Execute ( myproc, v );
			idx_io = ip-(uptr)myproc.Proc().Text();
			if(Error())
			{
				// note: no test for errEOF because we _are_ at script bottom level here!
				PushError();
				Tokenize ( String((cUTF8CharPtr)ibu,ilen), idx_io );	// find src position
				PopError();
			}
		}

		if(Error())
		{	cptr q=ibu,e=ibu+idx_io;
			while (q&&q<e) { row++; col=e-q; q=(cptr)memchr(q,nl,col); if(q)q++; }
			col+=1;
		}
	}

// clean up
x:	delete[] ibu;
	if (ifile) fclose(ifile);

// error ?
	if(Error())
		ForceError( Error(),
			CatStr( ErrorStr(), " at row ", NumStr(row), ", col ", NumStr(col) ) );
}


/* ---- run script -------------
		stream '0,1,2 = stdin/out/err
		sets/clears errno

		*** invocation for "script" ***
		shell script or cgi script

		returned Var* v may be set to NULL for 'no value'
*/
void VScript::RunScript ( cString& script, int argc, cstr argv[], Var*& v )
{
	XXTRAP(Error());

	run_type = rt_script;
	for ( int i=0; i<argc; i++ ) globals->AppendItem(new Var(argv[i]));
	GetCGIArguments();

	if( run_type==rt_cgi )
		prefs = new Prefs( "vipsi.cgi",    "/etc/vipsi.cgi.conf"   );
	else
	{
		CreateDir("~/.vipsi");
		prefs = new Prefs( "vipsi.script", "~/.vipsi/script.prefs" );
	}
	ReadPrefs();

	ClearError();
	Run(script,v);

	delete prefs; prefs=NULL;

// error ?
	if(Error())
	{
		cstr errstr = ErrorStr();
		PushError();

		if(run_type==rt_cgi)
		{
			str server_admin = getenv("SERVER_ADMIN");
			if ( !server_admin||strchr(server_admin,'@')==NULL )
			{
				server_admin = "please inform someone who can help.";
			}
			else
			{
				server_admin = CatStr(
					"please inform the system administrator <a href='mailto:", server_admin, "'>",
					server_admin, "</a>" );
			}
			stdoutStream->PutString( emptyString +
			"content-type: text/html; charset=utf-8\n\n"
			"<!doctype html public \"-//w3c//dtd html 4.01 strict//en\">\n"
			"<html><head><title>vipsi error message</title></head>"
			"<body style='font-family:sans-serif;font-size:14pt;padding:30px;'>"
			"<table bgcolor='#ff7766' align=center cellpadding=30 border=2 width='90%'>"
			"<tr><td align=center>"
			"<p>vipsi VIP Script Interpreter version " _VERSION_ " (c) 2001-2003 kio@little-bat.de"
			"<span style='font-size:10pt;'><br>vipsi - shell and script language: <a href='http://vipsi.sourceforge.net'>vipsi.sourceforge.net</a></span>"
			"<p>an error occured while processing the script:"
			"<p>script: " +ToQuoted(script)+
			"<br>error: " +errstr+
			"<p style='font-size:10pt;'>maybe the owner of this site is moving things around."
			"<br>if this problem persists over a prolongued time, e.g. more than an hour, "
			"<br>" +server_admin+
			"</td></tr></table></body>\n"
			);
		}
		else
		{
			stderrStream->PutNL();
			stderrStream->PutString( VT_FGColor(String("error: file ")+ToQuoted(script),VT_red) );
			stderrStream->PutNL();
			stderrStream->PutString( VT_FGColor(SpaceString(7)+errstr,VT_red) );
			stderrStream->PutNL();
		}

		PopError();
	}
}


VT_Color ReadColorPref ( Prefs* prefs, cstr idf, VT_Color result /*default*/  )
{
	XXLogIn("ReadColorPref()");

	String ps = prefs->GetStr(idf,"");

	if (ps.Len())
	{
		int i = String("ack red een low lue nta yan ite").Find(ps.RightString(3));

		if(i>=0)	// color name found ?
		{
			result = VT_black + i/4;
		//	if(ps.Find("dark")>=0) result |= VT_dark;		dark == standard
			if(ps.Find("ligh")>=0) result |= VT_bright;
		}
		else
		{
			result = VT_normal;
		}
		if(ps.Find("bold")>=0)  result |= VT_bold;
		if(ps.Find("inver")>=0) result |= VT_inverse;
		if(ps.Find("under")>=0) result |= VT_underline;
	}

	XXLogOut(" = %x",(int)result);
	return result;
}


void VScript::ReadPrefs ( )
{
	XXXTRAP(!prefs);

// color term?
	cstr s = getenv("TERM");
	bool cterm = s && strcmp(s,"xterm")==0;

	color[stdin_stream]  = ReadColorPref( prefs, "color_get", cterm ? VT_blue : VT_bold    );
	color[stdout_stream] = ReadColorPref( prefs, "color_put",         VT_normal            );
	color[stderr_stream] = ReadColorPref( prefs, "color_err", cterm ? VT_red  : VT_inverse );
}


/* ---- run in shell mode -------------
		stream '0,1,2 = stdin/out/err
		sets/clears errno

		*** invocation for "shell" ***

		creates a copy of stdin for interactive input _and_ output
		stream 0 is used for get and may be changed without affecting shell io
		stream 1 is solely used for put
		stream 2 is solely used for log
*/
void VScript::RunShell ( )
{
	XXTRAP(Error());
	run_type = rt_shell;
	CreateDir("~/.vipsi");
	prefs = new Prefs( "vipsi.shell", "~/.vipsi/shell.prefs" );
	ClearError();

// own reference to stdinStream => the user can "open#0,.."
// own copy of the stdinStream  => preserve our Stream settings
	XXXTRAP(stdinStream->GetFD()!=0);
	Stream term(0,stdinStream->GetPath());
	XXTRAP(term.GetType()!=s_tty);
	XXTRAP(!term.IsOpenIn());
	XXTRAP(!term.IsOpenOut());
	//defaults are all good:
	//term.ClearGetStoreCtls();
	//term.ResetGetStops();
	//term.SetConversion(utf8_noconv);
	//term.SetTimeoutIn(0);
	//term.SetTimeoutOut(0);

// read preferences:
	ReadPrefs();
	String prompt = prefs->GetStr("prompt","vipsi> ");
	if (prompt=="") prompt="vipsi> ";
	if (prompt[prompt.Len()-1]!=' ') prompt+=" ";

	term.PutString("\033%G");		// select UTF-8 character set
	ShowHelp(&term);
	XXXTRAP(Error());

	Var history(isList);
	Var myproc ( "shell", isProc );
	long editlastitem = 0;

// doit:
	for(;;)
	{
		ClearError();
		term.SetAttr(color[stdout_stream]);
		term.PutString(prompt);
		if (Error()) break;

		term.SetAttr(color[stdin_stream]);
		String s = term.Input(history,editlastitem);
		if (Error()) break;

		Var temp;		// scratch for Value()
		Var* v = &temp;
		long idx_io=0;
		myproc.Proc() = Tokenize ( s, idx_io );
		XXTRAP( (idx_io==0)!=(NoError()) );

		if (NoError())
		{
			term.SetAttr(color[stdout_stream]);
			last_color = color[stdout_stream];
			term.PutSpaces(2); if(Error()) break;
			uptr ip = Execute(myproc,v); //if (Error()==errEOF) ClearError();

			if (Error())
			{
				idx_io = ip-(uptr)myproc.Proc().Text();
				PushError();
				Tokenize(s,idx_io);
				PopError();
			}
		}

		term.SetAttr(color[stdout_stream]);

		if (Error())
		{
			cstr errstr = ErrorStr();
			ClearError();
			term.PutNL();
			term.SetAttr(color[stderr_stream]);
			term.PutString( String("*****  ")+errstr );
			term.SetAttr(color[stdout_stream]);
			editlastitem = idx_io?idx_io:1;
		}
		else
		{
			if (v) term.PutString( String("   = ")+v->ToString(yes,DisassString) );
			editlastitem = 0;
		}
		term.PutNL();
	}

	ClearError();
	term.ClearAttr();
	term.PutString("\nvipsi: have a nice day. ;-)\n\n");
	delete prefs; prefs=NULL;
	ClearError();
}


/* ---- quick calculate & print -------------

		*** invocation for "calc" ***

		stream '0,1,2 = stdin/out/err
		sets/clears errno
*/
void VScript::RunCalc ( cString& s )
{
	run_type = rt_calc;
	CreateDir("~/.vipsi");
	prefs = new Prefs( "vipsi.calc", "~/.vipsi/calc.prefs" );
	ClearError();
	ReadPrefs();

	Var myproc( "calc", isProc );
	long idx_io=0;
	myproc.Proc() = Tokenize ( s, idx_io );

	Var  temp;
	Var* v = &temp;

	if (NoError())
	{
		Execute(myproc,v);
		//if (Error()==errEOF) ClearError();
	}

	if (Error())
	{
		cstr errstr = ErrorStr();
		ClearError();
		stderrStream->SetAttr(color[stderr_stream]);
		last_color = color[stderr_stream];
		stderrStream->PutString( String("error: ")+errstr );
		stderrStream->SetAttr(color[stdout_stream]);
		last_color = color[stdout_stream];
		stderrStream->PutChar( nl );
	}
	else
	{
		stdoutStream->SetAttr(color[stdout_stream]);
		last_color = color[stdout_stream];
		if (v) stdoutStream->PutString( String(" = ") + v->ToString(yes,DisassString) + nl );
		else   stdoutStream->PutString( String("  ((no result))") + nl );
	}

	delete prefs; prefs=NULL;
	ClearError();
}






