/** JavaScript -- DOT NET Javascript Library
* Copyright (C) 2005 John Garrison
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace JavaScript
{
public class TypeMismatchException : Exception
{
public TypeMismatchException( string msg ) : base( msg )
{
}
public TypeMismatchException( ) : base ( "Type Mismatch Exception" )
{
}
}
/// <summary>
///
/// </summary>
public class JsObject
{
public JsObject()
{
//NativeLValue lvalue = new NativeLValue( "length", new NativeValueGet( _LengthGetter ), new NativeValueSet( _LengthSetter ) );
//m_properties.Add( lvalue.Name, lvalue );
}
public virtual List<LValue> Properties
{
get
{
List<LValue> values = new List<LValue>();
foreach (LValue lv in m_properties.Values)
{
values.Add(lv);
}
return values;
}
}
public virtual string Name
{
get
{
return "object";
}
}
public virtual string TypeName
{
get
{
return Name;
}
}
public virtual void Construct(Stack<object> stk, int argCount)
{
if (argCount != 0)
{
throw new ParseError("Object() takes zero parameters");
}
}
public virtual LValue GetPropertyLValueNoCreate( string name )
{
if (m_properties.ContainsKey(name))
{
return m_properties[name];
}
if (name == "toString")
{
TypeNativeFunction nfn = new TypeNativeFunction("toString", new string[] { "val", "rad" }, new JsNativeCall(ToStrFunction));
AddLValue(new LValue(name, nfn));
return m_properties[name];
}
return null;
}
public virtual LValue GetPropertyLValue( string name )
{
if (!m_properties.ContainsKey(name))
{
m_properties.Add( name, new LValue( name, TypeUndefined.Instance ) );
}
if (name == "toString")
{
TypeNativeFunction nfn = new TypeNativeFunction("toString", new string[] { "val", "rad" }, new JsNativeCall(ToStrFunction));
AddLValue(new LValue(name, nfn));
return m_properties[name];
}
Debug.Assert(null != m_properties[name]);
return m_properties[name];
}
public void CreateProperty(string name, JsObject val)
{
LValue lvalue = GetPropertyLValueNoCreate(name);
if (lvalue == null)
{
m_properties[name] = new LValue(name, val);
}
else
{
lvalue.Set(val);
}
}
/// <summary>
/// All objects can have dynamic properties. If the property
/// isn't defined, return TypeUndefined.
/// </summary>
public virtual JsObject GetProperty( string name )
{
LValue lvalue = (LValue)m_properties[name];
if ( lvalue == null )
{
if (name == "toString")
{
TypeNativeFunction nfn = new TypeNativeFunction("toString", new string[] { "val", "rad" }, new JsNativeCall(ToStrFunction));
AddLValue(new LValue(name, nfn));
return nfn;
}
return TypeUndefined.Instance;
}
return lvalue.Get();
}
/// <summary>
/// Set a dynamic property. If the property doesn't exist,
/// create it.
/// </summary>
/// <param name="name"></param>
/// <param name="val"></param>
public virtual void SetProperty( string name, JsObject val )
{
Debug.Assert(null != val);
Debug.Assert(val != this || name == "window", "Property assignment should not resolve to self");
if ( ! m_properties.ContainsKey( name ) )
{
m_properties[name] = new LValue( name );
}
((LValue)m_properties[name]).Set( val );
}
public JsObject Dup()
{
JsObject obj = SubClassDup();
foreach (string key in m_properties.Keys)
{
LValue lvalue = (LValue)m_properties[key];
if (!obj.m_properties.ContainsKey(lvalue.Name))
{
if (lvalue is NativeLValue)
{
obj.m_properties.Add(lvalue.Name, lvalue);
}
else
{
//obj.SetProperty(key, lvalue.Get().Dup());
obj.SetProperty(key, lvalue.Get());
}
}
}
return obj;
}
public virtual JsObject SubClassDup()
{
return new JsObject();
}
/// <summary>
/// All objects except for TypeFunction should throw a type exception here.
/// </summary>
public virtual void PrepareCall( Stack<object> stk, int argCount )
{
throw new TypeMismatchException( "Not a function type" );
}
/// <summary>
/// Attempt to convert to an integer -- throws TypeMismatch if can't
/// </summary>
/// <exception cref="TypeMismatch"></exception>
public virtual JsObject ToInt()
{
return TypeNumber.NAN;
}
/// <summary>
/// Attempt to convert to a double -- throws TypeMismatch if can't
/// </summary>
/// <exception cref="TypeMismatch"></exception>
public virtual JsObject ToDouble()
{
return TypeNumber.NAN;
}
/// <summary>
/// Attempt to converto to a bool -- throws TypeMismatch if can't
/// </summary>
/// <exception cref="TypeMismatch"></exception>
public virtual JsObject ToBool()
{
//throw new TypeMismatchException( );
return TypeBool.True;
}
public virtual JsObject ToJsString()
{
throw new TypeMismatchException( );
}
public override string ToString()
{
StringBuilder props = new StringBuilder();
props.Append(Name);
props.Append('[');
foreach ( string key in m_properties.Keys )
{
LValue lval = (LValue)m_properties[key];
if (lval.Get() == this)
{
continue;
}
if (props.Length > 7)
{
props.Append(",");
}
props.Append(key + "=" + lval.ToString());
}
props.Append(']');
return props.ToString();
}
public virtual JsObject PlusOperator (JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject MinusOperator (JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject TimesOperator (JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject DivOperator (JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject ModOperator (JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject AndOperator( JsObject o)
{
//throw new TypeMismatchException( );
if (o is TypeNull || this is TypeNull || o is TypeUndefined || this is TypeUndefined)
{
return TypeBool.False;
}
return TypeBool.True;
}
public virtual JsObject BitAndOperator( JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject OrOperator( JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject BitOrOperator( JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject XorOperator( JsObject o)
{
throw new TypeMismatchException( );
}
public virtual JsObject OnesComplOperator( )
{
throw new TypeMismatchException( );
}
public virtual TypeBool NotOperator()
{
return TypeBool.False;
}
public virtual TypeBool LessThanOperator(JsObject o)
{
throw new TypeMismatchException( );
}
public virtual TypeBool LessThanEqOperator(JsObject o)
{
throw new TypeMismatchException( );
}
public virtual TypeBool GreaterThanOperator(JsObject o)
{
throw new TypeMismatchException( );
}
public virtual TypeBool GreaterThanEqOperator(JsObject o)
{
throw new TypeMismatchException( );
}
public virtual TypeBool EqualOperator(JsObject o)
{
if (o == TypeNull.Instance)
{
return TypeBool.False;
}
throw new TypeMismatchException( );
}
protected JsObject _LengthGetter(string name)
{
return new TypeInt( m_properties.Count );
}
protected void _LengthSetter(string name, JsObject val)
{
}
protected JsObject ToStrFunction(StackFrame ctx, int argCount)
{
if (argCount == 1)
{
return ctx.RValue("val").ToStr();
}
else if (argCount == 2)
{
int val = ((TypeInt)ctx.RValue("val").ToInt()).IntValue();
int radix = ((TypeInt)ctx.RValue("radix").ToInt()).IntValue();
if (radix == 10)
{
return new TypeString(val.ToString());
}
if (radix == 16)
{
return new TypeString(val.ToString("x"));
}
throw new ParseError("Unsupported radix of " + radix.ToString());
}
else
{
return ToStr();
}
}
protected virtual JsObject ToStr()
{
return new TypeString("[object]");
}
public void AddGlobalFunctions()
{
TypeNativeFunction nfn = new TypeNativeFunction( "parseInt", new string[] {"val", "radix"}, new JsNativeCall( ParseInt ) );
SetProperty( nfn.Name, nfn );
nfn = new TypeNativeFunction( "parseFloat", new string[] {"val"}, new JsNativeCall( ParseFloat ) );
SetProperty( nfn.Name, nfn );
nfn = new TypeNativeFunction( "typeof", new string[] {"val"}, new JsNativeCall( TypeOfFunction ) );
SetProperty( nfn.Name, nfn );
nfn = new TypeNativeFunction("eval", new string[] { "sourcecode" }, new JsNativeCall(EvalFunction));
SetProperty(nfn.Name, nfn);
nfn = new TypeNativeFunction("isNaN", new string[] { "val" }, new JsNativeCall(IsNanFunction));
SetProperty(nfn.Name, nfn);
nfn = new TypeNativeFunction("toString", new string[] { "val", "rad" }, new JsNativeCall(ToStrFunction));
SetProperty(nfn.Name, nfn);
nfn = new TypeNativeFunction("escape", new string[] { "str" }, new JsNativeCall(EscapeFunction));
SetProperty(nfn.Name, nfn);
nfn = new TypeNativeFunction("unescape", new string[] { "str" }, new JsNativeCall(UnescapeFunction));
SetProperty(nfn.Name, nfn);
}
protected JsObject ParseInt( StackFrame ctx, int argCount )
{
int radix = 10;
if ( argCount == 2 )
{
radix = ((TypeInt)ctx.RValue("radix").ToInt()).IntValue();
}
else if( argCount != 1 )
{
throw new ParseError( "parseInt doesn't take " + argCount + " parameters" );
}
JsObject val = ctx.RValue("val");
if ( val is TypeInt || val is TypeDouble )
{
return val.ToInt();
}
string str = val.ToString();
if (str.Length == 0)
{
return TypeNumber.NAN;
}
if (str.Length > 2 && str[0] == '0' && str[1] == 'x')
{
// hex
return new TypeInt(str, 16);
}
if (str.Length > 0 && str[0] == '0')
{
// octal
return new TypeInt(str, 8);
}
if ( Char.IsDigit(str[0]) )
{
int pos = 1;
while (pos < str.Length)
{
if (! Char.IsDigit(str[pos]) )
{
break;
}
pos++;
}
string digits = str.Substring(0, pos);
return new TypeInt(digits, radix);
}
return TypeNumber.NAN;
}
protected JsObject ParseFloat( StackFrame ctx, int argCount )
{
if ( argCount != 1 )
{
throw new ParseError( "parseFloat doesn't take " + argCount + " parameters" );
}
JsObject val = ctx.RValue("val");
if (val is TypeDouble || val is TypeInt)
{
return val.ToDouble();
}
double dval;
string str = val.ToString();
if (Double.TryParse(str, out dval))
{
return new TypeDouble(dval);
}
if (Char.IsDigit(str[0]))
{
int pos = 1;
while (pos < str.Length && (Char.IsDigit(str[pos]) || str[pos] == '.' || str[pos] == 'e'))
{
pos++;
}
str = str.Substring(0, pos);
return new TypeDouble(Double.Parse(str));
}
return TypeNumber.NAN;
}
protected virtual JsObject TypeOf()
{
return new TypeString("object");
}
protected JsObject TypeOfFunction( StackFrame ctx, int argCount )
{
Debug.Assert(argCount == 1);
return ctx.RValue("val").TypeOf();
}
protected JsObject EvalFunction(StackFrame ctx, int argCount)
{
if (argCount == 0)
{
return TypeUndefined.Instance;
}
if (argCount > 1)
{
throw new ParseError("eval only takes one argument");
}
TypeString sourcecode = (TypeString)ctx.LValue("sourcecode").Get();
Parser parser = new Parser(false);
ExecList prog = parser.Parse(sourcecode.ToString());
StackFrame[] frames = ctx.OuterContext.ToArray();
JsInterp interp = new JsInterp(this, prog, false);
JsObject ret = interp.Run(frames[frames.Length-2]);
//return ret;
return TypeUndefined.Instance;
}
protected JsObject IsNanFunction(StackFrame ctx, int argCount)
{
if (argCount != 1)
{
throw new ParseError("isNaN takes one argument");
}
JsObject val = ctx.LValue("val").Get();
if (val is TypeDouble)
{
return new TypeBool( Double.NaN == ((TypeDouble)val).DoubleVal() );
}
if (val is TypeInt)
{
return TypeBool.False;
}
return TypeBool.True;
}
protected JsObject EscapeFunction(StackFrame ctx, int argCount)
{
if (argCount != 1)
{
throw new ParseError("escape takes one argument");
}
JsObject val = ctx.LValue("str").Get();
return new TypeString(Uri.EscapeDataString(val.ToString()).Replace("'", "%27"));
}
protected JsObject UnescapeFunction(StackFrame ctx, int argCount)
{
if (argCount != 1)
{
throw new ParseError("escape takes one argument");
}
JsObject val = ctx.LValue("str").Get();
return new TypeString(Uri.UnescapeDataString(val.ToString()).Replace("%27", "'"));
}
/// <summary>
/// Used by sub classes to add properties
/// </summary>
/// <param name="lvalue">the property to add</param>
protected void AddLValue(LValue lvalue)
{
m_properties[lvalue.Name] = lvalue;
}
public override bool Equals(object obj)
{
if (!(obj is JsObject))
{
return false;
}
return ((TypeBool)this.EqualOperator((JsObject)obj)).Value();
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
internal Dictionary<string, LValue> _Properties
{
get { return m_properties; }
}
protected Dictionary<string, LValue> m_properties = new Dictionary<string,LValue>();
}
}