张金坤 9 vuotta sitten
vanhempi
commit
bcdf2c8e0a

+ 40 - 0
common/src/github.com/yuin/gopher-json/api.go

@@ -0,0 +1,40 @@
+package json
+
+import (
+	"encoding/json"
+
+	"github.com/yuin/gopher-lua"
+)
+
+var api = map[string]lua.LGFunction{
+	"decode": apiDecode,
+	"encode": apiEncode,
+}
+
+func apiDecode(L *lua.LState) int {
+	str := L.CheckString(1)
+
+	var value interface{}
+	err := json.Unmarshal([]byte(str), &value)
+	if err != nil {
+		L.Push(lua.LNil)
+		L.Push(lua.LString(err.Error()))
+		return 2
+	}
+	L.Push(fromJSON(L, value))
+	return 1
+}
+
+func apiEncode(L *lua.LState) int {
+	value := L.CheckAny(1)
+
+	visited := make(map[*lua.LTable]bool)
+	data, err := toJSON(value, visited)
+	if err != nil {
+		L.Push(lua.LNil)
+		L.Push(lua.LString(err.Error()))
+		return 2
+	}
+	L.Push(lua.LString(string(data)))
+	return 1
+}

+ 16 - 0
common/src/github.com/yuin/gopher-json/doc.go

@@ -0,0 +1,16 @@
+// Package json is a simple JSON encoder/decoder for gopher-lua.
+//
+// Documentation
+//
+// The following functions are exposed by the library:
+//  decode(string): Decodes a JSON string. Returns nil and an error string if
+//                  the string could not be decoded.
+//  encode(value):  Encodes a value into a JSON string. Returns nil and an error
+//                  string if the value could not be encoded.
+//
+// Example
+//
+// Below is an example usage of the library:
+//  L := lua.NewState()
+//  luajson.Preload(s)
+package json

+ 21 - 0
common/src/github.com/yuin/gopher-json/json.go

@@ -0,0 +1,21 @@
+package json
+
+import (
+	"github.com/yuin/gopher-lua"
+)
+
+// Preload adds json to the given Lua state's package.preload table. After it
+// has been preloaded, it can be loaded using require:
+//
+//  local json = require("json")
+func Preload(L *lua.LState) {
+	L.PreloadModule("json", Loader)
+}
+
+// Loader is the module loader function.
+func Loader(L *lua.LState) int {
+	t := L.NewTable()
+	L.SetFuncs(t, api)
+	L.Push(t)
+	return 1
+}

+ 75 - 0
common/src/github.com/yuin/gopher-json/json_test.go

@@ -0,0 +1,75 @@
+package json
+
+import (
+	"testing"
+
+	"github.com/yuin/gopher-lua"
+)
+
+func TestSimple(t *testing.T) {
+	const str = `
+	local json = require("json")
+	assert(type(json) == "table")
+	assert(type(json.decode) == "function")
+	assert(type(json.encode) == "function")
+
+	assert(json.encode(true) == "true")
+	assert(json.encode(1) == "1")
+	assert(json.encode(-10) == "-10")
+	assert(json.encode(nil) == "{}")
+
+	local obj = {"a",1,"b",2,"c",3}
+	local jsonStr = json.encode(obj)
+	local jsonObj = json.decode(jsonStr)
+	for i = 1, #obj do
+		assert(obj[i] == jsonObj[i])
+	end
+
+	local obj = {name="Tim",number=12345}
+	local jsonStr = json.encode(obj)
+	local jsonObj = json.decode(jsonStr)
+	assert(obj.name == jsonObj.name)
+	assert(obj.number == jsonObj.number)
+
+	local obj = {"a","b",what="c",[5]="asd"}
+	local jsonStr = json.encode(obj)
+	local jsonObj = json.decode(jsonStr)
+	assert(obj[1] == jsonObj["1"])
+	assert(obj[2] == jsonObj["2"])
+	assert(obj.what == jsonObj["what"])
+	assert(obj[5] == jsonObj["5"])
+
+	assert(json.decode("null") == nil)
+
+	assert(json.decode(json.encode({person={name = "tim",}})).person.name == "tim")
+
+	local obj = {
+		abc = 123,
+		def = nil,
+	}
+	local obj2 = {
+		obj = obj,
+	}
+	obj.obj2 = obj2
+	assert(json.encode(obj) == nil)
+	`
+	s := lua.NewState()
+	Preload(s)
+	if err := s.DoString(str); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestCustomRequire(t *testing.T) {
+	const str = `
+	local j = require("JSON")
+	assert(type(j) == "table")
+	assert(type(j.decode) == "function")
+	assert(type(j.encode) == "function")
+	`
+	s := lua.NewState()
+	s.PreloadModule("JSON", Loader)
+	if err := s.DoString(str); err != nil {
+		t.Error(err)
+	}
+}

+ 111 - 0
common/src/github.com/yuin/gopher-json/util.go

@@ -0,0 +1,111 @@
+package json
+
+import (
+	"encoding/json"
+	"errors"
+	"strconv"
+
+	"github.com/yuin/gopher-lua"
+)
+
+var (
+	errFunction = errors.New("cannot encode function to JSON")
+	errChannel  = errors.New("cannot encode channel to JSON")
+	errState    = errors.New("cannot encode state to JSON")
+	errUserData = errors.New("cannot encode userdata to JSON")
+	errNested   = errors.New("cannot encode recursively nested tables to JSON")
+)
+
+type jsonValue struct {
+	lua.LValue
+	visited map[*lua.LTable]bool
+}
+
+func (j jsonValue) MarshalJSON() ([]byte, error) {
+	return toJSON(j.LValue, j.visited)
+}
+
+func toJSON(value lua.LValue, visited map[*lua.LTable]bool) (data []byte, err error) {
+	switch converted := value.(type) {
+	case lua.LBool:
+		data, err = json.Marshal(converted)
+	case lua.LChannel:
+		err = errChannel
+	case lua.LNumber:
+		data, err = json.Marshal(converted)
+	case *lua.LFunction:
+		err = errFunction
+	case *lua.LNilType:
+		data, err = json.Marshal(converted)
+	case *lua.LState:
+		err = errState
+	case lua.LString:
+		data, err = json.Marshal(converted)
+	case *lua.LTable:
+		var arr []jsonValue
+		var obj map[string]jsonValue
+
+		if visited[converted] {
+			panic(errNested)
+		}
+		visited[converted] = true
+
+		converted.ForEach(func(k lua.LValue, v lua.LValue) {
+			i, numberKey := k.(lua.LNumber)
+			if numberKey && obj == nil {
+				index := int(i) - 1
+				if index != len(arr) {
+					// map out of order; convert to map
+					obj = make(map[string]jsonValue)
+					for i, value := range arr {
+						obj[strconv.Itoa(i+1)] = value
+					}
+					obj[strconv.Itoa(index+1)] = jsonValue{v, visited}
+					return
+				}
+				arr = append(arr, jsonValue{v, visited})
+				return
+			}
+			if obj == nil {
+				obj = make(map[string]jsonValue)
+				for i, value := range arr {
+					obj[strconv.Itoa(i+1)] = value
+				}
+			}
+			obj[k.String()] = jsonValue{v, visited}
+		})
+		if obj != nil {
+			data, err = json.Marshal(obj)
+		} else {
+			data, err = json.Marshal(arr)
+		}
+	case *lua.LUserData:
+		// TODO: call metatable __tostring?
+		err = errUserData
+	}
+	return
+}
+
+func fromJSON(L *lua.LState, value interface{}) lua.LValue {
+	switch converted := value.(type) {
+	case bool:
+		return lua.LBool(converted)
+	case float64:
+		return lua.LNumber(converted)
+	case string:
+		return lua.LString(converted)
+	case []interface{}:
+		arr := L.CreateTable(len(converted), 0)
+		for _, item := range converted {
+			arr.Append(fromJSON(L, item))
+		}
+		return arr
+	case map[string]interface{}:
+		tbl := L.CreateTable(0, len(converted))
+		for key, item := range converted {
+			tbl.RawSetH(lua.LString(key), fromJSON(L, item))
+		}
+		return tbl
+	}
+	return lua.LNil
+}