


function JSLogo( e, width, height )
{
	this.e       = e;
	this.ctxt    = e.getContext( '2d' );
	this.height  = height;
	this.turtles = { t1: new Turtle( 't1', Math.floor( width / 2 ), Math.floor( height / 2 ) ) };
	this.width   = width;

	this.turtle = this.turtles.t1;
	this.s0     = {
		bg: { r: 0, g: 0, b: 0 },
		fg: { r: 255, g: 255, b: 255 },
		h:  90,
		p:  true,
		w:  1,
		x:  Math.floor( width / 2 ),
		y:  Math.floor( height / 2 )
	};

	this.C = new LogoCommands( this );
	this.P = new LogoParser( this.C );

	this.vars = [{}];

	this.ctxt.strokeStyle = 'white';



	JSLogo.prototype.addContext = function ( a, v )
	{
		if ( this.vars.length > 500 )
		{
			alert( 'Recursion has exceeded 500 levels; terminating.' );
			throw( 'ERR_RECURSION' );
		}

		var vars = {};

		for ( var i = 0; i < a.length; ++i )
		{
			vars[a[i]] = v[i];
		}

		this.vars.push( vars );
	};

	JSLogo.prototype.dropContext = function ()
	{
		this.vars.pop();
	};

	JSLogo.prototype.evaluate = function ( a, context )
	{
		var b = Array( a.length );
		for ( var i = 0; i < a.length; ++i )
		{
			b[i] = a[i].evaluate( context );
		}
		return [ b ];
	};

	JSLogo.prototype.nullTurtle = function ( t )
	{
		return t == undefined || t == null ? this.turtle : t;
	};

	JSLogo.prototype.parse = function ( code )
	{
		return this.P.parse( code );
	};

	JSLogo.prototype.parseAndRun = function ( code )
	{
		return this.run( this.parse( code ) );
	};

	JSLogo.prototype.resetBackground = function ()
	{
		this.setBackground( this.s0.bg.r, this.s0.bg.g, this.s0.bg.b );
	};
	
	JSLogo.prototype.run = function ( cs )
	{
		var rv;

		for ( var i = 0; i < cs.length; ++i )
		{
			rv = cs[i].c.apply( this.C, this.evaluate( cs[i].a, this ) );
		}

		return rv;
	};

	JSLogo.prototype.setBackground = function ( r, g, b )
	{
		this.s0.bg = { r: r, g: g, b: b };
		var op     = this.ctxt.globalCompositeOperation;
		this.ctxt.globalCompositeOperation = 'destination-over';
		this.ctxt.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',1.0)';
		this.ctxt.fillRect( 0, 0, this.width, this.height );
		this.ctxt.globalCompositeOperation = op;
	};

	JSLogo.prototype.setTurtle = function ( t )
	{
		if ( ( typeof t ).toLowerCase() == 'string' )
		{
			this.turtle = this.turtles[t];
		}
		else
		{
			this.turtle = t;
		}
	};

	JSLogo.prototype.toPNG = function ()
	{
		document.getElementById( 'pngescape' ).value = this.e.toDataURL( 'image/png' );
		document.getElementById( 'savepng' ).submit();
	};

	JSLogo.prototype.turtleClean = function ( t )
	{
		t = this.nullTurtle( t );
		t.setPosition( this.s0.x, this.s0.y );
		t.setHeading( this.s0.h );
	};

	JSLogo.prototype.turtleColor = function ( r, g, b, t )
	{
		this.nullTurtle( t ).setFG( r, g, b );
	};

	JSLogo.prototype.turtleDown = function ( t )
	{
		this.nullTurtle( t ).penDown();
	};

	JSLogo.prototype.turtleErase = function ( t )
	{
		this.nullTurtle( t ).penErase();
	};

	JSLogo.prototype.turtleForward = function ( d, t )
	{
		t = this.nullTurtle( t );

		x1 = t.getX() + d * Math.cos( t.getHeading() * Math.PI / 180 );
		y1 = t.getY() - d * Math.sin( t.getHeading() * Math.PI / 180 );

		if ( t.getPD() != 0 )
		{
			this.ctxt.strokeStyle = t.getPC();
			this.ctxt.lineWidth   = t.getPenSize();
			this.ctxt.beginPath();
			this.ctxt.moveTo( t.getX(), t.getY() );
			this.ctxt.lineTo( x1, y1 );
			this.ctxt.stroke();
		}

		t.setPosition( x1, y1 );
	};

	JSLogo.prototype.turtleHeading = function ( h, t )
	{
		this.nullTurtle( t ).setHeading( h );
	};

	JSLogo.prototype.turtleLeft = function ( dh, t )
	{
		this.nullTurtle( t ).turnLeft( dh );
	};

	JSLogo.prototype.turtleNew = function ( n )
	{
		if ( n in this.turtles )
		{
			throw( 'ERR_TURTLE_EXISTS' );
		}

		this.turtles[n] = new Turtle( n, this.s0.x, this.s0.y );
		this.turtle     = this.turtles[n];
	};

	JSLogo.prototype.turtlePenSize = function ( t )
	{
		return this.nullTurtle( t ).getPenSize();
	};

	JSLogo.prototype.turtlePosition = function ( t )
	{
		t = this.nullTurtle( t );
		return [ t.getX() - this.s0.x, t.getY() - this.s0.y ];
	};

	JSLogo.prototype.turtleSetPenSize = function ( w, t )
	{
		this.nullTurtle( t ).setPenSize( w );
	};

	JSLogo.prototype.turtleSetPosition = function ( x, y, t )
	{
		x = x != null ? x + this.s0.x : null;
		y = y != null ? y + this.s0.y : null;
		this.nullTurtle( t ).setPosition( x, y );
	};

	JSLogo.prototype.turtleTowards = function ( n )
	{
		var t  = this.turtles[n];
		var dx = t.getX() - this.turtle.getX();
		var dy = t.getY() - this.turtle.getY();

		if ( dx == 0 )
		{
			this.turtle.setHeading( dy < 0 ? -90 : 90 );
		}
		else
		{
			this.turtle.setHeading( Math.arctan( dy / dx ) );
		}
	};

	JSLogo.prototype.turtleUp = function ( t )
	{
		this.nullTurtle( t ).penUp();
	};

	JSLogo.prototype.variableClear = function ()
	{
		this.vars[ this.vars.length - 1 ] = {};
	};

	JSLogo.prototype.variableDelete = function ( a )
	{
		for ( var i = this.vars.length - 1; i >= 0; --i )
		{
			if ( a in this.vars[i] )
			{
				delete( this.vars[i][a] );
				return;
			}
		}

		alert( 'Cannot find variable "' + a + '" on stack to delete.' );
		throw( 'ERR_VARIABLE_UNDEFINED' );
	};

	JSLogo.prototype.variableGet = function ( a )
	{
		for ( var i = this.vars.length - 1; i >= 0; --i )
		{
			if ( a in this.vars[i] )
			{
				return this.vars[i][a];
			}
		}

		alert( 'Cannot find variable "' + a + '" on stack.' );
		throw( 'ERR_VARIABLE_UNDEFINED' );
	};

	JSLogo.prototype.variableSet = function ( a, v )
	{
		this.vars[ 0 ][a] = v;
	};

	this.setBackground( 0, 0, 0 );
}



function LogoCommands ( L )
{
	this.fns = {};
	this.L   = L;

	LogoCommands.prototype.answer    = function ( a ) { throw( 'INTERACTION NOT IMPLEMENTED' ); };
	LogoCommands.prototype.ask       = function ( a ) { throw( 'MULTITURTLE NOT IMPLEMENTED' ); };
	LogoCommands.prototype.bg        = function ( a ) { throw( 'COLORS NOT IMPLEMENTED' ); };
	LogoCommands.prototype.bottom    = function ( a ) { throw( 'TEXT NOT IMPLEMENTED' ); };
	LogoCommands.prototype.broadcast = function ( a ) { throw( 'MULTITURTLE NOT IMPLEMENTED' ); };
	LogoCommands.prototype.butfirst  = function ( a ) { throw( 'NOT IMPLEMENTED' ); }; // this is basically SHIFT
	LogoCommands.prototype.butlast   = function ( a ) { throw( 'NOT IMPLEMENTED' ); };  // this is basically POP
	LogoCommands.prototype.cancel    = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.cb        = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.cc        = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.cd        = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.cf        = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.chdir     = function ( a ) { throw( 'FILESYSTEM NOT IMPLEMENTED' ); };
	LogoCommands.prototype.cleartext = function ( a ) { throw( 'TEXT NOT IMPLEMENTED' ); };
	LogoCommands.prototype.clickoff  = function ( a ) { throw( 'INTERACTION NOT IMPLEMENTED' ); };
	LogoCommands.prototype.clickon   = function ( a ) { throw( 'INTERACTION NOT IMPLEMENTED' ); };
	LogoCommands.prototype.clipboard = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.clone     = function ( a ) { throw( 'MULTITURTLE NOT IMPLEMENTED' ); };
	LogoCommands.prototype.closeworksheet = function ( a ) { throw( 'EXCEL NOT IMPLEMENTED' ); };
	LogoCommands.prototype.color = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.colorunder = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.copy = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.count = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.createprojectvar = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.cu = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.currentdir = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.cut = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.delete = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.directories = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.distance = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.dolist = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.done = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.dotimes = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.empty = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.eol = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.eot = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.erfile = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.errormessage = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.exporttext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.exportturtle = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.files = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.fill = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.fontsize = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.forever = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.forward = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.found = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.freeze = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.freezebg = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.get = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.getcell = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.getlabel = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.getpage = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.getproject = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.giveturtle = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.glide = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.heading = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.hidetext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.ht = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.identical = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.importtext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.importturtle = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.inback = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.infront = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.insert = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.key = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.launch = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.let = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.list = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.listen = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.loadpict = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.loadshape = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.loadtext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.local = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.lput = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.member = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.merge = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.message = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.mousepos = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.namepage = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.names = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newbutton = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newcheckbox = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newdropdown = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newlistbox = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newpage = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newprojectsize = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newroundbuttonset = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newslider = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.newtext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.nextpage = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.note = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.onreadline = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.opacity = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.opaque = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.openworksheet = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.output = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.pagelist = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.parse = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.paste = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.pictlist = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.placepict = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.presentationmode = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.prevpage = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.print = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.projectlist = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.projectsize = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.projectvars = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.question = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.readchar = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.recycle = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.remainder = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.remove = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.rename = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.rerandom = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.resetdialog = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.resett = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	// LogoCommands.prototype.resetvideo = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	// LogoCommands.prototype.rest = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.restore = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.run = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.saveproject = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.saveshape = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.savetext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.search = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.select = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.selected = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.sender = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.sentence = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.set = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setbg = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	// LogoCommands.prototype.setcell = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setfont = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setfontsize = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setfooter = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setinstruction = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	// LogoCommands.prototype.setinstrument = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setopacity = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setrotate = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setshape = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setsize = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.setstyle = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.settc = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.shape = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.show = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.showtext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.size = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.snaparea = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.snapshape = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.snapshot = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.sol = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.space = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.st = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.stamp = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.stamptext = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.startup = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.stop = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.stopall = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.stopme = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.talkto = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.tc = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.tell = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.textcount = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.textitem = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.textlist = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.textpick = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.textwho = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.thing = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.timer = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.top = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.touchedturtle = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.touching = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.transparent = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.turtlesown = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.unfreeze = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.unfreezebg = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.unselect = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.wait = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.waituntil = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.wallpaper = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.when = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.who = function ( a ) { throw( 'NOT IMPLEMENTED' ); };
	LogoCommands.prototype.word = function ( a ) { throw( 'NOT IMPLEMENTED' ); };



	LogoCommands.prototype.addFunction = function ( f )
	{
		this.fns[ f.v ] = f;
	}

	LogoCommands.prototype.and = function ( a )
	{
		var b = true;
		for ( var i = 0; i < a.length; ++i )
		{
			b = b && a[i];
		}

		return b;
	};

	LogoCommands.prototype.announce = function ( a )
	{
		alert( a[0] );
	};

	LogoCommands.prototype.abs = function ( a )
	{
		return Math.abs( parseFloat( a[0] ) );
	};
	
	LogoCommands.prototype.arccos = function ( a )
	{
		return Math.acos( parseFloat( a[0] ) );
	};
	
	LogoCommands.prototype.arcsin = function ( a )
	{
		return Math.asin( parseFloat( a[0] ) );
	};

	LogoCommands.prototype.arctan = function ( a )
	{
		return Math.atan( parseFloat( a[0] ) );
	};

	LogoCommands.prototype.ascii = function ( a )
	{
		return a[0].charCodeAt( 0 );
	};

	LogoCommands.prototype.back = function ( a )
	{
		this.forward( [ -parseFloat( a[0] ) ] );
	};

	LogoCommands.prototype.bk = function ( a )
	{
		this.back( a );
	};

	LogoCommands.prototype.carefully = function ( a )
	{
		try
		{
			this.L.run( a[0] );
		}
		catch ( e )
		{
			this.L.run( a[1] );
		}
	};

	LogoCommands.prototype.cg = function ( a )
	{
		this.clean();
		this.L.turtleClean();
	};

	LogoCommands.prototype.char = function ( a )
	{
		return String.fromCharCode( a[0] );
	};

	LogoCommands.prototype.clean = function ( a )
	{
		this.L.e.width--;
		this.L.e.width++;

		this.L.resetBackground();
	};

	LogoCommands.prototype.clearname = function ( a )
	{
		this.L.variableDelete( a[0] );
	};

	LogoCommands.prototype.clearnames = function ( a )
	{
		this.L.variableClear();
	};

	LogoCommands.prototype.cos = function ( a )
	{
		return Math.cos( a[0] );
	};

	LogoCommands.prototype.difference = function ( a )
	{
		return a[0] - a[1];
	};

	LogoCommands.prototype.equal = function ( a )
	{
		switch ( ( typeof a[0] ).toLowerCase() )
		{
			case 'number': return a[0] == a[1];
			case 'string': return a[0].toLowerCase() == a[1].toLowerCase();
		}

		if ( a[0].length != a[1].length )
		{
			return false;
		}

		for ( var i = 0; i < a[0].length; ++i )
		{
			if ( !this.equal( a[0][i], a[1][i] ) )
			{
				return false;
			}
		}

		return true;
	};

	LogoCommands.prototype.everyone = function ( a )
	{
		var t0 = this.L.turtle;
		for ( var t in this.L.turtles )
		{
			this.L.setTurtle( this.L.turtles[t] );
			this.L.run( a[0] );
		}
		this.L.setTurtle( t0 );
	};

	LogoCommands.prototype.exp = function ( a )
	{
		return Math.exp( a[0] );
	};

	LogoCommands.prototype.fd = function ( a )
	{
		this.forward( a );
	};

	LogoCommands.prototype.first = function ( a )
	{
		return a[0][0];
	};
	
	LogoCommands.prototype.forward = function ( a )
	{
		this.L.turtleForward( a[0] );
	};

	LogoCommands.prototype.fput = function ( a )
	{
		var b = a[1];
		b.unshift( a[0] );
		return b;
	};

	LogoCommands.prototype.greater = function ( a )
	{
		return a[0] > a[1];
	};

	LogoCommands.prototype.home = function ( a )
	{
		this.L.turtleSetPosition( 0, 0 );
	};

	LogoCommands.prototype.if = function ( a )
	{
		if ( a[0] )
		{
			this.L.run( a[1] );
		}
	};

	LogoCommands.prototype.ifelse = function ( a )
	{
		if ( a[0] )
		{
			this.L.run( a[1] );
		}
		else
		{
			this.L.run( a[2] );
		}
	};

	LogoCommands.prototype.int = function ( a )
	{
		return parseInt( a[0] );
	};

	LogoCommands.prototype.item = function ( a )
	{
		return a[1][a[0]-1];
	};

	LogoCommands.prototype.last = function ( a )
	{
		return a[0][a[0].length-1];
	};

	LogoCommands.prototype.left = function ( a )
	{
		this.L.turtleLeft( a[0] );
	};

	LogoCommands.prototype.less = function ( a )
	{
		return a[1] < a[0];
	};

	LogoCommands.prototype.ln = function ( a )
	{
		return Math.log( a[0] );
	};

	LogoCommands.prototype.log = function ( a )
	{
		return Math.log( a[0] ) / Math.log( 10 );
	};

	LogoCommands.prototype.lt = function ( a )
	{
		this.left( a );
	};

	LogoCommands.prototype.make = function ( a )
	{
		this.L.variableSet( a[0], a[1] );
	};

	LogoCommands.prototype.minus = function ( a )
	{
		return a[0] - a[1];
	};

	LogoCommands.prototype.name = function ( a )
	{
		this.make( [ a[1], a[0] ] );
	};

	LogoCommands.prototype.newturtle = function ( a )
	{
		this.L.turtleNew( a[0] );
	};

	LogoCommands.prototype.not = function ( a )
	{
		return !a[0];
	};

	LogoCommands.prototype.number = function ( a )
	{
		return a[0] == parseFloat( a[0] );
	};

	LogoCommands.prototype.or = function ( a )
	{
		var b = false;
		for ( var i = 0; i < a.length; ++i )
		{
			b = b || a[i];
		}

		return b;
	};

	LogoCommands.prototype.pd = function ( a )
	{
		this.L.turtleDown();
	};

	LogoCommands.prototype.pe = function ( a )
	{
		this.L.turtleErase();
	};

	LogoCommands.prototype.pensize = function ( a )
	{
		return this.L.turtlePenSize();
	};

	LogoCommands.prototype.pi = function ( a )
	{
		return Math.PI;
	};

	LogoCommands.prototype.pick = function ( a )
	{
		return a[0][ this.random( a[0].length - 1 ) ];
	};

	LogoCommands.prototype.pos = function ( a )
	{
		return this.L.turtlePosition();
	};

	LogoCommands.prototype.power = function ( a )
	{
		return Math.pow( a[0], a[1] );
	};

	LogoCommands.prototype.product = function ( a )
	{
		var prod = 1;

		if ( ( typeof a[0] ).toLowerCase() == 'object' )
		{
			return this.product( a[0] );
		}

		for ( var i = 0; i < a.length; ++i )
		{
			prod = prod * a[i];
		}

		return prod;
	};

	LogoCommands.prototype.pu = function ( a )
	{
		this.L.turtleUp();
	};

	LogoCommands.prototype.quotient = function ( a )
	{
		return( a[0] / a[1] );
	};

	LogoCommands.prototype.random = function ( a )
	{
		return Math.floor( Math.random() * Math.floor( parseFloat( a[0] ) + 1 ) );
	};

	LogoCommands.prototype.repeat = function ( a )
	{
		for ( var i = 0; i < a[0]; ++i )
		{
			this.L.run( a[1] );
		}
	};

	LogoCommands.prototype.right = function ( a )
	{
		this.left( [ -a[0] ] );
	};

	LogoCommands.prototype.round = function ( a )
	{
		return Math.round( a[0] );
	};

	LogoCommands.prototype.rt = function ( a )
	{
		this.right( a );
	};

	LogoCommands.prototype.savepict = function ( a )
	{
		this.L.toPNG();
	};

	LogoCommands.prototype.setc = function ( a )
	{
		this.setcolor( a );
	};

	LogoCommands.prototype.setcolor = function ( a )
	{
		this.L.turtleColor( Math.round( a[0] ), Math.round( a[1] ), Math.round( a[2] ) );
	};

	LogoCommands.prototype.seth = function ( a )
	{
		this.setheading( a );
	};

	LogoCommands.prototype.setheading = function ( a )
	{
		this.L.turtleHeading( a[0] );
	};

	LogoCommands.prototype.setpensize = function ( a )
	{
		this.L.turtleSetPenSize( a[0] );
	};

	LogoCommands.prototype.setpos = function ( a )
	{
		this.L.turtleSetPosition( a[0], a[1] );
	};

	LogoCommands.prototype.setsh = function ( a )
	{
		this.setshape( a );
	};

	LogoCommands.prototype.setx = function ( a )
	{
		this.L.turtleSetPosition( a[0], null );
	};

	LogoCommands.prototype.sety = function ( a )
	{
		this.L.turtleSetPosition( null, a[0] );
	};

	LogoCommands.prototype.sin = function ( a )
	{
		return Math.sin( a[0] );
	};

	LogoCommands.prototype.sqrt = function ( a )
	{
		return Math.sqrt( a[0] );
	};

	LogoCommands.prototype.sum = function ( a )
	{
		var sum = 0;

		if ( ( typeof a[0] ).toLowerCase() == 'object' )
		{
			return this.sum( a[0] );
		}

		for ( var i = 0; i < a.length; ++i )
		{
			sum = sum + a[i];
		}

		return sum;
	};

	LogoCommands.prototype.tan = function ( a )
	{
		return Math.tan( a[0] );
	};

	LogoCommands.prototype.towards = function ( a )
	{
		this.L.turtleTowards( a[0] );
	};

	LogoCommands.prototype.tto = function ( a )
	{
		this.talkto( a );
	};

	LogoCommands.prototype.tturtle = function ( a )
	{
		this.touchedturtle( a );
	};
	
	LogoCommands.prototype.while = function ( a )
	{
		while ( this.L.run( a[0] ) )
		{
			this.L.run( a[1] );
		}
	};

	LogoCommands.prototype.xcor = function ( a )
	{
		return this.L.turtlePosition()[0];
	};

	LogoCommands.prototype.ycor = function ( a )
	{
		return this.L.turtlePosition()[1];
	};

	LogoCommands.prototype._userfun = function ( f )
	{
		return function ( a )
		{
			return this.fns[ f ].evaluate( L, a );
		};
	};
}



function LogoParser ( C )
{
	var cmd = {
		abs:        'E',
		and:        'L2',
		announce:   'E',
		arccos:     'E',
		arcsin:     'E',
		arctan:     'E',
		back:       'E',
		bk:         'E',
		carefully:  'XX',
		cg:         '',
		clean:      '',
		difference: 'EE',
		'equal?':   'EE',
		everyone:   'X',
		exp:        'E',
		fd:         'E',
		first:      'E',
		forward:    'E',
		fput:       'EE',
		'greater?': 'EE',
		home:       '',
		'if':       'EX',
		ifelse:     'EXX',
		'int':      'E',
		item:       'EE',
		last:       'E',
		left:       'E',
		'less?':    'EE',
		lt:         'E',
		make:       'EE',
		minus:      'EE',
		name:       'EE',
		newturtle:  'E',
		not:        'E',
		'number?':  'E',
		or:         'L2',
		pd:         '',
		pe:         '',
		pensize:    '',
		pi:         '',
		pick:       'E',
		pos:        '',
		power:      'EE',
		product:    'EE',
		pu:         '',
		quotient:   'EE',
		random:     'E',
		repeat:     'EX',
		right:      'E',
		round:      'E',
		rt:         'E',
		savepict:   '',
		setc:       'EEE',
		setcolor:   'EEE',
		setheading: 'E',
		setpensize: 'E',
		setx:       'E',
		sety:       'E',
		sin:        'E',
		sqrt:       'E',
		sum:        'EE',
		towards:    'E',
		wait:       'E',
		'while':    'XX', // is this a hack? this may expose a weakness in our parsing strategy
		xcor:       '',
		ycor:       ''
	};

	var BCEXP = /^\s*\]/;
	var BOEXP = /^\s*\[/;
	var CEXP  = /^\s*([a-zA-Z.]+\??)\s*/i;
	var LEXP  = /^\s*\[(.*?)]/;
	var NEXP  = /^\s*"?(-?(?:\d*\.?\d+|\d+\.\d*))/;
	var SEXP  = /^\s*"([^\s]+)/
	var VEXP  = /^\s*\:([a-zA-Z.]+)/;



	LogoParser.prototype.parse = function ( s, lim, remtail )
	{
		var a, c, f, m, p;
		var queue = [];

		// this will need to be string-protected at some point
		s = s.replace( /(?:;.*)[\r\n]*/g, ' ' );

		while ( ( m = s.match( CEXP ) ) && ( lim == null || lim-- ) )
		{
			c = m[1].toLowerCase();
			s = s.substr( m[0].length );

			if ( c in cmd )
			{
				a = cmd[c];
				p = this.parseArgs( s, a );
				s = s.substr( p.length );
				queue.push( { c: C[c.replace(/\?/,'')], a: p.args } );
			}
			else if ( c in C.fns )
			{
				p = this.parseArgs( s, C.fns[c].getArgStr() );
				s = s.substr( p.length );
				queue.push( { c: C._userfun( c.replace(/\.+/,'_') ), a: p.args } );
			}
			else if ( c == 'to' )
			{
				m = s.match( CEXP );
				if ( !m )
				{
					alert( 'Malformed function declaration "' + s + '"; parse halted.' );
				}
				f = new DataFunction( m[1] );
				C.addFunction( f );
				s = s.substr( m[0].length );

				while ( m = s.match( VEXP ) )
				{
					s = s.substr( m[0].length );
					f.addArgument( m[1] );
				}

				p = this.parse( s );
				s = s.substr( s.length - p.s.length );
				f.setQueue( p.queue );
				C.addFunction( f );
			}
			else if ( c == 'end' )
			{
				//
				// if the syntax is right up to this point, we have exited a function declaration
				//
				return {
					queue: queue,
					s:     s
				};
			}
			else
			{
				alert( 'Unrecognized command "' + c + '"; parse halted.' );
				return false;
			}
		}

		if ( lim == null && !BCEXP.exec( s ) )
		{
			if ( !/^\s*$/.exec( s ) )
			{
				alert( 'Parse halted with tail "' + s );
			}

			return queue;
		}
		else
		{
			return {
				queue: queue,
				s:     remtail ? s.replace( BCEXP, '' ) : s
			};
		}
	};



	LogoParser.prototype.parseArgs = function ( s, a )
	{
		var p;
		var args   = [];
		var length = 0;

		for ( var i = 0; i < a.length; ++i )
		{
			if ( a[i] != 'L' )
			{
				p = this[ 'parseArg' + a[i] ]( s );
			}
			else
			{
				p = this.parseArgL( s, a[++i] );
			}
			s = s.substr( p.length );

			args.push( p.arg );
			length += p.length;
		}

		return {
			args:   args,
			length: length
		};
	};



	LogoParser.prototype.parseArgE = function ( s )
	{
		var a, m, p;

		// LISTS ARE VERY BROKEN
		if ( m = s.match( LEXP ) )
		{
			a = m.split( /\s+/ );
			for ( var i = 0; i < a.length; ++i )
			{
				if ( a[i] == parseFloat( a[i] ) )
				{
					a[i] = parseFloat( a[i] );
				}
			}

			return { arg: a, length: m[0].length, v: false };
		}
		else if ( m = s.match( NEXP ) )
		{
			return { arg: new DataNumber( parseFloat( m[1] ) ), length: m[0].length };
		}
		else if ( m = s.match( SEXP ) )
		{
			return { arg: new DataString( m[1] ), length: m[0].length };
		}
		else if ( m = s.match( VEXP ) )
		{
			return { arg: new DataVariable( m[1] ), length: m[0].length };
		}
		else if ( m = s.match( CEXP ) )
		{
			p = this.parse( s, 1 );
			return { arg: new DataExpression( p.queue[0] ), length: s.length - p.s.length, v: 'EXP' };
		}
		else
		{
			alert( 'Cannot parse tail "' + s + '"; halting parse.' );
			return false;
		}
	};



	LogoParser.prototype.parseArgL = function ( s, n )
	{
		//
		// DO THIS!
		//
		if ( /^\s*\[/.exec( s ) )
		{
			return null;
		}
		else
		{
			return null;
		}
	};



	LogoParser.prototype.parseArgX = function ( s )
	{
		var m, p;

		if ( !( m = s.match( BOEXP ) ) )
		{
			alert( 'Executable expressions must open with "["; halting parse.' );
			return false;
		}

		p = this.parse( s.replace( BOEXP, '' ), null, true );
		return {
			arg:    new DataRunnable( p.queue ),
			length: s.length - p.s.length
		};
	};
}



function DataExpression ( v )
{
	this.v = v;

	DataExpression.prototype.evaluate = function ( C )
	{
		return C.run( [ this.v ] );
	};
}



function DataFunction ( v )
{
	this.a = [];
	this.q = null;
	this.v = v;

	DataFunction.prototype.addArgument = function ( a )
	{
		this.a.push( a );
	};

	DataFunction.prototype.evaluate = function ( C, a )
	{
		C.addContext( this.a, a );
		var rv = C.run( this.q );
		C.dropContext();
		return rv;
	};

	DataFunction.prototype.getArgStr = function ()
	{
		var s = '';
		for ( var i = 0; i < this.a.length; ++i )
		{
			s += 'E';
		}
		return s;
	};

	DataFunction.prototype.setQueue = function ( q )
	{
		this.q = q;
	};
}



function DataList ( v )
{
	this.v = v;

	DataList.prototype.evaluate = function ( C )
	{
		var w = Array( this.v.length );
		for ( var i = 0; i < this.v.length; ++i )
		{
			w[i] = this.v[i].evaluate( C );
		}
		return w;
	};
}



function DataNumber ( v )
{
	this.v = v;

	DataNumber.prototype.evaluate = function ( C )
	{
		return this.v;
	};
}



function DataRunnable ( v )
{
	this.v = v;

	DataRunnable.prototype.evaluate = function ( C )
	{
		return this.v;
	};
}



function DataString( v )
{
	this.v = v;

	DataString.prototype.evaluate = function ( C )
	{
		return this.v;
	};
}



function DataVariable ( v )
{
	this.v = v;

	DataVariable.prototype.evaluate = function ( C )
	{
		return C.variableGet( this.v );
	};
}



function Turtle ( n, x0, y0 )
{
	this.bg = { r: 0, g: 0, b: 0 };
	this.fg = { r: 255, g: 255, b: 255 };
	this.h  = 90;
	this.n  = n;
	this.pc = this.fg;
	this.pd = true;
	this.w  = 1;
	this.x  = x0;
	this.y  = y0;

	Turtle.prototype.getPC = function ()
	{
		if ( this.pd == 1 )
		{
			return 'rgba(' + this.fg.r + ',' + this.fg.g + ',' + this.fg.b + ',1.0)';
		}
		else if ( this.pd == -1 )
		{
			return 'rgba(' + this.bg.r + ',' + this.bg.g + ',' + this.bg.b + ',1.0)';
		}
		else
		{
			return null;
		}
	};

	Turtle.prototype.getHeading = function ()
	{
		return this.h;
	};

	Turtle.prototype.getName = function ()
	{
		return this.n;
	};

	Turtle.prototype.getPD = function ()
	{
		return this.pd;
	};

	Turtle.prototype.getPenSize = function ()
	{
		return this.w;
	};

	Turtle.prototype.getX = function ()
	{
		return this.x;
	};

	Turtle.prototype.getY = function ()
	{
		return this.y;
	};

	Turtle.prototype.penDown = function ()
	{
		this.pd = 1;
	};

	Turtle.prototype.penErase = function ()
	{
		this.pd = -1;
	};

	Turtle.prototype.penUp = function ()
	{
		this.pd = 0;
	};

	Turtle.prototype.setFG = function ( r, g, b )
	{
		this.fg = { r: r, g: g, b: b };
	};

	Turtle.prototype.setHeading = function ( h )
	{
		this.h = h % 360;
	};

	Turtle.prototype.setPenSize = function ( w )
	{
		this.w = w;
	};

	Turtle.prototype.setPosition = function ( x, y )
	{
		if ( x != null )
		{
			this.x = x;
		}

		if ( y != null )
		{
			this.y = y;
		}
	};

	Turtle.prototype.turnLeft = function ( dh )
	{
		this.h += dh;
		this.h %= 360;
	};
}




