package otto import ( "encoding/hex" "fmt" "math" "net/url" "regexp" "strconv" "strings" "unicode/utf16" "unicode/utf8" ) // Global func builtinGlobal_eval(call FunctionCall) Value { source := call.Argument(0) if !source.IsString() { return source } program, err := parse(toString(source)) if err != nil { switch err := err.(type) { case *_syntaxError, *_error, _error: panic(err) default: panic(&_syntaxError{Message: fmt.Sprintf("%v", err)}) } } runtime := call.runtime if call.evalHint { runtime.EnterEvalExecutionContext(call) defer runtime.LeaveExecutionContext() } returnValue := runtime.evaluate(program) if returnValue.isEmpty() { return UndefinedValue() } return returnValue } func builtinGlobal_isNaN(call FunctionCall) Value { value := toFloat(call.Argument(0)) return toValue(math.IsNaN(value)) } func builtinGlobal_isFinite(call FunctionCall) Value { value := toFloat(call.Argument(0)) return toValue(!math.IsNaN(value) && !math.IsInf(value, 0)) } // 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) } 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) } var parseFloat_matchBadSpecial = regexp.MustCompile(`[\+\-]?(?:[Ii]nf$|infinity)`) var parseFloat_matchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`) func builtinGlobal_parseFloat(call FunctionCall) Value { // Caveat emptor: This implementation does NOT match the specification input := strings.TrimSpace(toString(call.Argument(0))) if parseFloat_matchBadSpecial.MatchString(input) { return NaNValue() } value, err := strconv.ParseFloat(input, 64) if err != nil { for end := len(input); end > 0; end -= 1 { input := input[0:end] if !parseFloat_matchValid.MatchString(input) { return NaNValue() } value, err = strconv.ParseFloat(input, 64) if err == nil { break } } if err != nil { return NaNValue() } } return toValue(value) } // encodeURI/decodeURI func _builtinGlobal_encodeURI(call FunctionCall, characterRegexp *regexp.Regexp) Value { value := []byte(toString(call.Argument(0))) value = characterRegexp.ReplaceAllFunc(value, func(target []byte) []byte { // Probably a better way of doing this if target[0] == ' ' { return []byte("%20") } return []byte(url.QueryEscape(string(target))) }) return toValue(string(value)) } var encodeURI_Regexp = regexp.MustCompile(`([^~!@#$&*()=:/,;?+'])`) func builtinGlobal_encodeURI(call FunctionCall) Value { return _builtinGlobal_encodeURI(call, encodeURI_Regexp) } var encodeURIComponent_Regexp = regexp.MustCompile(`([^~!*()'])`) func builtinGlobal_encodeURIComponent(call FunctionCall) Value { return _builtinGlobal_encodeURI(call, encodeURIComponent_Regexp) } func builtinGlobal_decodeURI_decodeURIComponent(call FunctionCall) Value { value, err := url.QueryUnescape(toString(call.Argument(0))) if err != nil { panic(newURIError("URI malformed")) } return toValue(value) } // escape/unescape func builtin_shouldEscape(chr byte) bool { if 'A' <= chr && chr <= 'Z' || 'a' <= chr && chr <= 'z' || '0' <= chr && chr <= '9' { return false } return !strings.ContainsRune("*_+-./", rune(chr)) } const escapeBase16 = "0123456789ABCDEF" func builtin_escape(input string) string { output := make([]byte, 0, len(input)) length := len(input) for index := 0; index < length; { if builtin_shouldEscape(input[index]) { chr, width := utf8.DecodeRuneInString(input[index:]) chr16 := utf16.Encode([]rune{chr})[0] if 256 > chr16 { output = append(output, '%', escapeBase16[chr16>>4], escapeBase16[chr16&15], ) } else { output = append(output, '%', 'u', escapeBase16[chr16>>12], escapeBase16[(chr16>>8)&15], escapeBase16[(chr16>>4)&15], escapeBase16[chr16&15], ) } index += width } else { output = append(output, input[index]) index += 1 } } return string(output) } func builtin_unescape(input string) string { output := make([]rune, 0, len(input)) length := len(input) for index := 0; index < length; { if input[index] == '%' { if index <= length-6 && input[index+1] == 'u' { byte16, err := hex.DecodeString(input[index+2 : index+6]) if err == nil { value := uint16(byte16[0])<<8 + uint16(byte16[1]) chr := utf16.Decode([]uint16{value})[0] output = append(output, chr) index += 6 continue } } if index <= length-3 { byte8, err := hex.DecodeString(input[index+1 : index+3]) if err == nil { value := uint16(byte8[0]) chr := utf16.Decode([]uint16{value})[0] output = append(output, chr) index += 3 continue } } } output = append(output, rune(input[index])) index += 1 } return string(output) } func builtinGlobal_escape(call FunctionCall) Value { return toValue(builtin_escape(toString(call.Argument(0)))) } func builtinGlobal_unescape(call FunctionCall) Value { return toValue(builtin_unescape(toString(call.Argument(0)))) } // Error func builtinError(call FunctionCall) Value { return toValue(call.runtime.newError("", call.Argument(0))) } func builtinNewError(self *_object, _ Value, argumentList []Value) Value { return toValue(self.runtime.newError("", valueOfArrayIndex(argumentList, 0))) } func builtinError_toString(call FunctionCall) Value { thisObject := call.thisObject() if thisObject == nil { panic(newTypeError()) } name := "Error" nameValue := thisObject.get("name") if nameValue.IsDefined() { name = toString(nameValue) } message := "" messageValue := thisObject.get("message") if messageValue.IsDefined() { message = toString(messageValue) } if len(name) == 0 { return toValue(message) } if len(message) == 0 { return toValue(name) } return toValue(fmt.Sprintf("%s: %s", name, message)) }