瀏覽代碼

Implementing Date, Math and unary expressions

Sebastien Ros 12 年之前
父節點
當前提交
4c24792b1d

+ 19 - 0
Jint.Tests/Runtime/EngineTests.cs

@@ -235,6 +235,25 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void DateConstructor()
+        {
+            RunTest(@"
+                var o = new Date();
+                assert(o.constructor == Date);
+                assert(o.hasOwnProperty('constructor') == false);
+            ");
+        }
+
+        [Fact]
+        public void MathObjectIsDefined()
+        {
+            RunTest(@"
+                var o = Math.abs(-1)
+                assert(o == 1);
+            ");
+        }
+
         [Fact]
         public void Scratch()
         {

+ 11 - 1
Jint/Engine.cs

@@ -1,10 +1,11 @@
 using System;
 using System.Collections.Generic;
-using Jint.Native;
 using Jint.Native.Array;
 using Jint.Native.Boolean;
+using Jint.Native.Date;
 using Jint.Native.Errors;
 using Jint.Native.Function;
+using Jint.Native.Math;
 using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.String;
@@ -43,6 +44,8 @@ namespace Jint
             String = new StringConstructor(this);
             Number = new NumberConstructor(this);
             Boolean = new BooleanConstructor(this);
+            Date = new DateConstructor(this);
+            Math = MathInstance.CreateMathObject(this, RootObject);
 
             Global.Set("Object", Object);
             Global.Set("Function", Function);
@@ -50,6 +53,8 @@ namespace Jint
             Global.Set("String", String);
             Global.Set("Number", Number);
             Global.Set("Boolean", Boolean);
+            Global.Set("Date", Date);
+            Global.Set("Math", Math);
 
             // create the global environment http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.3
             _globalEnvironment = LexicalEnvironment.NewObjectEnvironment(Global, null);
@@ -86,6 +91,8 @@ namespace Jint
         public StringConstructor String { get; private set; }
         public BooleanConstructor Boolean { get; private set; }
         public NumberConstructor Number { get; private set; }
+        public DateConstructor Date { get; private set; }
+        public MathInstance Math { get; private set; }
 
         public ExecutionContext CurrentExecutionContext { get { return _executionContexts.Peek(); } }
 
@@ -240,6 +247,9 @@ namespace Jint
                 case SyntaxNodes.UpdateExpression:
                     return _expressions.EvaluateUpdateExpression(expression.As<UpdateExpression>());
 
+                case SyntaxNodes.UnaryExpression:
+                    return _expressions.EvaluateUnaryExpression(expression.As<UnaryExpression>());
+
                 default:
                     throw new ArgumentOutOfRangeException();
             }

+ 6 - 0
Jint/Jint.csproj

@@ -41,14 +41,19 @@
     <Compile Include="Native\Array\ArrayInstance.cs" />
     <Compile Include="Native\Boolean\BooleanConstructor.cs" />
     <Compile Include="Native\Boolean\BooleanInstance.cs" />
+    <Compile Include="Native\Date\DateConstructor.cs" />
+    <Compile Include="Native\Date\DateInstance.cs" />
+    <Compile Include="Native\Errors\SyntaxError.cs" />
     <Compile Include="Native\Errors\ReferenceError.cs" />
     <Compile Include="Native\Errors\TypeError.cs" />
     <Compile Include="Native\Function\FunctionConstructor.cs" />
     <Compile Include="Native\Function\FunctionInstance.cs" />
     <Compile Include="Native\Function\FunctionShim.cs" />
+    <Compile Include="Native\ICallable.cs" />
     <Compile Include="Native\Function\ScriptFunctionInstance.cs" />
     <Compile Include="Native\IConstructor.cs" />
     <Compile Include="Native\IPrimitiveType.cs" />
+    <Compile Include="Native\Math\MathInstance.cs" />
     <Compile Include="Native\Null.cs" />
     <Compile Include="Native\Number\NumberConstructor.cs" />
     <Compile Include="Native\Number\NumberInstance.cs" />
@@ -113,6 +118,7 @@
     <Compile Include="Parser\State.cs" />
     <Compile Include="Parser\Token.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Runtime\Arguments.cs" />
     <Compile Include="Runtime\Descriptors\AccessorDescriptor.cs" />
     <Compile Include="Runtime\Descriptors\DataDescriptor.cs" />
     <Compile Include="Runtime\Descriptors\PropertyDescriptor.cs" />

+ 3 - 2
Jint/Native/Array/ArrayConstructor.cs

@@ -20,8 +20,9 @@ namespace Jint.Native.Array
             // Array prototype properties
             this.Prototype.DefineOwnProperty("length", new ClrAccessDescriptor<ArrayInstance>(_engine, x => x.Length), false);
 
-            this.Prototype.DefineOwnProperty("push", new ClrDataDescriptor<ArrayInstance>(engine, Push), false);
-            this.Prototype.DefineOwnProperty("pop", new ClrDataDescriptor<ArrayInstance>(engine, Pop), false);
+            // Array prototype functions
+            this.Prototype.DefineOwnProperty("push", new ClrDataDescriptor<ArrayInstance, object>(engine, Push), false);
+            this.Prototype.DefineOwnProperty("pop", new ClrDataDescriptor<ArrayInstance, object>(engine, Pop), false);
         }
 
         public override object Call(object thisObject, object[] arguments)

+ 82 - 0
Jint/Native/Date/DateConstructor.cs

@@ -0,0 +1,82 @@
+using System;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.Date
+{
+    public sealed class DateConstructor : FunctionInstance, IConstructor
+    {
+        private readonly Engine _engine;
+
+        public DateConstructor(Engine engine)
+            : base(engine, new ObjectInstance(engine.Object), null, null)
+        {
+            _engine = engine;
+
+            // the constructor is the function constructor of an object
+            this.Prototype.DefineOwnProperty("constructor", new DataDescriptor(this) { Writable = true, Enumerable = false, Configurable = false }, false);
+            this.Prototype.DefineOwnProperty("prototype", new DataDescriptor(Prototype) { Writable = true, Enumerable = false, Configurable = false }, false);
+
+        }
+
+        public override object Call(object thisObject, object[] arguments)
+        {
+            return Construct(arguments);
+        }
+
+        /// <summary>
+        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.3
+        /// </summary>
+        /// <param name="arguments"></param>
+        /// <returns></returns>
+        public ObjectInstance Construct(object[] arguments)
+        {
+            if (arguments.Length == 0)
+            {
+                return Construct(DateTime.UtcNow);
+            }
+            else if (arguments.Length == 1)
+            {
+                var v = TypeConverter.ToPrimitive(arguments[0]);
+                if (TypeConverter.GetType(v) == TypeCode.String)
+                {
+                    var d = DateTime.Parse(TypeConverter.ToString(v));
+                    return Construct(d);
+                }
+
+                v = TypeConverter.ToNumber(v);
+
+                // todo: implement TimeClip
+                return Construct(new DateTime(TypeConverter.ToUint32(v)));
+            }
+            else
+            {
+                var y = TypeConverter.ToNumber(arguments[0]);
+                var m = TypeConverter.ToInteger(arguments[0]);
+                var date = arguments.Length > 2 ? TypeConverter.ToInteger(arguments[2]) : 1;
+                var hours = arguments.Length > 3 ? TypeConverter.ToInteger(arguments[3]) : 0;
+                var minutes = arguments.Length > 4 ? TypeConverter.ToInteger(arguments[4]) : 0;
+                var seconds = arguments.Length > 5 ? TypeConverter.ToInteger(arguments[5]) : 0;
+                var ms = arguments.Length > 6 ? TypeConverter.ToInteger(arguments[6]) : 0;
+
+                if ((!double.IsNaN(y)) && (0 <= TypeConverter.ToInteger(y)) && (TypeConverter.ToInteger(y) <= 99))
+                {
+                    y += 1900;
+                }
+
+                return Construct(new DateTime((int) y, m + 1, date, hours, minutes, seconds, ms, DateTimeKind.Utc));
+            }
+        }
+
+        public DateInstance Construct(DateTime value)
+        {
+            var instance = new DateInstance(Prototype);
+            instance.PrimitiveValue = value;
+            return instance;
+        }
+
+
+    }
+}

+ 33 - 0
Jint/Native/Date/DateInstance.cs

@@ -0,0 +1,33 @@
+using System;
+using Jint.Native.Object;
+
+namespace Jint.Native.Date
+{
+    public sealed class DateInstance : ObjectInstance, IPrimitiveType
+    {
+        public DateInstance(ObjectInstance prototype)
+            : base(prototype)
+        {
+        }
+
+        public override string Class
+        {
+            get
+            {
+                return "Date";
+            }
+        }
+
+        TypeCode IPrimitiveType.TypeCode
+        {
+            get { return TypeCode.DateTime; }
+        }
+
+        object IPrimitiveType.PrimitiveValue
+        {
+            get { return PrimitiveValue; }
+        }
+
+        public DateTime PrimitiveValue { get; set; }
+    }
+}

+ 8 - 0
Jint/Native/Errors/SyntaxError.cs

@@ -0,0 +1,8 @@
+using System;
+
+namespace Jint.Native.Errors
+{
+    public class SyntaxError : Exception
+    {
+    }
+}

+ 2 - 3
Jint/Native/Function/FunctionInstance.cs

@@ -4,7 +4,7 @@ using Jint.Runtime.Environments;
 
 namespace Jint.Native.Function
 {
-    public abstract class FunctionInstance : ObjectInstance
+    public abstract class FunctionInstance : ObjectInstance, ICallable
     {
         private readonly Engine _engine;
 
@@ -18,8 +18,7 @@ namespace Jint.Native.Function
         /// <summary>
         /// Executed when a function object is used as a function
         /// </summary>
-        /// <param name="interpreter"></param>
-        /// <param name="state"></param>
+        /// <param name="thisObject"></param>
         /// <param name="arguments"></param>
         /// <returns></returns>
         public abstract object Call(object thisObject, object[] arguments);

+ 7 - 0
Jint/Native/ICallable.cs

@@ -0,0 +1,7 @@
+namespace Jint.Native
+{
+    public interface ICallable
+    {
+        object Call(object thisObject, object[] arguments);
+    }
+}

+ 50 - 0
Jint/Native/Math/MathInstance.cs

@@ -0,0 +1,50 @@
+using System;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors.Specialized;
+
+namespace Jint.Native.Math
+{
+    public sealed class MathInstance : ObjectInstance, IPrimitiveType
+    {
+        public MathInstance(ObjectInstance prototype)
+            : base(prototype)
+        {
+        }
+
+        public override string Class
+        {
+            get
+            {
+                return "Math";
+            }
+        }
+
+        TypeCode IPrimitiveType.TypeCode
+        {
+            get { return TypeCode.DateTime; }
+        }
+
+        object IPrimitiveType.PrimitiveValue
+        {
+            get { return PrimitiveValue; }
+        }
+
+        public DateTime PrimitiveValue { get; set; }
+
+        public static MathInstance CreateMathObject(Engine engine, ObjectInstance prototype)
+        {
+            var math = new MathInstance(prototype);
+            math.DefineOwnProperty("abs", new ClrDataDescriptor<MathInstance, double>(engine, Abs), false);
+
+            return math;
+        }
+
+        private static double Abs(MathInstance thisObject, object[] arguments)
+        {
+            var x = TypeConverter.ToNumber(arguments[0]);
+            return System.Math.Abs(x);
+        }
+
+    }
+}

+ 5 - 7
Jint/Native/Object/ObjectConstructor.cs

@@ -1,9 +1,7 @@
-using System;
-using Jint.Native.Function;
+using Jint.Native.Function;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors.Specialized;
-using Jint.Runtime.Interop;
 
 namespace Jint.Native.Object
 {
@@ -14,8 +12,8 @@ namespace Jint.Native.Object
         public ObjectConstructor(Engine engine) : base(engine, engine.RootFunction, null, null)
         {
             _engine = engine;
-            engine.RootFunction.DefineOwnProperty("hasOwnProperty", new ClrDataDescriptor<ObjectInstance>(engine, HasOwnProperty), false);
-            engine.RootFunction.DefineOwnProperty("toString", new ClrDataDescriptor<ObjectInstance>(engine, ToString), false);
+            engine.RootFunction.DefineOwnProperty("hasOwnProperty", new ClrDataDescriptor<ObjectInstance, bool>(engine, HasOwnProperty), false);
+            engine.RootFunction.DefineOwnProperty("toString", new ClrDataDescriptor<ObjectInstance, string>(engine, ToString), false);
         }
 
         public override object Call(object thisObject, object[] arguments)
@@ -33,14 +31,14 @@ namespace Jint.Native.Object
             return instance;
         }
 
-        private static object HasOwnProperty(ObjectInstance thisObject, object[] arguments)
+        private static bool HasOwnProperty(ObjectInstance thisObject, object[] arguments)
         {
             var propertyName = TypeConverter.ToString(arguments[0]);
             var desc = thisObject.GetOwnProperty(propertyName);
             return desc != PropertyDescriptor.Undefined;
         }
 
-        private static object ToString(ObjectInstance thisObject, object[] arguments)
+        private static string ToString(ObjectInstance thisObject, object[] arguments)
         {
             if (thisObject == null || thisObject == Undefined.Instance)
             {

+ 60 - 3
Jint/Native/Object/ObjectInstance.cs

@@ -1,5 +1,8 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using Jint.Native.Errors;
+using Jint.Native.String;
+using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 
 namespace Jint.Native.Object
@@ -276,10 +279,64 @@ namespace Jint.Native.Object
         /// Hint is a String. Returns a default value for the 
         /// object.
         /// </summary>
-        /// <param name="hintError"></param>
+        /// <param name="hint"></param>
         /// <returns></returns>
-        public object DefaultValue(string hintError)
+        public object DefaultValue(TypeCode hint)
         {
+            if ((hint == TypeCode.String) || (hint == TypeCode.Empty && this is StringInstance))
+            {
+                var toString = this.Get("toString");
+                var callable = toString as ICallable;
+                if (callable != null)
+                {
+                    var str = callable.Call(this, Arguments.Empty);
+                    if (str is IPrimitiveType)
+                    {
+                        return str;
+                    }
+                }
+
+                var valueOf = this.Get("valueOf");
+                callable = valueOf as ICallable;
+                if (callable != null)
+                {
+                    var val = callable.Call(this, Arguments.Empty);
+                    if (val is IPrimitiveType)
+                    {
+                        return val;
+                    }
+                }
+
+                throw new TypeError();
+            }
+
+            if ((hint == TypeCode.Double) || (hint == TypeCode.Empty))
+            {
+                var valueOf = this.Get("valueOf");
+                var callable = valueOf as ICallable;
+                if (callable != null)
+                {
+                    var val = callable.Call(this, Arguments.Empty);
+                    if (val is IPrimitiveType)
+                    {
+                        return val;
+                    }
+                }
+
+                var toString = this.Get("toString");
+                callable = toString as ICallable;
+                if (callable != null)
+                {
+                    var str = callable.Call(this, Arguments.Empty);
+                    if (str is IPrimitiveType)
+                    {
+                        return str;
+                    }
+                }
+
+                throw new TypeError();
+            }
+
             return ToString();
         }
 

+ 1 - 1
Jint/Parser/Ast/UnaryExpression.cs

@@ -3,7 +3,7 @@ namespace Jint.Parser.Ast
     public class UnaryExpression : Expression
     {
         public string Operator;
-        public SyntaxNode Argument;
+        public Expression Argument;
         public bool Prefix;
     }
 }

+ 1 - 1
Jint/Parser/JavascriptParser.cs

@@ -1683,7 +1683,7 @@ namespace Jint.Parser
                 };
         }
 
-        public UnaryExpression CreateUnaryExpression(string op, SyntaxNode argument)
+        public UnaryExpression CreateUnaryExpression(string op, Expression argument)
         {
             if (op == "++" || op == "--")
             {

+ 12 - 0
Jint/Runtime/Arguments.cs

@@ -0,0 +1,12 @@
+namespace Jint.Runtime
+{
+    public static class Arguments
+    {
+        public static object[] Empty = new object[0];
+
+        public static object[] From(params object[] o)
+        {
+            return o;
+        }
+    }
+}

+ 3 - 3
Jint/Runtime/Descriptors/Specialized/ClrDataDescriptor.cs

@@ -3,10 +3,10 @@ using Jint.Runtime.Interop;
 
 namespace Jint.Runtime.Descriptors.Specialized
 {
-    public sealed class ClrDataDescriptor<T> : DataDescriptor
+    public sealed class ClrDataDescriptor<TObject, TResult> : DataDescriptor
     {
-        public ClrDataDescriptor(Engine engine, Func<T, object[], object> func) 
-            : base(new ClrFunctionInstance<T>(engine, func))
+        public ClrDataDescriptor(Engine engine, Func<TObject, object[], TResult> func)
+            : base(new ClrFunctionInstance<TObject, TResult>(engine, func))
         {
         }
 

+ 126 - 2
Jint/Runtime/ExpressionIntepreter.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Linq;
 using Jint.Native;
+using Jint.Native.Errors;
 using Jint.Native.Function;
 using Jint.Parser.Ast;
 using Jint.Runtime.Descriptors;
@@ -211,7 +212,7 @@ namespace Jint.Runtime
 
         public object EvaluateObjectExpression(ObjectExpression objectExpression)
         {
-            var value = _engine.Object.Construct(new dynamic[0]);
+            var value = _engine.Object.Construct(Arguments.Empty);
 
             foreach (var property in objectExpression.Properties)
             {
@@ -255,7 +256,7 @@ namespace Jint.Runtime
                 identifier, 
                 functionExpression.Parameters.ToArray(), 
                 _engine.Function.Prototype,
-                _engine.Object.Construct(new dynamic[0]),
+                _engine.Object.Construct(Arguments.Empty),
                 LexicalEnvironment.NewDeclarativeEnvironment(_engine.CurrentExecutionContext.LexicalEnvironment)
                 );
         }
@@ -346,5 +347,128 @@ namespace Jint.Runtime
 
             return instance;
         }
+
+        public object EvaluateUnaryExpression(UnaryExpression unaryExpression)
+        {
+            var value = _engine.EvaluateExpression(unaryExpression.Argument);
+            object result;
+            Reference r;
+            int i;
+            double n;
+
+            switch (unaryExpression.Operator)
+            {
+                case "++" :
+                    r = value as Reference;
+                    if(r != null 
+                        && r.IsStrict() 
+                        && (r.GetBase() is EnvironmentRecord )
+                        && (Array.IndexOf(new []{"eval", "arguments"}, r.GetReferencedName()) != -1) )
+                    {
+                        throw new SyntaxError();
+                    }
+
+                    var oldValue = _engine.GetValue(value);
+                    var newValue = TypeConverter.ToNumber(value) + 1;
+                    _engine.SetValue(r, newValue);
+
+                    return unaryExpression.Prefix ? newValue : oldValue;
+                    
+                case "--":
+                    r = value as Reference;
+                    if(r != null 
+                        && r.IsStrict() 
+                        && (r.GetBase() is EnvironmentRecord )
+                        && (Array.IndexOf(new []{"eval", "arguments"}, r.GetReferencedName()) != -1) )
+                    {
+                        throw new SyntaxError();
+                    }
+
+                    oldValue = _engine.GetValue(value);
+                    newValue = TypeConverter.ToNumber(value) - 1;
+                    _engine.SetValue(r, newValue);
+
+                    return unaryExpression.Prefix ? newValue : oldValue;
+                    
+                case "+":
+                    return TypeConverter.ToNumber(_engine.GetValue(value));
+                    
+                case "-":
+                    n = TypeConverter.ToNumber(value);
+                    return double.IsNaN(n) ? double.NaN : n*-1;
+                
+                case "~":
+                    i = TypeConverter.ToInt32(value);
+                    return ~i;
+                
+                case "!":
+                    return !TypeConverter.ToBoolean(value);
+                
+                case "delete":
+                    r = value as Reference;
+                    if (r == null)
+                    {
+                        return true;
+                    }
+                    if (r.IsUnresolvableReference())
+                    {
+                        if (r.IsStrict())
+                        {
+                            throw new SyntaxError();
+                        }
+
+                        return true;
+                    }
+                    if (r.IsPropertyReference())
+                    {
+                        var o = TypeConverter.ToObject(_engine, r.GetBase());
+                        o.Delete(r.GetReferencedName(), r.IsStrict());
+                    }
+                    if (r.IsStrict())
+                    {
+                        throw new SyntaxError();
+                    }
+                    var bindings = r.GetBase() as EnvironmentRecord;
+                    return bindings.DeleteBinding(r.GetReferencedName());
+                
+                case "void":
+                    _engine.GetValue(value);
+                    return Undefined.Instance;
+
+                case "typeof":
+                    r = value as Reference;
+                    if (r != null)
+                    {
+                        
+                    }
+                    if (r.IsUnresolvableReference())
+                    {
+                        return Undefined.Instance;
+                    }
+                    var v = _engine.GetValue(value);
+                    if (v == Undefined.Instance)
+                    {
+                        return "undefined";
+                    }
+                    if (v == Null.Instance)
+                    {
+                        return "object";
+                    }
+                    switch (TypeConverter.GetType(v))
+                    {
+                        case TypeCode.Boolean: return "boolean";
+                        case TypeCode.Double: return "number";
+                        case TypeCode.String: return "string";
+                    }
+                    if (v is ICallable)
+                    {
+                        return "function";
+                    }
+                    return "object";
+
+                default:
+                    throw new ArgumentException();
+            }
+        }
     }
 }

+ 4 - 4
Jint/Runtime/Interop/ClrFunctionInstance.cs

@@ -7,12 +7,12 @@ namespace Jint.Runtime.Interop
     /// <summary>
     /// Wraps a Clr method into a FunctionInstance
     /// </summary>
-    public sealed class ClrFunctionInstance<T> : FunctionInstance
+    public sealed class ClrFunctionInstance<TObject, TResult> : FunctionInstance
     {
         private readonly Engine _engine;
-        private readonly Func<T, object[], object> _func;
+        private readonly Func<TObject, object[], TResult> _func;
 
-        public ClrFunctionInstance(Engine engine, Func<T, object[], object> func)
+        public ClrFunctionInstance(Engine engine, Func<TObject, object[], TResult> func)
             : base(engine, null, null, null)
         {
             _engine = engine;
@@ -24,7 +24,7 @@ namespace Jint.Runtime.Interop
             // initialize Return flag
             _engine.CurrentExecutionContext.Return = Undefined.Instance;
 
-            return _func((T) thisObject, arguments);
+            return _func((TObject) thisObject, arguments);
         }
     }
 }

+ 1 - 1
Jint/Runtime/StatementInterpreter.cs

@@ -134,7 +134,7 @@ namespace Jint.Runtime
                     identifier, 
                     functionDeclaration.Parameters.ToArray(),
                     _engine.Function.Prototype,
-                    _engine.Object.Construct(new dynamic[0]),
+                    _engine.Object.Construct(Arguments.Empty),
                     LexicalEnvironment.NewDeclarativeEnvironment(_engine.CurrentExecutionContext.LexicalEnvironment)
                 )
             );

+ 19 - 2
Jint/Runtime/TypeConverter.cs

@@ -13,9 +13,26 @@ namespace Jint.Runtime
         /// <param name="input"></param>
         /// <param name="preferredType"></param>
         /// <returns></returns>
-        public static object ToPrimitive(object input, TypeCode preferredType)
+        public static object ToPrimitive(object input, TypeCode preferredType = TypeCode.Empty)
         {
-            return Undefined.Instance;
+            if (input == Null.Instance || input == Undefined.Instance)
+            {
+                return input;
+            }
+
+            if (input is IPrimitiveType)
+            {
+                return input;
+            }
+
+            var o = input as ObjectInstance;
+
+            if (o == null)
+            {
+                throw new ArgumentException();
+            }
+
+            return o.DefaultValue(preferredType);
         }
 
         /// <summary>