diff --git a/Makefile b/Makefile index 8ae6fc9..635bc67 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ TEST := -v --run String_fromCharCode TEST := -v --run ParseFailure TEST := -v --run Lexer\|Parse TEST := -v --run Lexer +TEST := -v --run String_ TEST := . test: test-i diff --git a/builtin.go b/builtin.go index edcde57..134dc83 100644 --- a/builtin.go +++ b/builtin.go @@ -589,7 +589,7 @@ func builtinString_slice(call FunctionCall) Value { target := toString(call.This) length := uint(len(target)) - start, end := sliceStartEnd(call.ArgumentList, length) + start, end := rangeStartEnd(call.ArgumentList, length) if 0 >= end - start { return toValue("") } @@ -600,15 +600,40 @@ func builtinString_substring(call FunctionCall) Value { checkObjectCoercible(call.This) target := toString(call.This) - length := uint(len(target)) - start := valueToArrayIndex(call.Argument(0), length, false) - end := valueToArrayIndex(call.Argument(1), length, false) + size := uint(len(target)) + start := valueToArrayIndex(call.Argument(0), size, false) + end := valueToArrayIndex(call.Argument(1), size, false) if start > end { start, end = end, start } return toValue(target[start:end]) } +func builtinString_substr(call FunctionCall) Value { + target := toString(call.This) + + size := int64(len(target)) + start, length := rangeStartLength(call.ArgumentList, uint(size)) + + if start >= size { + return toValue("") + } + + if length <= 0 { + return toValue("") + } + + if start + length >= size { + // Cap length to be to the end of the string + // start = 3, length = 5, size = 4 [0, 1, 2, 3] + // 4 - 3 = 1 + // target[3:4] + length = size - start + } + + return toValue(target[start:start+length]) +} + func builtinString_toLowerCase(call FunctionCall) Value { checkObjectCoercible(call.This) return toValue(strings.ToLower(toString(call.This))) @@ -789,17 +814,38 @@ func builtinArray_joinNative(valueArray []Value, separator string) string { return strings.Join(stringList, separator) } -func sliceStartEnd(source []Value, length uint) (start, end uint) { - start = valueToArrayIndex(valueOfArrayIndex(source, 0), length, true) +func rangeStartEnd(source []Value, size uint) (start, end uint) { + start = valueToArrayIndex(valueOfArrayIndex(source, 0), size, true) if len(source) == 1 { - end = length + // If there is only the start argument, then end = size + end = size return } - end = length + // Assuming the argument is undefined... + end = size endValue := valueOfArrayIndex(source, 1) if !endValue.IsUndefined() { - end = valueToArrayIndex(endValue, length, true) + // Which it is not, so get the value as an array index + end = valueToArrayIndex(endValue, size, true) + } + return +} + +func rangeStartLength(source []Value, size uint) (start, length int64) { + start = int64(valueToArrayIndex(valueOfArrayIndex(source, 0), size, true)) + + // Assume the second argument is missing or undefined + length = int64(size) + if len(source) == 1 { + // If there is only the start argument, then length = size + return + } + + lengthValue := valueOfArrayIndex(source, 1) + if !lengthValue.IsUndefined() { + // Which it is not, so get the value as an array index + length = toInteger(lengthValue) } return } @@ -884,7 +930,7 @@ func builtinArray_slice(call FunctionCall) Value { thisObject := call.thisObject() length := uint(toUI32(thisObject.Get("length"))) - start, end := sliceStartEnd(call.ArgumentList, length) + start, end := rangeStartEnd(call.ArgumentList, length) if start >= end { // Always an empty array diff --git a/builtin_test.go b/builtin_test.go index 6985930..720e40a 100644 --- a/builtin_test.go +++ b/builtin_test.go @@ -16,3 +16,51 @@ func TestString_fromCharCode(t *testing.T) { test(`String.fromCharCode("0x21")`, "!") } +func TestString_substr(t *testing.T) { + Terst(t) + + test := runTest() + test(`"abc".substr(0,1)`, "a") + test(`"abc".substr(0,2)`, "ab") + test(`"abc".substr(0,3)`, "abc") + test(`"abc".substr(0,4)`, "abc") + test(`"abc".substr(0,9)`, "abc") + + test(`"abc".substr(1,1)`, "b") + test(`"abc".substr(1,2)`, "bc") + test(`"abc".substr(1,3)`, "bc") + test(`"abc".substr(1,4)`, "bc") + test(`"abc".substr(1,9)`, "bc") + + test(`"abc".substr(2,1)`, "c") + test(`"abc".substr(2,2)`, "c") + test(`"abc".substr(2,3)`, "c") + test(`"abc".substr(2,4)`, "c") + test(`"abc".substr(2,9)`, "c") + + test(`"abc".substr(3,1)`, "") + test(`"abc".substr(3,2)`, "") + test(`"abc".substr(3,3)`, "") + test(`"abc".substr(3,4)`, "") + test(`"abc".substr(3,9)`, "") + + test(`"abc".substr(0)`, "abc") + test(`"abc".substr(1)`, "bc") + test(`"abc".substr(2)`, "c") + test(`"abc".substr(3)`, "") + test(`"abc".substr(9)`, "") + + test(`"abc".substr(-9)`, "abc") + test(`"abc".substr(-3)`, "abc") + test(`"abc".substr(-2)`, "bc") + test(`"abc".substr(-1)`, "c") + + test(`"abc".substr(-9, 1)`, "a") + test(`"abc".substr(-3, 1)`, "a") + test(`"abc".substr(-2, 1)`, "b") + test(`"abc".substr(-1, 1)`, "c") + test(`"abc".substr(-1, 2)`, "c") + + test(`"abcd".substr(3, 5)`, "d") +} + diff --git a/global.go b/global.go index 222319f..5962ac1 100644 --- a/global.go +++ b/global.go @@ -237,6 +237,7 @@ func newContext() *_runtime { "substring", 2, builtinString_substring, "toLowerCase", 0, builtinString_toLowerCase, "toUpperCase", 0, builtinString_toUpperCase, + "substr", 2, builtinString_substr, ) // TODO Maybe streamline this redundancy? self.Global.String.Define(