//
//  WebScriptObject.m
//  SimpleWebKit
//
//  Created by Dr. H. Nikolaus Schaller on Tue May 16 2006.
//  Added Java/ECMAScript by H. Nikolaus Schaller on 15.03.07.
//  Copyright (c) 2006 DSITRI. All rights reserved.
//
//  syntax for parser is based on overview http://en.wikipedia.org/wiki/WebScript_syntax
//	and ECMAScript http://www.ecma-international.org/publications/standards/Ecma-262.htm
//

#import <WebKit/WebDOMOperations.h>
#import <WebKit/WebScriptObject.h>
#import <WebKit/WebUndefined.h>

#import "Private.h"

NSString *WebScriptException=@"WebScriptException";

@implementation WebScriptObject

+ (void) initialize;
{
	/* predefine global variables so that they return a prototype object
		Array, Boolean, Date, Function, Math, Number, Object, RegExp, String
	with predefined WebScriptBuiltinFunction for methods like length, size etc.
	
		Browser predefines variables through the context like
	navigator
	document
	event
	window
	
	*/
}

+ (BOOL) throwException:(NSString *) message;
{
	[[NSException exceptionWithName:WebScriptException reason:message userInfo:nil] raise];
	return NO;
}

- (id) callWebScriptMethod:(NSString *) name withArguments:(NSArray *) args;
{
	return [[[self _get:name] _getValue] _call:self arguments:args];
}

- (id) evaluateWebScript:(NSString *) script;
{
		NSScanner *sc=[NSScanner scannerWithString:script];
	// how do we pass the environment context (windows, event, etc.)?
	// well, if we are a DOM(HTML)Element we should know our DOMDocument and that should know
		return [[_WebScriptTreeNode _programWithScanner:sc] _evaluate];	// evaluate into an internal representation
}

- (void) removeWebScriptKey:(NSString *) key;
{
	[self _delete:key];
}

- (void) setException:(NSString *) message;
{
	[[NSException exceptionWithName:WebScriptException reason:message userInfo:[NSDictionary dictionaryWithObject:self forKey:@"this"]] raise];
}

- (void) setWebScriptValueAtIndex:(unsigned int) index value:(id) val;
{
	NIMP;
	[self _put:@"key from index" value:val];
}

- (NSString *) stringRepresentation;
{
	// should we format an array or object in JavaScript language?
	return NIMP;
}

- (id) webScriptValueAtIndex:(unsigned int) index;
{
	return NIMP;
	[self _get:@"key from index"];
}

	// KVC

- (void) setValue:(id) val forKey:(NSString *) path;
{
	// or superclass?
	NIMP;
}

- (id) valueForKey:(NSString *) path;
{
	// or superclass?
	return NIMP;
}

@end

@implementation NSObject (WebScripting)

// NIMP - we don't use that

@end

@implementation NSObject (WebScriptDereference)

- (id) _getValue; { return self; }	// 8.7.1 - rule 1

- (void) _putValue:(id) val;
{ // 8.7.2
	// throw ReferenceError	// rule 1. - is not an lvalue
}

@end

@implementation NSObject (WebScriptTypeConversion)

// default implementations

- (id) _toPrimitive:(Class) preferredType; {	return self; }	// 9.1 - already a primitive

- (NSNumber *) _toBoolean; { return NO; }	// default
- (NSNumber *) _toNumber;	{ /* typeError */ return NIMP; }	// 9.9

- (NSNumber *) _toInteger;
{
	double d=[[self _toNumber] doubleValue];
	// check for NaN -> 0
	// keep +0 / -1 or +Inf, -Inf unchanged
	if(d < 0)
		return [NSNumber numberWithDouble:-floor(-d)];
	else
		return [NSNumber numberWithDouble:floor(d)];
}

- (NSNumber *) _toInt32;
{ // 9.5
	double d=[[self _toNumber] doubleValue];
	// check for NaN -> 0
	// map +0 / -1 or +Inf, -Inf -> 0
	return [NSNumber numberWithInt:(int) d];	// is that exactly as defined???
}

- (NSNumber *) _toUint32;
{ // 9.6
	double d=[[self _toNumber] doubleValue];
	// check for NaN -> 0
	// map +0 / -1 or +Inf, -Inf -> 0
	return [NSNumber numberWithUnsignedInt:(unsigned int) d];	// is that exactly as defined???
}

- (NSNumber *) _toUint16;
{ // 9.7
	double d=[[self _toNumber] doubleValue];
	// check for NaN -> 0
	// map +0 / -1 or +Inf, -Inf -> 0
	return [NSNumber numberWithUnsignedShort:(unsigned short) d];	// is that exactly as defined???
}

- (NSString *) _toString; {	return [self description]; } // 9.8
- (WebScriptObject *) _toObject;	{ /* typeError */ return NIMP; }	// 9.9

@end

@implementation WebScriptObject (WebScriptObjectAccess)

// default implementations for all WebScriptObjects

- (WebScriptObject *) _prototype; { return nil; }
- (NSString *) _class; { return NSStringFromClass(isa); }	// default...
- (id) _get:(NSString *) property; { return NIMP; }
- (void) _put:(NSString *) property value:(id) val; { /* throw exception */ NIMP; }
- (BOOL) _canPut:(NSString *) property; { return NO; }
- (BOOL) _hasProperty:(NSString *) property; { return NO; }
- (BOOL) _delete:(NSString *) property; { return NO; }
- (id) _defaultValue:(Class) hint; { return NIMP; }
- (WebScriptObject *) _construct:(NSArray *) arguments; { return NIMP; }
- (WebScriptObject *) _call:(WebScriptObject *) this arguments:(NSArray *) arguments; {	return NIMP; }
- (BOOL) _hasInstance:(WebScriptObject *) value; { return NO; }
- (id) _scope; { return NIMP; }
- (id /*<MatchResult>*/) _match:(NSString *) pattern index:(unsigned) index; { return NIMP; }

@end

@implementation WebUndefined (_WebScriptReference)
- (NSNumber *) _toBoolean; { return [NSNumber numberWithBool:NO]; }	// default
- (NSNumber *) _toNumber;	{ return [NSNumber numberWithDouble:0.0]; }	// FIXME: should return +NaN
- (NSString *) _toString; {	return @"undefined"; } // 9.8
@end

@implementation NSNull (_WebScriptReference)
- (NSNumber *) _toBoolean; { return [NSNumber numberWithBool:NO]; }	// default
- (NSNumber *) _toNumber;	{ return [NSNumber numberWithDouble:0.0]; }
- (NSString *) _toString; {	return @"null"; } // 9.8
@end

@implementation NSNumber (_WebScriptReference)

- (NSNumber *) _toBoolean;
{
	// we should check if it is already a boolean!
	double d=[self doubleValue];
	// if +0, -0 or NaN -> NO
	return [NSNumber numberWithBool:(d != 0.0)];
}

- (NSNumber *) _toNumber;
{
	return [NSNumber numberWithDouble:[self doubleValue]];	// also converts TRUE/FALSE -> 1.0/0.0
}

- (NSString *) _toString;
{
	if([self isKindOfClass:[[NSNumber numberWithBool:NO] class]])
		return [self boolValue]?@"true":@"false";
	// handle Infinity, NaN
	return [self description];
}
@end

@implementation NSString (_WebScriptReference)
- (NSNumber *) _toBoolean; { return [NSNumber numberWithBool:[self length] > 0]; }	// default
- (NSNumber *) _toNumber;
{
	NSScanner *sc=[NSScanner scannerWithString:self];
	double d;
	unsigned ui;
	[sc setCaseSensitive:NO];
	// set whitespace according to list at 9.3.1
	if([sc scanString:@"Infinity" intoString:NULL])
		; // [NSNumber numberWithDouble:Inf];
	if([sc scanString:@"+Infinity" intoString:NULL])
		; // [NSNumber numberWithDouble:Inf];
	if([sc scanString:@"-Infinity" intoString:NULL])
		; // [NSNumber numberWithDouble:Inf];
	if([sc scanString:@"0x" intoString:NULL] && [sc scanHexInt:&ui])
		return [NSNumber numberWithDouble:(double) ui];
	if([sc scanDouble:&d])
		return [NSNumber numberWithDouble:d];
	return [NSNumber numberWithDouble:0.0];	// should be NaN
}
- (NSString *) _toString; {	return self; } // 9.8
@end

@implementation _ConcreteWebScriptObject	// a real (internal) WebScriptObject

- (id) init;
{
	if((self=[super init]))
		{
		properties=[[NSMutableDictionary alloc] initWithCapacity:20];
		}
	return self;
}

- (void) dealloc;
{
	[properties release];
	[super dealloc];
}

- (NSString *) description;
{
	// should format the properties in WebScript format
	// i.e. something like { key:value, key:value etc. }
	// add proper indentation to all lines of sub-properties
	return [properties description];
}

- (WebScriptObject *) _prototype; { return prototype; }
- (NSString *) _class; { return NSStringFromClass(isa); }	// default...

- (id) _get:(NSString *) property;
{ // 8.6.2.1
	id val;
	if((val=[properties objectForKey:property]))
		return val;	// got it
	if(!prototype)
		return [WebUndefined undefined];
	return [prototype _get:property];	// ask prototype
}

// override by Array object  15.4.5.1
- (void) _put:(NSString *) property value:(id) val;
{ // 8.6.2.2
	if(![self _canPut:property])
		return;
	if(![attributes objectForKey:property])
		[attributes setObject:[NSNumber numberWithInt:0] forKey:property];
	[properties setObject:val forKey:property];
}

- (BOOL) _canPut:(NSString *) property;
{ // 8.6.2.3
	NSNumber *att=[attributes objectForKey:property];
	if(!att)	// does not exist yet
		return [prototype _canPut:property];	// ask prototype - if present
	return ([att intValue]&WebScriptPropertyAttributeReadOnly) == 0;	// does not have readonly
}

- (BOOL) _hasProperty:(NSString *) property;
{ // 8.6.2.4
	return [properties objectForKey:property] || [prototype _hasProperty:property];
}

- (BOOL) _delete:(NSString *) property;
{ // 8.6.2.5
	if(![properties objectForKey:property])
		return YES;
	if(([[attributes objectForKey:property] intValue]&WebScriptPropertyAttributeDontDelete) != 0)
		return NO;
	[properties removeObjectForKey:property];
	[attributes removeObjectForKey:property];
	return YES;	// success
}

- (id) _defaultValue:(Class) hint;
{ // 8.6.2.6
	id val;
	if([hint isKindOfClass:[NSString class]] || 0 /* self is a Date object */)
		{ // rules for String or Date hint
		val=[self _get:@"toString"];
		if([val isKindOfClass:isa])
			{ // is an object (step 3-4)
			val=[val _call:self arguments:nil];
			if(![val isKindOfClass:isa])
				return val;	// appears to be a primitive value
			}
		val=[self _get:@"valueOf"];
		if([val isKindOfClass:isa])
			{ // is an object (step 7-8)
			val=[val _call:self arguments:nil];
			if(![val isKindOfClass:isa])
				return val;	// appears to be a primitive value
			}
		}
	else
		{ // rules for Number or nil hint
		val=[self _get:@"valueOf"];
		if([val isKindOfClass:isa])
			{ // is an object (step 3-4)
			val=[val _call:self arguments:nil];
			if(![val isKindOfClass:isa])
				return val;	// appears to be a primitive value
			}
		val=[self _get:@"toString"];
		if([val isKindOfClass:isa])
			{ // is an object (step 7-8)
			val=[val _call:self arguments:nil];
			if(![val isKindOfClass:isa])
				return val;	// appears to be a primitive value
			}
		}
		// throw TypeError exception
	return nil;
}

- (WebScriptObject *) _construct:(NSArray *) arguments;
{
	return NIMP;
}

- (WebScriptObject *) _call:(WebScriptObject *) this arguments:(NSArray *) arguments;
{
	return NIMP;
}

- (BOOL) _hasInstance:(WebScriptObject *) value;
{
	NIMP; return NO;
}

- (id) _scope;
{
	return NIMP;
}

- (id /*<MatchResult>*/) _match:(NSString *) pattern index:(unsigned) index;
{
	return NIMP;
}

// override type conversion

- (id) _toPrimitive:(Class) preferredType; { return [self _defaultValue:preferredType]; } // 9.1
- (NSNumber *) _toBoolean;					{ return [NSNumber numberWithBool:YES]; }
- (NSNumber *) _toNumber;						{ return [[self _toPrimitive:[NSNumber class]] _toNumber]; }
- (NSString *) _toString;						{ return [[self _toPrimitive:[NSString class]] _toString]; } // 9.8
- (WebScriptObject *) _toObject;		{ return self; }	// 9.9

@end

@implementation _ConcreteWebScriptArray
// overrides _put method
@end

@implementation _ConcreteWebScriptFunction

@end

@implementation NSObject (_WebScriptParsingAndEvaluation)

- (BOOL) _isReference; { return NO; }
- (BOOL) _isKeyword; { return NO; }
- (BOOL) _isKeyword:(NSString *) str;	{ return NO; }

- (id) _evaluate;	{ return self; }	// primitives evaluate to themselves; statements evaluate to array triples

@end

@implementation _WebScriptTreeNode

// parser

+ (void) _skipComments:(NSScanner *) sc;
{ // 7.4
		static NSString *cComment=@"/*";
		 static NSString *cCommentEnd=@"*/";
		 static NSString *cPlusPlusComment=@"//";
			 // switch scanner to skip whitespace
		while(YES)
		{
			[sc setCharactersToBeSkipped:[NSCharacterSet whitespaceCharacterSet]];	// initially eat all whitespace (but no new lines)
			if([sc scanString:cPlusPlusComment intoString:NULL])
				{
				[sc setCharactersToBeSkipped:nil];	// don't end at first whitespace
				[sc scanUpToString:@"\n" intoString:NULL];
				}
			else if([sc scanString:cComment intoString:NULL])
				{
				[sc setCharactersToBeSkipped:nil];	// don't end at first whitespace
				[sc scanUpToString:cCommentEnd intoString:NULL];
				[sc scanString:cCommentEnd intoString:NULL];	// and eat stop string
				// how to handle the multiline comment rule that inserts a virtual \n?
				}
			else
				return;	// neither
		}
}

/* expressions 11. */

+ (id) _primaryExpressionWithScanner:(NSScanner *) sc;
{ // PrimaryExpression 11.1
	id r;
	NSString *string;
	unsigned int ui;
	double d;
	[self _skipComments:sc];
	if([sc isAtEnd])
		{
		return nil;	// unexpected EOF
		}
	else if([sc scanString:@"(" intoString:NULL])
		{ // grouping operator 11.1.6
		r=[self _expressionWithScanner:sc noIn:NO];
		if([r _isKeyword])
			;	// raise exception - you should not be able to write (if)(a==b)
		if(![sc scanString:@")" intoString:NULL])
			return nil;	// or some WebScriptError object
		}
	else if([sc scanString:@"[" intoString:NULL])
		{ // ArrayLiteral 11.1.4
		NSMutableArray *values=[[NSMutableArray alloc] initWithCapacity:5];
		r=[_ConcreteWebScriptArray new];	// should call constructor on the Array object - like "new Array()"
		[self _skipComments:sc];
		if(![sc scanString:@"]" intoString:NULL])
			{ // not an empty array
			int idx=0;
			for(idx=0; ; idx++)
				{ // collect properties for given indexes
				[self _skipComments:sc];
				if([sc scanString:@"," intoString:NULL])
					{
					[values addObject:[NSNull null]];	// "hole"
					continue;	// next index
					}
				if([sc scanString:@"]" intoString:NULL])
					break;
				if([sc isAtEnd])
					{ // error
					r=nil;
					break;
					}
				[values addObject:[self _assignmentExpressionWithScanner:sc noIn:NO]];
				[sc scanString:@"," intoString:NULL];	// skip if present
				}
			r=[_WebScriptTreeNodeArrayLiteralConstructor node:nil :values];	// should call constructor on the Array object like "new Array()"
			[values release];
			}
		}
	else if([sc scanString:@"{" intoString:NULL])
		{ // ObjectLiteral 11.1.5
		NSMutableArray *keys=[[NSMutableArray alloc] initWithCapacity:5];
		NSMutableArray *values=[[NSMutableArray alloc] initWithCapacity:5];
		[self _skipComments:sc];
		if(![sc scanString:@"}" intoString:NULL])
			{ // not an empty dictionary
			while(YES)
				{ // collect properties for given indexes
				if([sc isAtEnd])
					break;	// error...
				[keys addObject:[self _assignmentExpressionWithScanner:sc noIn:NO]];
				[self _skipComments:sc];
				if(![sc scanString:@":" intoString:NULL])
					break;	// error
				[values addObject:[self _assignmentExpressionWithScanner:sc noIn:NO]];
				[self _skipComments:sc];
				if([sc isAtEnd])
					break;	// error...
				if([sc scanString:@"," intoString:NULL])
					continue;	// next index
				if([sc scanString:@"}" intoString:NULL])
					break;
				}
			}
		r=[_WebScriptTreeNodeObjectLiteralConstructor node:keys :values];	// should call constructor on the Object object like "new Object()"
		[keys release];
		[values release];
		}
	else if([sc scanString:@"\"" intoString:NULL])
		{ // 7.8.4
		static NSCharacterSet *doubleQuoteStopCharacterSet=nil;
		[sc setCharactersToBeSkipped:nil];
		string=@"";	// if we immediately hit a stop character
		if(!doubleQuoteStopCharacterSet)
			doubleQuoteStopCharacterSet=[[NSCharacterSet characterSetWithCharactersInString:@"\""] retain];
		[sc scanUpToCharactersFromSet:doubleQuoteStopCharacterSet intoString:&string];
		// FIXME: handle escape sequences
		[sc scanString:@"\"" intoString:NULL];	// should have been a double quote
		r=string;
		}
	else if([sc scanString:@"'" intoString:NULL])
		{ // 7.8.4 string
		static NSCharacterSet *quoteStopCharacterSet=nil;
		[sc setCharactersToBeSkipped:nil];
		string=@"";	// if we immediately hit a stop character
		if(!quoteStopCharacterSet)
			quoteStopCharacterSet=[[NSCharacterSet characterSetWithCharactersInString:@"'"] retain];
		// FIXME: handle escape sequences
		[sc scanUpToCharactersFromSet:quoteStopCharacterSet intoString:&string];
		[sc scanString:@"'" intoString:NULL];	// should have been a single quote
																						// if we allow to glue strings together, i.e. "abc"   "def", then check for a second quote and glue fragments together
		r=string;
		}
	else if([sc scanString:@"/" intoString:NULL])
		{ // regexp literal 7.8.5   /pattern/flags
		NSLog(@"regexp literals not implemented");
		r=nil;	// not implemented
		}
	else if([sc scanString:@"0x" intoString:NULL])	// we have setCaseSensitive:NO - which does not harm parsing of identifiers
		{ // 7.8.3
		if([sc scanHexInt:&ui])
			{ // Hex ints are converted to double
			r=[NSNumber numberWithDouble:(double) ui];
			}
		else
			return nil;	// invalid 0x
		}
	else if([sc scanDouble:&d])
		{ // 7.8.3
		r=[NSNumber numberWithDouble:d];
		}
	else
		{ // must be an identifier 7.6
			static NSCharacterSet *symbolCharacterSet=nil;
		[sc setCharactersToBeSkipped:nil];
		string=@"";	// if we immediately hit a stop character
		if(!symbolCharacterSet)
			{ // we must allow any Unicode letter or digit (7.6) or _ or $ or \Unicode escape + some punctuation...
			symbolCharacterSet=[[NSCharacterSet characterSetWithCharactersInString:@"_$abcdefghijlmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"] retain];
			}
		if([sc scanCharactersFromSet:symbolCharacterSet intoString:&string])
			{
			// literals 7.8.1 and 7.8.2
			if([string isEqualToString:@"null"])
				r=[NSNull null];
			else if([string isEqualToString:@"true"])
				r=[NSNumber numberWithBool:YES];
			else if([string isEqualToString:@"false"])
				r=[NSNumber numberWithBool:NO];
			else if([string isEqualToString:@"this"])
				r=[_WebScriptTreeNodeThis node:nil :nil];
			else
				{ // identifier -> either keyword or reference
				static NSArray *reservedWords;
				if(!reservedWords)
					{ // inhibit any other use of reserved words 
					reservedWords=[[NSArray alloc] initWithObjects:
						/* reserved 7.5.2 */
						@"break",
						@"case",
						@"catch",
						@"continue",
						@"default",
						@"delete",
						@"do",
						@"else",
						@"finally",
						@"for",
						@"function",
						@"if",
						@"in",
						@"instanceof",
						@"new",
						@"return",
						@"switch",
						@"this",
						@"throw", 
						@"try", 
						@"typeof", 
						@"var",
						@"void", 
						@"while",
						@"with",
						/* future reserved 7.5.3 */
						@"abstract",
						@"boolean",
						@"byte",
						@"char",
						@"class",
						@"const",
						@"debugger",
						@"double",
						@"enum",
						@"export",
						@"extends",
						@"final",
						@"float",
						@"goto",
						@"implements",
						@"import",
						@"int",
						@"interface",
						@"long",
						@"native",
						@"package",
						@"private",
						@"protected",
						@"public",
						@"short", 
						@"static", 
						@"super", 
						@"synchronized", 
						@"throws", 
						@"transient", 
						@"volatile",
						nil];
					}
				if([reservedWords containsObject:string])
					{ // return it as a special "keyword" token down the expression tree
					r=[_WebScriptTreeNodeKeyword node:string :nil];
					}
				else
					{
					// FIXME: evaluate according to the scoping rules 10.1.4
					r=[_WebScriptTreeNodeReference node:nil :string];	// return a reference
					}
				}
			}
		else
			{ // invalid symbol
			NSLog(@"unexpected character %C", [[sc string] characterAtIndex:[sc scanLocation]]);
			[sc setScanLocation:[sc scanLocation]+1];	// skip at least one character each time
			r=nil;
			}
		}
	return r;
}

+ (id) _lhsExpressionWithScanner:(NSScanner *) sc forNew:(BOOL) flag;
{ // Left Hand Side Expression 11.2
	id l;
		l=[self _primaryExpressionWithScanner:sc];
		if([l _isKeyword])
			{
			if([l _isKeyword:@"new"])
				{ // new xxx
				l=[_WebScriptTreeNodeNew node:l :[self _lhsExpressionWithScanner:sc forNew:YES]];
				}
			if([l _isKeyword:@"function"])
				{
				}
			return l;
			}
	while(![sc isAtEnd])
		{ // suffix operators may follow
		[self _skipComments:sc];
		if([sc scanString:@"(" intoString:NULL])
			{ // function call
			NSMutableArray *arglist=[[NSMutableArray alloc] initWithCapacity:5];	// argument list - 11.2.4
			[self _skipComments:sc];
			if(![sc scanString:@")" intoString:NULL])
				{
				do
					{
					[arglist addObject:[self _assignmentExpressionWithScanner:sc noIn:flag]];
					} while([sc scanString:@"," intoString:NULL]);
				if(![sc scanString:@")" intoString:NULL])
					return nil;	// or some WebScriptError object
				}
			l=[_WebScriptTreeNodeCall node:l :arglist];
			if(flag)
				break;	// only one argument list for each new
			}
		else if([sc scanString:@"[" intoString:NULL])
			{ // array/object indexing - 11.2.1
			l=[_WebScriptTreeNodeIndex node:l :[self _expressionWithScanner:sc noIn:NO]];
			if(![sc scanString:@"]" intoString:NULL])
				return nil;	// or some WebScriptError object
			}
		else if([sc scanString:@"." intoString:NULL])
			{ // apply and make a reference
			id r=[self _primaryExpressionWithScanner:sc];
			if(![r _isReference])
				;	// error
			l=[_WebScriptTreeNodeReference node:l :[r getPropertyName]];	// make a static reference
			}
		else
			break;
		}
	return l;
}

+ (id) _postfixExpressionWithScanner:(NSScanner *) sc;
{ // 11.3
	id l;
	l=[self _lhsExpressionWithScanner:sc forNew:NO];	// allow any number of function evaluations
	if([l _isKeyword])
		return l;
	// no new line allowed here but comments...
	if([sc scanString:@"++" intoString:NULL])
		{ // 11.3.1
		l=[_WebScriptTreeNodePostfix node:l :nil];
		((_WebScriptTreeNodePostfix *)l)->op=PlusPlus;
		}
	else if([sc scanString:@"--" intoString:NULL])
		{ // 11.3.2
		l=[_WebScriptTreeNodePostfix node:l :nil];
		((_WebScriptTreeNodePostfix *)l)->op=MinusMinus;
		}
	return l;
}

+ (id) _unaryExpressionWithScanner:(NSScanner *) sc;
{
	id r;
	[self _skipComments:sc];
	if([sc scanString:@"++" intoString:NULL])
		{ // 11.4.4
		r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
		((_WebScriptTreeNodeUnary *)r)->op=UPlusPlus;
		}
	if([sc scanString:@"--" intoString:NULL])
		{ // 11.4.5
		r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
		((_WebScriptTreeNodeUnary *)r)->op=UMinusMinus;
		}
	if([sc scanString:@"+" intoString:NULL])
		{ // 11.4.6
		r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
		((_WebScriptTreeNodeUnary *)r)->op=Plus;
		}
	if([sc scanString:@"-" intoString:NULL])
		{ // 11.4.7
		r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
		((_WebScriptTreeNodeUnary *)r)->op=Minus;
		}
	if([sc scanString:@"~" intoString:NULL])
		{ // 11.4.8
		r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
		((_WebScriptTreeNodeUnary *)r)->op=Neg;
		}
	if([sc scanString:@"!" intoString:NULL])
		{ // logical not
		r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
		((_WebScriptTreeNodeUnary *)r)->op=Not;
		}
	else
		{
		r=[self _postfixExpressionWithScanner:sc];
		if([r _isKeyword:@"delete"])
			{ // 11.4.1
			r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
			((_WebScriptTreeNodeUnary *)r)->op=Delete;
			}
		if([r _isKeyword:@"void"])
			{ // 11.4.2
			r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
			((_WebScriptTreeNodeUnary *)r)->op=Void;
			}
		if([r _isKeyword:@"typeof"])
			{ // 11.4.3
			r=[_WebScriptTreeNodeUnary node:nil :[self _unaryExpressionWithScanner:sc]];
			((_WebScriptTreeNodeUnary *)r)->op=Typeof;
			}
		}
	return r;
}

+ (id) _multiplicativeExpressionWithScanner:(NSScanner *) sc;
{ // 11.5
	id l;
	l=[self _unaryExpressionWithScanner:sc];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"*" intoString:NULL])
			{ // 11.5.1
			l=[_WebScriptTreeNodeMultiplicative node:l :[self _unaryExpressionWithScanner:sc]];
			((_WebScriptTreeNodeMultiplicative *)l)->op=Mult;
			}
		else if([sc scanString:@"/" intoString:NULL])
			{ // 11.5.2
			l=[_WebScriptTreeNodeMultiplicative node:l :[self _unaryExpressionWithScanner:sc]];
			((_WebScriptTreeNodeMultiplicative *)l)->op=Div;
			}
		else if([sc scanString:@"%" intoString:NULL])
			{ // 11.5.3
			l=[_WebScriptTreeNodeMultiplicative node:l :[self _unaryExpressionWithScanner:sc]];
			((_WebScriptTreeNodeMultiplicative *)l)->op=Mod;
			}
		else
			break;
		}
	return l;
}

+ (id) _additiveExpressionWithScanner:(NSScanner *) sc;
{ // 11.6
	id l;
	l=[self _multiplicativeExpressionWithScanner:sc];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"+" intoString:NULL])
			{ // 11.6.1
			l=[_WebScriptTreeNodeAdditive node:l :[self _multiplicativeExpressionWithScanner:sc]];
			((_WebScriptTreeNodeAdditive *)l)->op=Add;
			}
		else if([sc scanString:@"-" intoString:NULL])
			{ // 11.6.2
			l=[_WebScriptTreeNodeAdditive node:l :[self _multiplicativeExpressionWithScanner:sc]];
			((_WebScriptTreeNodeAdditive *)l)->op=Sub;
			}
		else
			break;
		}
	return l;
}

+ (id) _shiftExpressionWithScanner:(NSScanner *) sc;
{ // 11.7
	id l;
	l=[self _additiveExpressionWithScanner:sc];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"<<" intoString:NULL])
			{ // 11.7.1
			l=[_WebScriptTreeNodeShift node:l :[self _additiveExpressionWithScanner:sc]];
			((_WebScriptTreeNodeShift *)l)->op=Shl;
			}
		else if([sc scanString:@">>>" intoString:NULL])
			{ // 11.7.3
			l=[_WebScriptTreeNodeShift node:l :[self _additiveExpressionWithScanner:sc]];
			((_WebScriptTreeNodeShift *)l)->op=UShr;
			}
		else if([sc scanString:@">>" intoString:NULL])
			{ // 11.7.2
			l=[_WebScriptTreeNodeShift node:l :[self _additiveExpressionWithScanner:sc]];
			((_WebScriptTreeNodeShift *)l)->op=Shr;
			}
		else
			break;
		}
	return l;
}

+ (id) _relationalExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.8
	id l;
	l=[self _shiftExpressionWithScanner:sc];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"<" intoString:NULL])
			{ // 11.8.1
			l=[_WebScriptTreeNodeRelational node:l :[self _shiftExpressionWithScanner:sc]];
			((_WebScriptTreeNodeRelational *)l)->op=LessThan;
			}
		else if([sc scanString:@">" intoString:NULL])
			{ // 11.8.2
			l=[_WebScriptTreeNodeRelational node:[self _shiftExpressionWithScanner:sc] :l];	// swapped operands
			((_WebScriptTreeNodeRelational *)l)->op=LessThan;
			}
		else if([sc scanString:@"<=" intoString:NULL])
			{ // 11.8.3
			l=[_WebScriptTreeNodeRelational node:l :[self _shiftExpressionWithScanner:sc]];
			((_WebScriptTreeNodeRelational *)l)->op=LessEqual;
			}
		else if([sc scanString:@">=" intoString:NULL])
			{ // 11.8.4
			l=[_WebScriptTreeNodeRelational node:[self _shiftExpressionWithScanner:sc] :l];	// swapped operands
			((_WebScriptTreeNodeRelational *)l)->op=LessEqual;
			}
		else if([sc scanString:@"instanceof" intoString:NULL])
			{ // 11.8.6
			l=[_WebScriptTreeNodeRelational node:l :[self _shiftExpressionWithScanner:sc]];
			((_WebScriptTreeNodeRelational *)l)->op=InstanceOf;
			}
		else if(!flag && [sc scanString:@"in" intoString:NULL])
			{ // 11.8.7
			l=[_WebScriptTreeNodeRelational node:l :[self _shiftExpressionWithScanner:sc]];
			((_WebScriptTreeNodeRelational *)l)->op=In;
			}
		else 
			break;
		}
	return l;
}

+ (id) _equalityExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.9
	id l;
	l=[self _relationalExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"==" intoString:NULL])
			{ // 11.9.1
			l=[_WebScriptTreeNodeEquality node:l :[self _relationalExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeEquality *)l)->equal=YES;
			}
		else if([sc scanString:@"!=" intoString:NULL])
			{ // 11.9.2
			l=[_WebScriptTreeNodeEquality node:l :[self _relationalExpressionWithScanner:sc noIn:flag]];
			}
		else if([sc scanString:@"===" intoString:NULL])
			{ // 11.9.4
			l=[_WebScriptTreeNodeEquality node:l :[self _relationalExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeEquality *)l)->equal=YES;
			((_WebScriptTreeNodeEquality *)l)->strict=YES;
			}
		else if([sc scanString:@"!=" intoString:NULL])
			{ // 11.9.5
			l=[_WebScriptTreeNodeEquality node:l :[self _relationalExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeEquality *)l)->strict=YES;
			}
		else
			break;
		}
	return l;
}

+ (id) _bitwiseAndExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.10 (a)
	id l;
	l=[self _equalityExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"&" intoString:NULL])
			{
			l=[_WebScriptTreeNodeBitwise node:l :[self _equalityExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeBitwise *)l)->op=And;
			}
		else
			break;
		}
	return l;
}

+ (id) _bitwiseXorExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.10 (b)
	id l;
	l=[self _bitwiseAndExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"^" intoString:NULL])
			{
			l=[_WebScriptTreeNodeBitwise node:l :[self _bitwiseAndExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeBitwise *)l)->op=Xor;
			}
		else
			break;
		}
	return l;
}

+ (id) _bitwiseOrExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.10 (c)
	id l;
	l=[self _bitwiseXorExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"|" intoString:NULL])
			{
			l=[_WebScriptTreeNodeBitwise node:l :[self _bitwiseXorExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeBitwise *)l)->op=Or;
			}
		else
			break;
		}
	return l;
}

+ (id) _logicalAndExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.11 (a)
	id l;
	l=[self _bitwiseOrExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"&&" intoString:NULL])
			{ // logical and operator
			l=[_WebScriptTreeNodeLogical node:l :[self _bitwiseOrExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeLogical *)l)->op=LAnd;
			}
		else
			break;	// done
		}
	return l;
}

+ (id) _logicalOrExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.11 (b)
	id l;
	l=[self _logicalAndExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"||" intoString:NULL])
			{ // logical or operator
			l=[_WebScriptTreeNodeLogical node:l :[self _logicalAndExpressionWithScanner:sc noIn:flag]];
			((_WebScriptTreeNodeLogical *)l)->op=LOr;
			}
		else
			break;	// done
		}
	return l;
}

+ (id) _conditionalExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.12
	id l, r;
	l=[self _logicalOrExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	[self _skipComments:sc];
	if([sc scanString:@"?" intoString:NULL])
		{ // conditional operator
		r=[self _assignmentExpressionWithScanner:sc noIn:NO];
		[self _skipComments:sc];
		if(![sc scanString:@":" intoString:NULL])
			; // syntax error
		l=[_WebScriptTreeNodeConditional node:l :[self _assignmentExpressionWithScanner:sc noIn:NO]];
		((_WebScriptTreeNodeConditional *)l)->third=[[self _assignmentExpressionWithScanner:sc noIn:flag] retain];
		}
	return l;
}

+ (id) _assignmentExpressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.13
	id l;
	l=[self _conditionalExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
		[self _skipComments:sc];
	if([sc scanString:@"=" intoString:NULL])
		{ // assignment operator - right associative
		l=[_WebScriptTreeNodeAssignment node:l :[self _assignmentExpressionWithScanner:sc noIn:NO]];
		((_WebScriptTreeNodeAssignment *)l)->op=Assign;
		}
	if([sc scanString:@"*=" intoString:NULL])
		{
		l=[_WebScriptTreeNodeAssignment node:l :[self _assignmentExpressionWithScanner:sc noIn:NO]];
		((_WebScriptTreeNodeAssignment *)l)->op=MultAssign;
		}
	// etc.
	return l;
}

+ (id) _expressionWithScanner:(NSScanner *) sc noIn:(BOOL) flag;
{ // 11.14
	id l;
	l=[self _assignmentExpressionWithScanner:sc noIn:flag];
	if([l _isKeyword])
		return l;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc scanString:@"," intoString:NULL])
			{ // comma operator
			l=[_WebScriptTreeNodeComma node:l :[self _assignmentExpressionWithScanner:sc noIn:flag]];
			}
		else
			break;
		}
	return l;
}

/* statements 12. */

/// FIXME !!!

+ (id) _statementOrBlockWithScanner:(NSScanner *) sc;
{
	id r=nil;
	[self _skipComments:sc];
	if([sc scanString:@"{" intoString:NULL])
		{ // statement list
			// while not } or EOF
		r=[self _statementOrBlockWithScanner:sc];
		}
	else
		r=[self _statementWithScanner:sc];	// single statement
	return r;
}

+ (id) _statementWithScanner:(NSScanner *) sc;
{
	id r=nil;
	[self _skipComments:sc];
	if([sc scanString:@"{" intoString:NULL])
		{ // statement list
		// while not } or EOF
		r=[self _programWithScanner:sc];
		}
	r=[self _assignmentExpressionWithScanner:sc noIn:NO];	// simple expression may also return a keyword
	if([r _isKeyword:@"var"])
		{ // variable declaration
		}
	else if([r _isKeyword:@"if"])
		{
		}
	else if([r _isKeyword:@"for"])
		{
		}
	else if([r _isKeyword:@"while"])
		{
		}
	else if([r _isKeyword:@"return"])
		{ // 12.9
		}
	else if([r _isKeyword:@"with"])
		{ // 12.10
		id l;
		[self _skipComments:sc];
		if(![sc scanString:@"(" intoString:NULL])
			;	// missing (
		l=[self _expressionWithScanner:sc noIn:NO];
		if(![sc scanString:@")" intoString:NULL])
			;	// missing )
		r=[_WebScriptTreeNodeWith node:l :[self _expressionWithScanner:sc noIn:NO]];
		}
  // etc.
	[self _skipComments:sc];
	[sc scanString:@";" intoString:NULL];	// skip if present
	return r;
}

/* functions 13. */

+ (id) _functionExpressionWithScanner:(NSScanner *) sc;
{
	return NIMP;
}

/* program 14. */

+ (id) _programWithScanner:(NSScanner *) sc;
{
	id r=nil;
	while(YES)
		{
		[self _skipComments:sc];
		if([sc isAtEnd])
			break;
		r=[self _statementWithScanner:sc];
		// collect statement(s)
		}
	return r;
}

/* *** */

+ (id) node:(id) l :(id) r;
{ // create a new node (not released)
	_WebScriptTreeNode *n=[self alloc];
	n->left=[l retain];
	n->right=[r retain];
	return n;
}

- (void) dealloc;
{
	[left release];
	[right release];
	[super dealloc];
}

- (id) _evaluate;
{
	return NIMP;
}

- (id) evaluateWithScope:(id) scope activation:(WebScriptObject *) activationObject this:(WebScriptObject *) this;
{
	// establish the scope chain
	return nil;
}

- (id) evaluateWithGlobalObjects:(NSDictionary *) objects;
{ // evaluate in given context
	// create the global object
	// populate with Array, Date, Math, String etc.
	// populate with external objects (e.g. windows)
	// create activation object
	// evaluate
	return nil;
}

@end

@implementation _WebScriptTreeNodeArrayLiteralConstructor

- (id) _evaluate;
{
	return nil;
}

@end

@implementation _WebScriptTreeNodeObjectLiteralConstructor

- (id) _evaluate;
{
	return nil;
}

@end

@implementation _WebScriptTreeNodeThis

- (id) _evaluate;
{
	// FIXME: get this from current execution context
	return nil;	// 11.1.1
}

@end

@implementation _WebScriptTreeNodeReference

- (NSString *) description;
{
	return [NSString stringWithFormat:@"%@: base.%@", [super description], right];
}

- (WebScriptObject *) getBase; { return left; }
- (NSString *) getPropertyName; { return right; }

- (id) getValue;
{ // 8.7.1
	if(left)
		return [left _get:right];
	// ?? shouldn't we reference the this object here or how is this done?
	// throw ReferenceError
	return nil;
}

- (void) putValue:(id) val;
{ // 8.7.2
	if(left)
		[left _put:right value:val];
	// FIXME: what is the global object?
	//	[globalObject _put:propertyName value:val];
}

- (BOOL) _isReference; { return YES; }

@end

@implementation _WebScriptTreeNodeKeyword

- (NSString *) description;
{
	return [NSString stringWithFormat:@"%@: %@", [super description], left];
}

- (id) getValue;
{
	// throw SyntaxError
	return nil;
}

- (void) putValue:(id) val;
{
	// throw SyntaxError
}

- (BOOL) _isKeyword; { return YES; }
- (BOOL) _isKeyword:(NSString *) str; { return [left isEqualToString:str]; }

@end

@implementation _WebScriptTreeNodeNew

- (id) _evaluate;
{
	NSArray *arglist=nil;
	if(![left isKindOfClass:isa])
		; // throw TypeErrorException
	return [left _construct:arglist];
}

@end

@implementation _WebScriptTreeNodeCall

- (id) _evaluate;
{
	id fn, l=nil, this=nil;
	fn=[left _getValue];	// 3.
// for all arguments:	arg=[arg _getValue];
	if(![fn isKindOfClass:[WebScriptObject class]])
		; // 4. throw TypeErrorException
	if(![l respondsToSelector:@selector(_call:)])
		; // 5. throw TypeErrorException
	if([fn _isReference])
		{ // 6. & 7.
		id base=[(_WebScriptTreeNodeReference *) l getBase];
		// FIXME
		//				if(![base isKindOfClass:[WebScriptActivationObject class]])
		//					this=base;
		}
	return [fn _call:this arguments:/* should apply _getValue to all arguments first! */ right];	// 8. & 9.
}

@end

@implementation _WebScriptTreeNodeIndex

- (id) _evaluate;
{
	id l, r;
	l=[left _getValue];
	r=[right _getValue];
	l=[l _toObject];
	r=[r _toString];
	return [_WebScriptTreeNodeReference node:l :r];	// make it a dynamic reference
}

@end

@implementation _WebScriptTreeNodePostfix

- (id) _evaluate;
{
	id r;
	r=[[left _getValue] _toNumber];
	switch(op)
		{
		default:
		case PlusPlus:
			// add 1
			break;
		case MinusMinus:
			// subtract 1
			break;
		}
	[left _putValue:r];
	return left; 
}

@end

@implementation _WebScriptTreeNodeUnary

- (id) _evaluate;
{
	id r, l;
	switch(op)
		{
		default:
		case UPlusPlus:
			r=[right _evaluate];			
			l=[[r _getValue] _toNumber];
			// add 1 to l
			[r _putValue:l];
			return l;
		case UMinusMinus:
			r=[right _evaluate];			
			l=[[r _getValue] _toNumber];
			// subtract 1 fm l
			[r _putValue:l];
			return l;
		case Plus:
			return [[[right _evaluate] _getValue] _toNumber];
		case Minus:
			r=[[[right _evaluate] _getValue] _toNumber];
			// if NaN return l
			return [NSNumber numberWithDouble:-[r doubleValue]];
		case Neg:
			r=[[[right _evaluate] _getValue] _toInt32];
			return [NSNumber numberWithDouble:(double) ~[r intValue]];
		case Not:
			r=[[[right _evaluate] _getValue] _toBoolean];
			return [NSNumber numberWithDouble:(double) ![r boolValue]];
		case Delete:
			{
				r=[right _evaluate];			
				if(![r _isReference])
					return [NSNumber numberWithBool:YES];
				return [NSNumber numberWithBool:[[r getBase] _delete:[r getPropertyName]]];
			}
		case Void:
			{
				[[right _evaluate] _getValue];	// evaluate
				return [WebUndefined undefined];										// throw away
			}
		case Typeof:
			{
			r=[right _evaluate];
			if([r _isReference] && ![r getBase])
				return [WebUndefined undefined];
			r=[r _getValue];
			if([r isKindOfClass:[WebUndefined class]])
				return @"undefined";
			if([r isKindOfClass:[NSNull class]])
				return @"object";
			if([r isKindOfClass:[[NSNumber numberWithBool:NO] class]])
				return @"boolean";
			if([r isKindOfClass:[NSNumber class]])
				return @"number";
			if([r isKindOfClass:[NSString class]])
				return @"string";
			if([r isKindOfClass:[WebScriptObject class]])
				{
				if([r respondsToSelector:@selector(_call:arguments:)])
					return @"function";
				return @"object";
				}
			return NSStringFromClass([r class]);	// implementation-dependent
			}
		}
}

@end

@implementation _WebScriptTreeNodeMultiplicative

- (id) _evaluate;
{
	id r, l;
	switch(op)
		{
		default:
		case Mult:
			{
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				l=[l _toNumber];
				r=[r _toNumber];
				return [NSNumber numberWithDouble:[l doubleValue]*[r doubleValue]];
			}
		case Div:
			{
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				l=[l _toNumber];
				r=[r _toNumber];
				return [NSNumber numberWithDouble:[l doubleValue]/[r doubleValue]];
			}
		case Mod:
			{
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				l=[l _toNumber];
				r=[r _toNumber];
				return [NSNumber numberWithDouble:fmod([l doubleValue], [r doubleValue])];
			}
		}
}

@end

@implementation _WebScriptTreeNodeAdditive

+ (id) _abstractAdd:(id) l and:(id) r;
{ // with type conversion
	l=[l _toPrimitive:[NSString class]];	// 5.
	r=[r _toPrimitive:[NSString class]];	// 6.
	if([l isKindOfClass:[NSString class]] || [r isKindOfClass:[NSString class]])
				{ // make a string concatenate
				l=[l _toString];	// 12.
				r=[r _toString];	// 13.
				return [l stringByAppendingString:r];	// 14. & 15.
				}
	else
				{
				l=[l _toNumber];	// 8.
				r=[r _toNumber];	// 9.
				return [NSNumber numberWithDouble:[l doubleValue]+[r doubleValue]];	// 10. & 11.
				}
}

- (id) _evaluate;
{
	id r, l;
	switch(op)
		{
		default:
		case Add:
			{
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				return [isa _abstractAdd:l and:r];
			}
		case Sub:
			{
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				l=[l _toNumber];
				r=[r _toNumber];
				return [NSNumber numberWithDouble:[l doubleValue]-[r doubleValue]];
			}
		}
}

@end

@implementation _WebScriptTreeNodeShift

- (id) _evaluate;
{
	id r, l;
	switch(op)
		{
		default:
		case Shl:
			{ // 11.7.1
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				l=[l _toInt32];
				r=[r _toUint32];
				return [NSNumber numberWithDouble:(double) ([l intValue] << ([r intValue]&0x1f))];
			}
		case Shr:
			{ // 11.7.2
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				l=[l _toInt32];
				r=[r _toUint32];
				return [NSNumber numberWithDouble:(double) ([l intValue] >> ([r intValue]&0x1f))];
			}
		case UShr:
			{ // 11.7.3
				l=[left _evaluate];
				l=[l _getValue];
				r=[right _evaluate];
				r=[r _getValue];
				l=[l _toInt32];
				r=[r _toUint32];
				return [NSNumber numberWithDouble:(double) (((unsigned)[l intValue]) >> ([r intValue]&0x1f))];
			}
		}
}

@end

@implementation _WebScriptTreeNodeRelational

- (id) _evaluate;
{ // 10.8.5
	id r, l;
	l=[left _evaluate];
	l=[l _getValue];
	r=[right _evaluate];
	r=[r _getValue];
	switch(op)
		{
		case InstanceOf:
			{
				// if r is not an object -> TypeError	// 5.
				// if r does not have _hasInstance -> TypeError -- add to default implementation of _hasInstance:
				return [NSNumber numberWithBool:[r _hasInstance:l]];	// 7. & 8.
			}
		case In:
			{
				// if r is not an object -> TypeError	// 5.
				l=[l _toString];	// 6.
				return [NSNumber numberWithBool:[r _hasProperty:l]];	// 7. & 8.
			}
		default:
			{
				// implement conversion rules...
				// if undefined -> return [NSNumber numberWithBool:NO];
				switch(op)
					{
					default:	// there is no GreaterThan since we swap the arguments during parsing
					case LessThan:
						{
							return [NSNumber numberWithBool:[l doubleValue] < [r doubleValue]];
						}
					case LessEqual:
						{
							return [NSNumber numberWithBool:[l doubleValue] <= [r doubleValue]];
						}
					}
			}
		}
}

@end

@implementation _WebScriptTreeNodeEquality

- (id) _evaluate;
{
	id r, l;
	l=[left _evaluate];
	l=[l _getValue];
	r=[right _evaluate];
	r=[r _getValue];
	if(!strict)
		{ // 11.9.3
		return [NSNumber numberWithBool:YES];
		}
	else
		{
		// FIXME: add rules of 11.9.6
			// if different type -> flag
			// if l is undefined -> !flag
			// if l is null -> !flag
			// check for strings ->
			// compare numbers
			// or strings
			// compare objects (same object or objects joined: 13.1.2)
		return [NSNumber numberWithBool:equal];		
		}
}

@end

@implementation _WebScriptTreeNodeBitwise

- (id) _evaluate;
{
	id r, l;
	l=[left _evaluate];
	l=[l _getValue];
	r=[right _evaluate];
	r=[r _getValue];
	l=[l _toUint32];
	r=[r _toUint32];
	switch(op)
		{
		default:
		case And:
			{
				return [NSNumber numberWithDouble:(double) (unsigned) ([l intValue] & [r intValue])];
			}
		case Xor:
			{
				return [NSNumber numberWithDouble:(double) (unsigned) ([l intValue] ^ [r intValue])];
			}
		case Or:
			{
				return [NSNumber numberWithDouble:(double) (unsigned) ([l intValue] | [r intValue])];
			}
		}
}

@end

@implementation _WebScriptTreeNodeLogical

- (id) _evaluate;
{ // 11.11
	id r, l;
	l=[left _evaluate];
	l=[l _getValue];
	l=[l _toBoolean];
	switch(op)
		{
		default:
		case LAnd:
			{
				if([l boolValue])
					return [right _evaluate];
				return l;
			}
		case LOr:
			{
				if(![l boolValue])
					return [right _evaluate];
				return l;
			}
		}
}

@end

@implementation _WebScriptTreeNodeConditional

- (void) dealloc;
{
	[third release];
	[super dealloc];
}

- (id) _evaluate;
{ // 11.12
	id l;
	l=[left _evaluate];
	l=[l _toBoolean];
	if([l boolValue])
		return [right _evaluate];
	else
		return [third _evaluate];
}

@end

@implementation _WebScriptTreeNodeAssignment

- (id) _evaluate;
{ // 11.13
	id r, l;
	r=[r _getValue];
	switch(op)
		{
		default:
		case Assign:
			{ // plain assignment
				[left _putValue:r];	// must be a reference...
				return left;
			}
			// add others
			// need to current _getValue
			// operate on it
			// _putValue back
		}
}

@end

@implementation _WebScriptTreeNodeComma	// comma operator

- (id) _evaluate;
{ // 11.14
	// ignore left - or must we evaluate and call _getValue???
	return [right _evaluate];
}

@end

@implementation _WebScriptTreeNodeIf

- (void) dealloc;
{
	[third release];
	[super dealloc];
}

- (id) _evaluate;
{
	id l;
	l=[left _evaluate];
	l=[l _toBoolean];
	if([l boolValue])
		return [right _evaluate];
	else
		return [third _evaluate];	// return nil if not present!?
}

@end

@implementation _WebScriptTreeNodeWith

- (id) _evaluate;
{
	id l, r;
	l=[left _evaluate];
	l=[[l _getValue] _toObject];	// 2. & 3.
																// add l to the front of the scope chain
	NS_DURING
		r=[right _evaluate];
	NS_HANDLER
		r=nil;	// (trow, localException, empty)
	NS_ENDHANDLER
	// remove from the front of the scope chain	// 7.
	return r;
}

@end

@implementation WebUndefined

+ (id) _alloc; { return [super alloc]; }
+ (id) alloc; { return NIMP; }	// don't call explicitly...

+ (WebUndefined *) undefined
{ // constant like NSNull but used by WebScript
	static WebUndefined *undef=nil;
	if(!undef)
		undef=[[self _alloc] init];
	return undef;
}

- (NSString *) description;
{
	return @"undefined";
}

@end