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 { public class ObjectInstance { public ObjectInstance(ObjectInstance prototype) { Properties = new Dictionary(); Extensible = true; Prototype = prototype; DefineOwnProperty("prototype", new DataDescriptor(prototype), false); } public IDictionary Properties { get; private set; } /// /// The prototype of this object. /// public ObjectInstance Prototype { get; private set; } /// /// If true, own properties may be added to the /// object. /// public bool Extensible { get; set; } /// /// A String value indicating a specification defined /// classification of objects. /// public virtual string Class { get { return "Object"; } } /// /// Returns the value of the named property. /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.3 /// /// /// public virtual object Get(string propertyName) { var desc = GetProperty(propertyName); if (desc == PropertyDescriptor.Undefined) { return Undefined.Instance; } if (desc.IsDataDescriptor()) { return desc.As().Value; } var getter = desc.As().Get; return getter.Call(this, null); } public void Set(string name, object value) { if (!HasProperty(name)) { DefineOwnProperty(name, new DataDescriptor(value) { Configurable = true, Enumerable = true, Writable = true }, false); } else { Put(name, value, false); } } /// /// Returns the Property Descriptor of the named /// own property of this object, or undefined if /// absent. /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.1 /// /// /// public PropertyDescriptor GetOwnProperty(string propertyName) { PropertyDescriptor x; if (Properties.TryGetValue(propertyName, out x)) { PropertyDescriptor d; if (x.IsDataDescriptor()) { d = new DataDescriptor(x.As()); } else { d = new AccessorDescriptor(x.As()); } return d; } return PropertyDescriptor.Undefined; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.2 /// /// /// public PropertyDescriptor GetProperty(string propertyName) { var prop = GetOwnProperty(propertyName); if (prop != PropertyDescriptor.Undefined) { return prop; } if(Prototype == null) { return PropertyDescriptor.Undefined; } return Prototype.GetProperty(propertyName); } /// /// Sets the specified named property to the value /// of the second parameter. The flag controls /// failure handling. /// /// /// /// public void Put(string propertyName, object value, bool throwOnError) { if (!CanPut(propertyName)) { if (throwOnError) { throw new TypeError(); } return; } var ownDesc = GetOwnProperty(propertyName); if (ownDesc.IsDataDescriptor()) { var valueDesc = new DataDescriptor(value); DefineOwnProperty(propertyName, valueDesc, throwOnError); return; } var desc = GetProperty(propertyName); if (desc.IsAccessorDescriptor()) { var setter = desc.As().Set; setter.Call(this, new [] {value}); } else { var newDesc = new DataDescriptor(value) {Writable = true, Enumerable = true, Configurable = true}; DefineOwnProperty(propertyName, newDesc, throwOnError); } } /// /// Returns a Boolean value indicating whether a /// [[Put]] operation with PropertyName can be /// performed. /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.4 /// /// /// public bool CanPut(string propertyName) { var desc = GetOwnProperty(propertyName); if (desc != PropertyDescriptor.Undefined) { if (desc.IsAccessorDescriptor()) { if (desc.As().Set == null) { return false; } return true; } return desc.As().Writable; } if (Prototype == null) { return Extensible; } var inherited = Prototype.GetProperty(propertyName); if (inherited == PropertyDescriptor.Undefined) { return Prototype.Extensible; } if (inherited.IsAccessorDescriptor()) { if (inherited.As().Set == null) { return false; } return true; } if (!Extensible) { return false; } else { return inherited.As().Writable; } } /// /// Returns a Boolean value indicating whether the /// object already has a property with the given /// name. /// /// /// public bool HasProperty(string propertyName) { return GetProperty(propertyName) != PropertyDescriptor.Undefined; } /// /// Removes the specified named own property /// from the object. The flag controls failure /// handling. /// /// /// /// public bool Delete(string propertyName, bool throwOnError) { var desc = GetOwnProperty(propertyName); if (desc == PropertyDescriptor.Undefined) { return true; } if (desc.Configurable) { Properties.Remove(propertyName); return true; } else { if (throwOnError) { throw new TypeError(); } return false; } } /// /// Hint is a String. Returns a default value for the /// object. /// /// /// 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(); } /// /// Creates or alters the named own property to /// have the state described by a Property /// Descriptor. The flag controls failure handling. /// /// /// /// /// public bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError) { var current = GetOwnProperty(propertyName); if (current == PropertyDescriptor.Undefined) { if (!Extensible) { if (throwOnError) { throw new TypeError(); } return false; } else { if (desc.IsGenericDescriptor() || desc.IsDataDescriptor()) { Properties.Add(propertyName, new DataDescriptor(desc.As())); } else { Properties.Add(propertyName, new AccessorDescriptor(desc.As())); } } return true; } // todo: if desc and current are the same, return true if (!current.Configurable) { if (desc.Configurable) { if (throwOnError) { throw new TypeError(); } return false; } if (desc.Enumerable != current.Enumerable) { if (throwOnError) { throw new TypeError(); } return false; } } if (desc.IsGenericDescriptor()) { // ???? } if (current.IsDataDescriptor() != desc.IsDataDescriptor()) { if (!current.Configurable) { if (throwOnError) { throw new TypeError(); } return false; } if (current.IsDataDescriptor()) { // todo: convert to accessor } } else if (current.IsDataDescriptor() && desc.IsDataDescriptor()) { var cd = current.As(); var dd = current.As(); if (!current.Configurable) { if (!cd.Writable && dd.Writable) { if (throwOnError) { throw new TypeError(); } return false; } } } else if (current.IsAccessorDescriptor() && desc.IsAccessorDescriptor()) { var ca = current.As(); var da = current.As(); if (!current.Configurable) { if ( (da.Set != null && da.Set != ca.Set) || (da.Get != null && da.Get != ca.Get)) { if (throwOnError) { throw new TypeError(); } return false; } } } Properties[propertyName] = desc; return true; } } }