ai_old/goja/builtin_json.go

543 lines
12 KiB
Go
Raw Normal View History

2024-09-20 16:50:35 +08:00
package goja
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
"apigo.cc/ai/ai/goja/unistring"
2024-09-20 16:50:35 +08:00
)
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
}