forked from golang/hotime
338 lines
8.0 KiB
Go
338 lines
8.0 KiB
Go
|
package otto
|
||
|
|
||
|
import (
|
||
|
"encoding/hex"
|
||
|
"math"
|
||
|
"net/url"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"unicode/utf16"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// Global
|
||
|
func builtinGlobal_eval(call FunctionCall) Value {
|
||
|
src := call.Argument(0)
|
||
|
if !src.IsString() {
|
||
|
return src
|
||
|
}
|
||
|
runtime := call.runtime
|
||
|
program := runtime.cmpl_parseOrThrow(src.string(), nil)
|
||
|
if !call.eval {
|
||
|
// Not a direct call to eval, so we enter the global ExecutionContext
|
||
|
runtime.enterGlobalScope()
|
||
|
defer runtime.leaveScope()
|
||
|
}
|
||
|
returnValue := runtime.cmpl_evaluate_nodeProgram(program, true)
|
||
|
if returnValue.isEmpty() {
|
||
|
return Value{}
|
||
|
}
|
||
|
return returnValue
|
||
|
}
|
||
|
|
||
|
func builtinGlobal_isNaN(call FunctionCall) Value {
|
||
|
value := call.Argument(0).float64()
|
||
|
return toValue_bool(math.IsNaN(value))
|
||
|
}
|
||
|
|
||
|
func builtinGlobal_isFinite(call FunctionCall) Value {
|
||
|
value := call.Argument(0).float64()
|
||
|
return toValue_bool(!math.IsNaN(value) && !math.IsInf(value, 0))
|
||
|
}
|
||
|
|
||
|
func digitValue(chr rune) int {
|
||
|
switch {
|
||
|
case '0' <= chr && chr <= '9':
|
||
|
return int(chr - '0')
|
||
|
case 'a' <= chr && chr <= 'z':
|
||
|
return int(chr - 'a' + 10)
|
||
|
case 'A' <= chr && chr <= 'Z':
|
||
|
return int(chr - 'A' + 10)
|
||
|
}
|
||
|
return 36 // Larger than any legal digit value
|
||
|
}
|
||
|
|
||
|
func builtinGlobal_parseInt(call FunctionCall) Value {
|
||
|
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
|
||
|
if len(input) == 0 {
|
||
|
return NaNValue()
|
||
|
}
|
||
|
|
||
|
radix := int(toInt32(call.Argument(1)))
|
||
|
|
||
|
negative := false
|
||
|
switch input[0] {
|
||
|
case '+':
|
||
|
input = input[1:]
|
||
|
case '-':
|
||
|
negative = true
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
base := radix
|
||
|
index := 0
|
||
|
for ; index < len(input); index++ {
|
||
|
digit := digitValue(rune(input[index])) // If not ASCII, then an error anyway
|
||
|
if digit >= base {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
input = input[0:index]
|
||
|
|
||
|
value, err := strconv.ParseInt(input, radix, 64)
|
||
|
if err != nil {
|
||
|
if err.(*strconv.NumError).Err == strconv.ErrRange {
|
||
|
base := float64(base)
|
||
|
// Could just be a very large number (e.g. 0x8000000000000000)
|
||
|
var value float64
|
||
|
for _, chr := range input {
|
||
|
digit := float64(digitValue(chr))
|
||
|
if digit >= base {
|
||
|
return NaNValue()
|
||
|
}
|
||
|
value = value*base + digit
|
||
|
}
|
||
|
if negative {
|
||
|
value *= -1
|
||
|
}
|
||
|
return toValue_float64(value)
|
||
|
}
|
||
|
return NaNValue()
|
||
|
}
|
||
|
if negative {
|
||
|
value *= -1
|
||
|
}
|
||
|
|
||
|
return toValue_int64(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.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
|
||
|
|
||
|
if parseFloat_matchBadSpecial.MatchString(input) {
|
||
|
return NaNValue()
|
||
|
}
|
||
|
value, err := strconv.ParseFloat(input, 64)
|
||
|
if err != nil {
|
||
|
for end := len(input); end > 0; end-- {
|
||
|
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_float64(value)
|
||
|
}
|
||
|
|
||
|
// encodeURI/decodeURI
|
||
|
|
||
|
func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
|
||
|
value := call.Argument(0)
|
||
|
var input []uint16
|
||
|
switch vl := value.value.(type) {
|
||
|
case []uint16:
|
||
|
input = vl
|
||
|
default:
|
||
|
input = utf16.Encode([]rune(value.string()))
|
||
|
}
|
||
|
if len(input) == 0 {
|
||
|
return toValue_string("")
|
||
|
}
|
||
|
output := []byte{}
|
||
|
length := len(input)
|
||
|
encode := make([]byte, 4)
|
||
|
for index := 0; index < length; {
|
||
|
value := input[index]
|
||
|
decode := utf16.Decode(input[index : index+1])
|
||
|
if value >= 0xDC00 && value <= 0xDFFF {
|
||
|
panic(call.runtime.panicURIError("URI malformed"))
|
||
|
}
|
||
|
if value >= 0xD800 && value <= 0xDBFF {
|
||
|
index += 1
|
||
|
if index >= length {
|
||
|
panic(call.runtime.panicURIError("URI malformed"))
|
||
|
}
|
||
|
// input = ..., value, value1, ...
|
||
|
value1 := input[index]
|
||
|
if value1 < 0xDC00 || value1 > 0xDFFF {
|
||
|
panic(call.runtime.panicURIError("URI malformed"))
|
||
|
}
|
||
|
decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000}
|
||
|
}
|
||
|
index += 1
|
||
|
size := utf8.EncodeRune(encode, decode[0])
|
||
|
encode := encode[0:size]
|
||
|
output = append(output, encode...)
|
||
|
}
|
||
|
{
|
||
|
value := escape.ReplaceAllFunc(output, 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(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)
|
||
|
}
|
||
|
|
||
|
// 3B/2F/3F/3A/40/26/3D/2B/24/2C/23
|
||
|
var decodeURI_guard = regexp.MustCompile(`(?i)(?:%)(3B|2F|3F|3A|40|26|3D|2B|24|2C|23)`)
|
||
|
|
||
|
func _decodeURI(input string, reserve bool) (string, bool) {
|
||
|
if reserve {
|
||
|
input = decodeURI_guard.ReplaceAllString(input, "%25$1")
|
||
|
}
|
||
|
input = strings.Replace(input, "+", "%2B", -1) // Ugly hack to make QueryUnescape work with our use case
|
||
|
output, err := url.QueryUnescape(input)
|
||
|
if err != nil || !utf8.ValidString(output) {
|
||
|
return "", true
|
||
|
}
|
||
|
return output, false
|
||
|
}
|
||
|
|
||
|
func builtinGlobal_decodeURI(call FunctionCall) Value {
|
||
|
output, err := _decodeURI(call.Argument(0).string(), true)
|
||
|
if err {
|
||
|
panic(call.runtime.panicURIError("URI malformed"))
|
||
|
}
|
||
|
return toValue_string(output)
|
||
|
}
|
||
|
|
||
|
func builtinGlobal_decodeURIComponent(call FunctionCall) Value {
|
||
|
output, err := _decodeURI(call.Argument(0).string(), false)
|
||
|
if err {
|
||
|
panic(call.runtime.panicURIError("URI malformed"))
|
||
|
}
|
||
|
return toValue_string(output)
|
||
|
}
|
||
|
|
||
|
// 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_string(builtin_escape(call.Argument(0).string()))
|
||
|
}
|
||
|
|
||
|
func builtinGlobal_unescape(call FunctionCall) Value {
|
||
|
return toValue_string(builtin_unescape(call.Argument(0).string()))
|
||
|
}
|