diff --git a/builtin.go b/builtin.go index c19aebb..9fdbf73 100644 --- a/builtin.go +++ b/builtin.go @@ -49,18 +49,77 @@ func builtinGlobal_isFinite(call FunctionCall) Value { return toValue(!math.IsNaN(value) && !math.IsInf(value, 0)) } -func builtinGlobal_parseInt(call FunctionCall) Value { - // Caveat emptor: This implementation does NOT match the specification - string_ := strings.TrimSpace(toString(call.Argument(0))) - radix := call.Argument(1) - radixValue := 0 - if radix.IsDefined() { - radixValue = int(toInt32(radix)) +// radix 3 => 2 (ASCII 50) +47 +// radix 11 => A/a (ASCII 65/97) +54/+86 +var parseInt_alphabetTable = func() []string { + table := []string{"", "", "01"} + for radix := 3; radix <= 36; radix += 1 { + alphabet := table[radix-1] + if radix <= 10 { + alphabet += string(radix + 47) + } else { + alphabet += string(radix+54) + string(radix+86) + } + table = append(table, alphabet) } - value, err := strconv.ParseInt(string_, radixValue, 64) + return table +}() + +func builtinGlobal_parseInt(call FunctionCall) Value { + input := strings.TrimSpace(toString(call.Argument(0))) + if len(input) == 0 { + return NaNValue() + } + + radix := int(toInt32(call.Argument(1))) + + sign := int64(1) + switch input[0] { + case '+': + sign = 1 + input = input[1:] + case '-': + sign = -1 + input = input[1:] + } + + strip := true + if radix == 0 { + radix = 10 + } else { + if radix < 2 || radix > 36 { + return NaNValue() + } else if radix != 16 { + strip = false + } + } + + switch len(input) { + case 0: + return NaNValue() + case 1: + default: + if strip { + if input[0] == '0' && (input[1] == 'x' || input[1] == 'X') { + input = input[2:] + radix = 16 + } + } + } + + alphabet := parseInt_alphabetTable[radix] + if index := strings.IndexFunc(input, func(chr rune) bool { + return !strings.ContainsRune(alphabet, chr) + }); index != -1 { + input = input[0:index] + } + + value, err := strconv.ParseInt(input, radix, 64) if err != nil { return NaNValue() } + value *= sign + return toValue(value) } diff --git a/global.go b/global.go index 0144dc9..1212337 100644 --- a/global.go +++ b/global.go @@ -701,8 +701,8 @@ func newContext() *_runtime { "undefined", UndefinedValue(), "NaN", NaNValue(), "Infinity", positiveInfinityValue(), - "eval", 1, builtinGlobal_eval, - "parseInt", builtinGlobal_parseInt, + "eval", -1, builtinGlobal_eval, + "parseInt", -2, builtinGlobal_parseInt, "parseFloat", builtinGlobal_parseFloat, "isNaN", builtinGlobal_isNaN, "isFinite", builtinGlobal_isFinite, @@ -722,9 +722,6 @@ func newContext() *_runtime { self._newError["URIError"] = self.defineError("URIError") self.eval = self.GlobalObject.get("eval")._object() - // eval.prototype === undefined - self.eval.stash.delete("prototype") - self.eval._Function.Construct = nil self.GlobalObject.prototype = self.Global.ObjectPrototype @@ -817,11 +814,21 @@ func (runtime *_runtime) newError(name string, message Value) *_object { } func (runtime *_runtime) newNativeFunction(_nativeFunction _nativeFunction, length int, name string) *_object { + prototype := true + // TODO Do this a better way... + if 0 > length { + length *= -1 + prototype = false + } self := runtime.newNativeFunctionObject(_nativeFunction, length, name) self.prototype = runtime.Global.FunctionPrototype - prototype := runtime.newObject() - self.stash.set("prototype", toValue(prototype), _propertyMode(0100)) - prototype.stash.set("constructor", toValue(self), _propertyMode(0101)) + if prototype { + prototype := runtime.newObject() + self.stash.set("prototype", toValue(prototype), _propertyMode(0100)) + prototype.stash.set("constructor", toValue(self), _propertyMode(0101)) + } else { + self._Function.Construct = nil + } return self } diff --git a/global_test.go b/global_test.go index b75c8c4..6cee9eb 100644 --- a/global_test.go +++ b/global_test.go @@ -119,13 +119,15 @@ func Test_parseInt(t *testing.T) { test(`parseInt(" 11\n")`, "11") test(`parseInt(" 11\n", 16)`, "17") test(`parseInt("Xyzzy")`, "NaN") - test(`parseInt("0x0a")`, "10") + test(`parseInt(" 0x11\n", 16)`, "17") + test(`parseInt("0x0aXyzzy", 16)`, "10") + test(`parseInt("0x1", 0)`, "1") if false { - test(`parseInt(" 0x11\n", 16)`, "17") - // TODO parseInt should return 10 in this scenario - test(`parseInt("0x0aXyzzy")`, "10") + // TODO + test(`parseInt("0x10000000000000000000", 16)`, "75557863725914323419136") } - test(`parseInt("0x0a", Infinity)`, "10") + test(`parseInt.length === 2`, "true") + test(`parseInt.prototype === undefined`, "true") } func Test_parseFloat(t *testing.T) {