543 lines
12 KiB
Go
543 lines
12 KiB
Go
package goja
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf16"
|
|
"unicode/utf8"
|
|
|
|
"apigo.cc/ai/ai/goja/unistring"
|
|
)
|
|
|
|
const hex = "0123456789abcdef"
|
|
|
|
func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
|
|
d := json.NewDecoder(strings.NewReader(call.Argument(0).toString().String()))
|
|
|
|
value, err := r.builtinJSON_decodeValue(d)
|
|
if errors.Is(err, io.EOF) {
|
|
panic(r.newError(r.getSyntaxError(), "Unexpected end of JSON input (%v)", err.Error()))
|
|
}
|
|
if err != nil {
|
|
panic(r.newError(r.getSyntaxError(), err.Error()))
|
|
}
|
|
|
|
if tok, err := d.Token(); err != io.EOF {
|
|
panic(r.newError(r.getSyntaxError(), "Unexpected token at the end: %v", tok))
|
|
}
|
|
|
|
var reviver func(FunctionCall) Value
|
|
|
|
if arg1 := call.Argument(1); arg1 != _undefined {
|
|
reviver, _ = arg1.ToObject(r).self.assertCallable()
|
|
}
|
|
|
|
if reviver != nil {
|
|
root := r.NewObject()
|
|
createDataPropertyOrThrow(root, stringEmpty, value)
|
|
return r.builtinJSON_reviveWalk(reviver, root, stringEmpty)
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
func (r *Runtime) builtinJSON_decodeToken(d *json.Decoder, tok json.Token) (Value, error) {
|
|
switch tok := tok.(type) {
|
|
case json.Delim:
|
|
switch tok {
|
|
case '{':
|
|
return r.builtinJSON_decodeObject(d)
|
|
case '[':
|
|
return r.builtinJSON_decodeArray(d)
|
|
}
|
|
case nil:
|
|
return _null, nil
|
|
case string:
|
|
return newStringValue(tok), nil
|
|
case float64:
|
|
return floatToValue(tok), nil
|
|
case bool:
|
|
if tok {
|
|
return valueTrue, nil
|
|
}
|
|
return valueFalse, nil
|
|
}
|
|
return nil, fmt.Errorf("Unexpected token (%T): %v", tok, tok)
|
|
}
|
|
|
|
func (r *Runtime) builtinJSON_decodeValue(d *json.Decoder) (Value, error) {
|
|
tok, err := d.Token()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.builtinJSON_decodeToken(d, tok)
|
|
}
|
|
|
|
func (r *Runtime) builtinJSON_decodeObject(d *json.Decoder) (*Object, error) {
|
|
object := r.NewObject()
|
|
for {
|
|
key, end, err := r.builtinJSON_decodeObjectKey(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if end {
|
|
break
|
|
}
|
|
value, err := r.builtinJSON_decodeValue(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
object.self._putProp(unistring.NewFromString(key), value, true, true, true)
|
|
}
|
|
return object, nil
|
|
}
|
|
|
|
func (r *Runtime) builtinJSON_decodeObjectKey(d *json.Decoder) (string, bool, error) {
|
|
tok, err := d.Token()
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
switch tok := tok.(type) {
|
|
case json.Delim:
|
|
if tok == '}' {
|
|
return "", true, nil
|
|
}
|
|
case string:
|
|
return tok, false, nil
|
|
}
|
|
|
|
return "", false, fmt.Errorf("Unexpected token (%T): %v", tok, tok)
|
|
}
|
|
|
|
func (r *Runtime) builtinJSON_decodeArray(d *json.Decoder) (*Object, error) {
|
|
var arrayValue []Value
|
|
for {
|
|
tok, err := d.Token()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if delim, ok := tok.(json.Delim); ok {
|
|
if delim == ']' {
|
|
break
|
|
}
|
|
}
|
|
value, err := r.builtinJSON_decodeToken(d, tok)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
arrayValue = append(arrayValue, value)
|
|
}
|
|
return r.newArrayValues(arrayValue), nil
|
|
}
|
|
|
|
func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holder *Object, name Value) Value {
|
|
value := nilSafe(holder.get(name, nil))
|
|
|
|
if object, ok := value.(*Object); ok {
|
|
if isArray(object) {
|
|
length := toLength(object.self.getStr("length", nil))
|
|
for index := int64(0); index < length; index++ {
|
|
name := asciiString(strconv.FormatInt(index, 10))
|
|
value := r.builtinJSON_reviveWalk(reviver, object, name)
|
|
if value == _undefined {
|
|
object.delete(name, false)
|
|
} else {
|
|
createDataProperty(object, name, value)
|
|
}
|
|
}
|
|
} else {
|
|
for _, name := range object.self.stringKeys(false, nil) {
|
|
value := r.builtinJSON_reviveWalk(reviver, object, name)
|
|
if value == _undefined {
|
|
object.self.deleteStr(name.string(), false)
|
|
} else {
|
|
createDataProperty(object, name, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return reviver(FunctionCall{
|
|
This: holder,
|
|
Arguments: []Value{name, value},
|
|
})
|
|
}
|
|
|
|
type _builtinJSON_stringifyContext struct {
|
|
r *Runtime
|
|
stack []*Object
|
|
propertyList []Value
|
|
replacerFunction func(FunctionCall) Value
|
|
gap, indent string
|
|
buf bytes.Buffer
|
|
allAscii bool
|
|
}
|
|
|
|
func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
|
|
ctx := _builtinJSON_stringifyContext{
|
|
r: r,
|
|
allAscii: true,
|
|
}
|
|
|
|
replacer, _ := call.Argument(1).(*Object)
|
|
if replacer != nil {
|
|
if isArray(replacer) {
|
|
length := toLength(replacer.self.getStr("length", nil))
|
|
seen := map[string]bool{}
|
|
propertyList := make([]Value, length)
|
|
length = 0
|
|
for index := range propertyList {
|
|
var name string
|
|
value := replacer.self.getIdx(valueInt(int64(index)), nil)
|
|
switch v := value.(type) {
|
|
case valueFloat, valueInt, String:
|
|
name = value.String()
|
|
case *Object:
|
|
switch v.self.className() {
|
|
case classNumber, classString:
|
|
name = value.String()
|
|
default:
|
|
continue
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
if seen[name] {
|
|
continue
|
|
}
|
|
seen[name] = true
|
|
propertyList[length] = newStringValue(name)
|
|
length += 1
|
|
}
|
|
ctx.propertyList = propertyList[0:length]
|
|
} else if c, ok := replacer.self.assertCallable(); ok {
|
|
ctx.replacerFunction = c
|
|
}
|
|
}
|
|
if spaceValue := call.Argument(2); spaceValue != _undefined {
|
|
if o, ok := spaceValue.(*Object); ok {
|
|
switch oImpl := o.self.(type) {
|
|
case *primitiveValueObject:
|
|
switch oImpl.pValue.(type) {
|
|
case valueInt, valueFloat:
|
|
spaceValue = o.ToNumber()
|
|
}
|
|
case *stringObject:
|
|
spaceValue = o.ToString()
|
|
}
|
|
}
|
|
isNum := false
|
|
var num int64
|
|
if i, ok := spaceValue.(valueInt); ok {
|
|
num = int64(i)
|
|
isNum = true
|
|
} else if f, ok := spaceValue.(valueFloat); ok {
|
|
num = int64(f)
|
|
isNum = true
|
|
}
|
|
if isNum {
|
|
if num > 0 {
|
|
if num > 10 {
|
|
num = 10
|
|
}
|
|
ctx.gap = strings.Repeat(" ", int(num))
|
|
}
|
|
} else {
|
|
if s, ok := spaceValue.(String); ok {
|
|
str := s.String()
|
|
if len(str) > 10 {
|
|
ctx.gap = str[:10]
|
|
} else {
|
|
ctx.gap = str
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ctx.do(call.Argument(0)) {
|
|
if ctx.allAscii {
|
|
return asciiString(ctx.buf.String())
|
|
} else {
|
|
return &importedString{
|
|
s: ctx.buf.String(),
|
|
}
|
|
}
|
|
}
|
|
return _undefined
|
|
}
|
|
|
|
func (ctx *_builtinJSON_stringifyContext) do(v Value) bool {
|
|
holder := ctx.r.NewObject()
|
|
createDataPropertyOrThrow(holder, stringEmpty, v)
|
|
return ctx.str(stringEmpty, holder)
|
|
}
|
|
|
|
func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
|
|
value := nilSafe(holder.get(key, nil))
|
|
|
|
switch value.(type) {
|
|
case *Object, *valueBigInt:
|
|
if toJSON, ok := ctx.r.getVStr(value, "toJSON").(*Object); ok {
|
|
if c, ok := toJSON.self.assertCallable(); ok {
|
|
value = c(FunctionCall{
|
|
This: value,
|
|
Arguments: []Value{key},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if ctx.replacerFunction != nil {
|
|
value = ctx.replacerFunction(FunctionCall{
|
|
This: holder,
|
|
Arguments: []Value{key, value},
|
|
})
|
|
}
|
|
|
|
if o, ok := value.(*Object); ok {
|
|
switch o1 := o.self.(type) {
|
|
case *primitiveValueObject:
|
|
switch pValue := o1.pValue.(type) {
|
|
case valueInt, valueFloat:
|
|
value = o.ToNumber()
|
|
default:
|
|
value = pValue
|
|
}
|
|
case *stringObject:
|
|
value = o.toString()
|
|
case *objectGoReflect:
|
|
if o1.toJson != nil {
|
|
value = ctx.r.ToValue(o1.toJson())
|
|
} else if v, ok := o1.origValue.Interface().(json.Marshaler); ok {
|
|
b, err := v.MarshalJSON()
|
|
if err != nil {
|
|
panic(ctx.r.NewGoError(err))
|
|
}
|
|
ctx.buf.Write(b)
|
|
ctx.allAscii = false
|
|
return true
|
|
} else {
|
|
switch o1.className() {
|
|
case classNumber:
|
|
value = o1.val.ordinaryToPrimitiveNumber()
|
|
case classString:
|
|
value = o1.val.ordinaryToPrimitiveString()
|
|
case classBoolean:
|
|
if o.ToInteger() != 0 {
|
|
value = valueTrue
|
|
} else {
|
|
value = valueFalse
|
|
}
|
|
}
|
|
if o1.exportType() == typeBigInt {
|
|
value = o1.val.ordinaryToPrimitiveNumber()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch value1 := value.(type) {
|
|
case valueBool:
|
|
if value1 {
|
|
ctx.buf.WriteString("true")
|
|
} else {
|
|
ctx.buf.WriteString("false")
|
|
}
|
|
case String:
|
|
ctx.quote(value1)
|
|
case valueInt:
|
|
ctx.buf.WriteString(value.String())
|
|
case valueFloat:
|
|
if !math.IsNaN(float64(value1)) && !math.IsInf(float64(value1), 0) {
|
|
ctx.buf.WriteString(value.String())
|
|
} else {
|
|
ctx.buf.WriteString("null")
|
|
}
|
|
case valueNull:
|
|
ctx.buf.WriteString("null")
|
|
case *valueBigInt:
|
|
ctx.r.typeErrorResult(true, "Do not know how to serialize a BigInt")
|
|
case *Object:
|
|
for _, object := range ctx.stack {
|
|
if value1.SameAs(object) {
|
|
ctx.r.typeErrorResult(true, "Converting circular structure to JSON")
|
|
}
|
|
}
|
|
ctx.stack = append(ctx.stack, value1)
|
|
defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
|
|
if _, ok := value1.self.assertCallable(); !ok {
|
|
if isArray(value1) {
|
|
ctx.ja(value1)
|
|
} else {
|
|
ctx.jo(value1)
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (ctx *_builtinJSON_stringifyContext) ja(array *Object) {
|
|
var stepback string
|
|
if ctx.gap != "" {
|
|
stepback = ctx.indent
|
|
ctx.indent += ctx.gap
|
|
}
|
|
length := toLength(array.self.getStr("length", nil))
|
|
if length == 0 {
|
|
ctx.buf.WriteString("[]")
|
|
return
|
|
}
|
|
|
|
ctx.buf.WriteByte('[')
|
|
var separator string
|
|
if ctx.gap != "" {
|
|
ctx.buf.WriteByte('\n')
|
|
ctx.buf.WriteString(ctx.indent)
|
|
separator = ",\n" + ctx.indent
|
|
} else {
|
|
separator = ","
|
|
}
|
|
|
|
for i := int64(0); i < length; i++ {
|
|
if !ctx.str(asciiString(strconv.FormatInt(i, 10)), array) {
|
|
ctx.buf.WriteString("null")
|
|
}
|
|
if i < length-1 {
|
|
ctx.buf.WriteString(separator)
|
|
}
|
|
}
|
|
if ctx.gap != "" {
|
|
ctx.buf.WriteByte('\n')
|
|
ctx.buf.WriteString(stepback)
|
|
ctx.indent = stepback
|
|
}
|
|
ctx.buf.WriteByte(']')
|
|
}
|
|
|
|
func (ctx *_builtinJSON_stringifyContext) jo(object *Object) {
|
|
var stepback string
|
|
if ctx.gap != "" {
|
|
stepback = ctx.indent
|
|
ctx.indent += ctx.gap
|
|
}
|
|
|
|
ctx.buf.WriteByte('{')
|
|
mark := ctx.buf.Len()
|
|
var separator string
|
|
if ctx.gap != "" {
|
|
ctx.buf.WriteByte('\n')
|
|
ctx.buf.WriteString(ctx.indent)
|
|
separator = ",\n" + ctx.indent
|
|
} else {
|
|
separator = ","
|
|
}
|
|
|
|
var props []Value
|
|
if ctx.propertyList == nil {
|
|
props = object.self.stringKeys(false, nil)
|
|
} else {
|
|
props = ctx.propertyList
|
|
}
|
|
|
|
empty := true
|
|
for _, name := range props {
|
|
off := ctx.buf.Len()
|
|
if !empty {
|
|
ctx.buf.WriteString(separator)
|
|
}
|
|
ctx.quote(name.toString())
|
|
if ctx.gap != "" {
|
|
ctx.buf.WriteString(": ")
|
|
} else {
|
|
ctx.buf.WriteByte(':')
|
|
}
|
|
if ctx.str(name, object) {
|
|
if empty {
|
|
empty = false
|
|
}
|
|
} else {
|
|
ctx.buf.Truncate(off)
|
|
}
|
|
}
|
|
|
|
if empty {
|
|
ctx.buf.Truncate(mark)
|
|
} else {
|
|
if ctx.gap != "" {
|
|
ctx.buf.WriteByte('\n')
|
|
ctx.buf.WriteString(stepback)
|
|
ctx.indent = stepback
|
|
}
|
|
}
|
|
ctx.buf.WriteByte('}')
|
|
}
|
|
|
|
func (ctx *_builtinJSON_stringifyContext) quote(str String) {
|
|
ctx.buf.WriteByte('"')
|
|
reader := &lenientUtf16Decoder{utf16Reader: str.utf16Reader()}
|
|
for {
|
|
r, _, err := reader.ReadRune()
|
|
if err != nil {
|
|
break
|
|
}
|
|
switch r {
|
|
case '"', '\\':
|
|
ctx.buf.WriteByte('\\')
|
|
ctx.buf.WriteByte(byte(r))
|
|
case 0x08:
|
|
ctx.buf.WriteString(`\b`)
|
|
case 0x09:
|
|
ctx.buf.WriteString(`\t`)
|
|
case 0x0A:
|
|
ctx.buf.WriteString(`\n`)
|
|
case 0x0C:
|
|
ctx.buf.WriteString(`\f`)
|
|
case 0x0D:
|
|
ctx.buf.WriteString(`\r`)
|
|
default:
|
|
if r < 0x20 {
|
|
ctx.buf.WriteString(`\u00`)
|
|
ctx.buf.WriteByte(hex[r>>4])
|
|
ctx.buf.WriteByte(hex[r&0xF])
|
|
} else {
|
|
if utf16.IsSurrogate(r) {
|
|
ctx.buf.WriteString(`\u`)
|
|
ctx.buf.WriteByte(hex[r>>12])
|
|
ctx.buf.WriteByte(hex[(r>>8)&0xF])
|
|
ctx.buf.WriteByte(hex[(r>>4)&0xF])
|
|
ctx.buf.WriteByte(hex[r&0xF])
|
|
} else {
|
|
ctx.buf.WriteRune(r)
|
|
if ctx.allAscii && r >= utf8.RuneSelf {
|
|
ctx.allAscii = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.buf.WriteByte('"')
|
|
}
|
|
|
|
func (r *Runtime) getJSON() *Object {
|
|
ret := r.global.JSON
|
|
if ret == nil {
|
|
JSON := r.newBaseObject(r.global.ObjectPrototype, classObject)
|
|
ret = JSON.val
|
|
r.global.JSON = ret
|
|
JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, "parse", 2), true, false, true)
|
|
JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, "stringify", 3), true, false, true)
|
|
JSON._putSym(SymToStringTag, valueProp(asciiString(classJSON), false, false, true))
|
|
}
|
|
return ret
|
|
}
|