From 87c12cae60e45388a1f0a10b1005de22776b7f75 Mon Sep 17 00:00:00 2001 From: "graydon@pobox.com" Date: Thu, 26 Apr 2007 20:53:05 -0700 Subject: [PATCH] a little refactoring --HG-- branch : com.mozilla.es4.smlnj extra : convert_revision : 87ef096d5188f45b437f78c1078cf47301f959e3 --- builtins/Conversions.es | 59 +++++++++++-------- eval.sml | 125 ++++++++++++++++++---------------------- mach.sml | 11 ++++ ustring.sml | 8 ++- 4 files changed, 108 insertions(+), 95 deletions(-) diff --git a/builtins/Conversions.es b/builtins/Conversions.es index a1a66dac..88e1cb3c 100644 --- a/builtins/Conversions.es +++ b/builtins/Conversions.es @@ -37,27 +37,14 @@ package throw new TypeError(); } - /* ... */ + /* ES-262-3 9.1: The ToPrimitive operation */ intrinsic function ToPrimitive(value, preferredType) { if (IsPrimitive(value)) return value; return DefaultValue(value, preferredType); } - /* ES-262-3 9.2: The ToBoolean operation */ - intrinsic function ToBoolean(value) : Boolean { - if (value is Boolean) - return value; - - if (value === undefined || value === null) - return false; - if (value is String) - return value !== ""; - if (value is Numeric) - return value !== 0 && value === value; - return true; - } - + /* ES-262-3 9.4: The ToInteger operation */ intrinsic function ToInteger(value) : Number { value = ToDouble(value); if (value !== value) @@ -68,6 +55,39 @@ package return sign * Math.floor(Math.abs(value)); } + /* + * ES-262-3 9.9: ToObject. + * + * ES-262-4 draft: All values except undefined and null are + * already objects, no conversion is necessary. + */ + + intrinsic function ToObject(value) : Object! { + if (value === undefined || value === null) + throw new TypeError("Can't convert undefined or null to Object"); + return value; + } + + /* + * The remaining ES-262-3 9.x primitive conversions are formulated + * in terms of calling the class meta::invoke of the associated + * primitive, which generally calls the primitive constructor and + * thus one of the native magic::bindFoo() primitive conversion + * routines provided by the implementation. + * + * It is done this way because expressing the conversion + * algorithms in ES4 code proved difficult: the code invariably + * fed back on primitive constructors (for literals, control-flow + * booleans, temporaries, etc). This meant that the conversions + * could seldom be called from primitive constructors without + * entering infinite loops. This was unacceptable since the + * conversion algorithms are primarily *intended* to be called + * from the bodies of primitive constructors. + */ + + intrinsic function ToBoolean(value) : boolean + boolean(value); + intrinsic function ToInt(v) : int int(value) @@ -83,14 +103,5 @@ package intrinsic function ToString(x) : string string(x); - /* ES-262-3 9.9: ToObject. - ES-262-4 draft: All values except undefined and null are - already objects, no conversion is necessary. */ - - intrinsic function ToObject(value) : Object! { - if (value === undefined || value === null) - throw new TypeError("Can't convert undefined or null to Object"); - return value; - } } diff --git a/eval.sml b/eval.sml index 28752122..20727ccb 100644 --- a/eval.sml +++ b/eval.sml @@ -447,7 +447,7 @@ and hasValue (obj:Mach.OBJ) (* - * *Similar to* ES3 8.7.1 GetValue(V), there's + * *Similar to* ES-262-3 8.7.1 GetValue(V), there's * no Reference type in ES4. *) and getValueOrVirtual (obj:Mach.OBJ) @@ -870,15 +870,15 @@ and magicToUstring (magic:Mach.MAGIC) case magic of Mach.Double n => if Real64.isFinite n andalso Real64.==(Real64.realFloor n, n) - then Ustring.fromString (LargeInt.toString (Real64.toLargeInt IEEEReal.TO_NEGINF n)) + then Ustring.fromString (Ustring.fixNegatives (LargeInt.toString (Real64.toLargeInt IEEEReal.TO_NEGINF n))) else (if Real64.isNan n then Ustring.NaN_ else (if Real64.==(Real64.posInf, n) then Ustring.Infinity_ else (if Real64.==(Real64.negInf, n) then Ustring.fromString "-Infinity" - else Ustring.fromString (Real64.toString n)))) - + else Ustring.fromString (Ustring.fixNegatives (Real64.toString n))))) + | Mach.Decimal d => Ustring.fromString (Decimal.toString d) | Mach.Int i => Ustring.fromInt32 i | Mach.UInt u => Ustring.fromString (LargeInt.toString (Word32.toLargeInt u)) @@ -901,7 +901,7 @@ and magicToUstring (magic:Mach.MAGIC) | _ => error ["Shouldn't happen: failed to match in Eval.magicToUstring."] (* - * ES3 9.8 ToString. + * ES-262-3 9.8 ToString. * * We do it down here because we have some actual callers who * need it inside the implementation of the runtime. Most of the rest @@ -919,15 +919,14 @@ and toUstring (v:Mach.VAL) in case !(#magic ob) of SOME magic => magicToUstring magic - | NONE => - let - val toPrimitiveFn = needObj (getValue (getGlobalObject ()) Name.intrinsic_ToPrimitive) - val prim = evalCallExpr obj toPrimitiveFn [v, newString Ustring.String_] - in - toUstring prim - end + | NONE => toUstring (callGlobal Name.intrinsic_ToPrimitive + [v, newString Ustring.String_]) end +(* + * ES-262-3 9.2: The ToBoolean operation + *) + and toBoolean (v:Mach.VAL) : bool = case v of Mach.Undef => false @@ -940,7 +939,10 @@ and toBoolean (v:Mach.VAL) : bool = | SOME (Mach.Double x) => not (Real64.==(x,(Real64.fromInt 0)) orelse Real64.isNan x) - | SOME (Mach.Decimal x) => not (x = Decimal.zero) + | SOME (Mach.Decimal x) => not ((x = Decimal.zero) + orelse + (Decimal.isNaN x)) + | SOME (Mach.String s) => not (Ustring.stringLength s = 0) | _ => true) (* @@ -967,7 +969,7 @@ and toNumeric (v:Mach.VAL) | SOME (Mach.Boolean true) => one () (* * FIXME: This is not the correct definition of ToNumber applied to string. - * See ES3 9.3.1. We need to talk it over. + * See ES-262-3 9.3.1. We need to talk it over. *) | SOME (Mach.String us) => let val s = Ustring.toAscii us @@ -977,7 +979,7 @@ and toNumeric (v:Mach.VAL) | NONE => NaN () end (* - * FIXME: ES3 9.3 defines ToNumber on objects in terms of primitives. We've + * FIXME: ES-262-3 9.3 defines ToNumber on objects in terms of primitives. We've * reorganized the classification of primitives vs. objects. Revisit this. *) | _ => zero ()) @@ -1129,7 +1131,7 @@ and sign (v:Mach.VAL) (* * FIXME: this implemented 'sign' function returns 1, 0, or -1 * depending on proximity to 0. Some definitions only return 1 or 0, - * or only return 1 or -1. Don't know which one the ES3 spec means. + * or only return 1 or -1. Don't know which one the ES-262-3 spec means. *) (* FIXME: should decimal rounding mode and precision used in sign-determination? *) @@ -1174,29 +1176,7 @@ and signFloorAbs (v:Mach.VAL) end -(* ES3 9.4 ToInteger - * - * FIXME: If I understand the compatibility requirements - * correctly, this should return an integral double. - * Not certain though. - *) - -and toInteger (v:Mach.VAL) - : Mach.VAL = - let - val v' = toNumeric v - in - if isNaN v' - then newDouble (Real64.fromInt 0) - else (if (isPositiveInf v' orelse - isNegativeInf v' orelse - isPositiveZero v' orelse - isNegativeZero v') - then v' - else newDouble (Real64.fromLargeInt (signFloorAbs v'))) - end - -(* ES3 9.5 ToInt32 *) +(* ES-262-3 9.5 ToInt32 *) and toInt32 (v:Mach.VAL) : Int32.int = @@ -1222,7 +1202,7 @@ and toInt32 (v:Mach.VAL) end -(* ES3 9.6 ToUInt32 *) +(* ES-262-3 9.6 ToUInt32 *) and toUInt32 (v:Mach.VAL) : Word32.word = @@ -1243,7 +1223,7 @@ and toUInt32 (v:Mach.VAL) end end -(* ES3 9.6 ToUInt16 *) +(* ES-262-3 9.6 ToUInt16 *) and toUInt16 (v:Mach.VAL) : Word32.word = @@ -1323,8 +1303,8 @@ and evalExpr (regs:Mach.REGS) val args = map (evalExpr regs) actuals in case func of - Ast.LexicalRef _ => evalCallMethod regs func args - | Ast.ObjectRef _ => evalCallMethod regs func args + Ast.LexicalRef _ => evalCallMethodByExpr regs func args + | Ast.ObjectRef _ => evalCallMethodByExpr regs func args | _ => evalCallExpr (#this regs) (needObj (evalExpr regs func)) args end @@ -1505,7 +1485,7 @@ and constructObjectViaFunction (ctorObj:Mach.OBJ) Mach.Obj { props, ... } => let (* FIXME: the default prototype should be the initial Object prototype, - * as per ES3 13.2.2, not the current Object prototype. *) + * as per ES-262-3 13.2.2, not the current Object prototype. *) val (proto:Mach.VAL) = if Mach.hasProp props Name.public_prototype then getValue ctorObj Name.public_prototype @@ -1534,9 +1514,20 @@ and evalNewExpr (obj:Mach.OBJ) | _ => error ["operator 'new' applied to unknown object"] -and evalCallMethod (regs:Mach.REGS) - (func:Ast.EXPR) - (args:Mach.VAL list) +and callGlobal (n:Ast.NAME) + (args:Mach.VAL list) + : Mach.VAL = + let + val _ = trace ["evaluator calling up to global function ", fmtName n] + val global = getGlobalObject() + in + evalCallMethodByRef global (global, n) args + end + + +and evalCallMethodByExpr (regs:Mach.REGS) + (func:Ast.EXPR) + (args:Mach.VAL list) : Mach.VAL = let (* @@ -1545,10 +1536,21 @@ and evalCallMethod (regs:Mach.REGS) * wrapper object. *) val _ = trace ["evaluating ref expr for call-method"]; - val (baseOpt, (obj, name)) = evalRefExprFull regs func true + val (baseOpt, r) = evalRefExprFull regs func true val thisObj = case baseOpt of NONE => (#this regs) | SOME base => base + in + evalCallMethodByRef thisObj r args + end + + +and evalCallMethodByRef (thisObj:Mach.OBJ) + (r:REF) + (args:Mach.VAL list) + : Mach.VAL = + let + val (obj, name) = r val _ = trace [">>> call method: ", fmtName name] val Mach.Obj { props, ... } = obj val res = case (#state (Mach.getProp props name)) of @@ -1559,7 +1561,6 @@ and evalCallMethod (regs:Mach.REGS) in res end - and evalCallExpr (thisObj:Mach.OBJ) (fobj:Mach.OBJ) @@ -1804,7 +1805,7 @@ and evalUnaryOp (regs:Mach.REGS) | Ast.Typeof => (* - * ES3 1.4.3 backward-compatibility operation. + * ES-262-3 1.4.3 backward-compatibility operation. *) let fun typeOfVal (v:Mach.VAL) = @@ -2176,7 +2177,9 @@ and evalBinaryTypeOp (regs:Mach.REGS) | "int" => newBoolean (Mach.isInt v) | "uint" => newBoolean (Mach.isUInt v) | "String" => newBoolean (Mach.isString v) + | "string" => newBoolean ((Mach.isString v) andalso (Mach.isDirectInstanceOf Name.public_string v)) | "Boolean" => newBoolean (Mach.isBoolean v) + | "boolean" => newBoolean ((Mach.isBoolean v) andalso (Mach.isDirectInstanceOf Name.public_boolean v)) | "Numeric" => newBoolean (Mach.isNumeric v) | n => (case v of @@ -3137,26 +3140,10 @@ and newByGlobalName (regs:Mach.REGS) (* - * ES3 9.9 ToObject - * - * FIXME: no idea if this makes the most sense given - * the ES3 meaning of the operation. - *) - -and toObject (regs:Mach.REGS) - (v:Mach.VAL) - : Mach.OBJ = - case v of - Mach.Undef => raise ThrowException (newByGlobalName regs Name.public_TypeError) - | Mach.Null => raise ThrowException (newByGlobalName regs Name.public_TypeError) - | Mach.Object ob => ob - - -(* - * ES3 8.6.2.1 [[Get]](P) + * ES-262-3 8.6.2.1 [[Get]](P) * * FIXME: no idea if this makes the most sense given - * the ES3 meaning of the operation. + * the ES-262-3 meaning of the operation. *) and get (obj:Mach.OBJ) (n:Ast.NAME) @@ -3385,7 +3372,7 @@ and evalWithStmt (regs:Mach.REGS) : Mach.VAL = let val v = evalExpr regs obj - val ob = (toObject regs v) + val ob = needObj (callGlobal Name.intrinsic_ToObject [v]) val s = extendScope (#scope regs) ob Mach.WithScope val regs = {this=ob, scope=s} in diff --git a/mach.sml b/mach.sml index 942b950b..0f40f49b 100644 --- a/mach.sml +++ b/mach.sml @@ -284,6 +284,17 @@ fun isNumeric (v:VAL) : bool = | _ => false +fun isDirectInstanceOf (n:Ast.NAME) + (v:VAL) + : bool = + case v of + Object (Obj { tag, ... }) => + (case tag of + ClassTag cn => nameEq cn n + | _ => false) + | _ => false + + (* Binding operations. *) fun newPropBindings _ : PROP_BINDINGS = diff --git a/ustring.sml b/ustring.sml index 92039365..226b04fa 100644 --- a/ustring.sml +++ b/ustring.sml @@ -27,9 +27,13 @@ fun internal_toEscapedAscii us = toEscapedAscii us [] end -fun internal_fromInt n = internal_fromString (Int.toString n) +fun fixNegative #"~" = "-" + | fixNegative c = String.str c +fun fixNegatives s = String.translate fixNegative s -fun internal_fromInt32 n = internal_fromString (Int32.toString n) +fun internal_fromInt n = internal_fromString (fixNegatives (Int.toString n)) + +fun internal_fromInt32 n = internal_fromString (fixNegatives (Int32.toString n)) fun internal_fromCharCode n = internal_fromString (Char.toString (Char.chr n))