From 3fa209b1c0ca124ea301badb62225a34bea9faa6 Mon Sep 17 00:00:00 2001 From: Alex Roitman Date: Mon, 20 Nov 2017 14:21:21 -0800 Subject: [PATCH] Add json library (encode/decode methods) to lua. --- Gopkg.lock | 8 +- controller/scripts.go | 4 + vendor/layeh.com/gopher-json/LICENSE | 22 +++++ vendor/layeh.com/gopher-json/README.md | 7 ++ vendor/layeh.com/gopher-json/api.go | 40 ++++++++ vendor/layeh.com/gopher-json/doc.go | 16 ++++ vendor/layeh.com/gopher-json/json.go | 21 ++++ vendor/layeh.com/gopher-json/json_test.go | 75 +++++++++++++++ vendor/layeh.com/gopher-json/util.go | 111 ++++++++++++++++++++++ 9 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 vendor/layeh.com/gopher-json/LICENSE create mode 100644 vendor/layeh.com/gopher-json/README.md create mode 100644 vendor/layeh.com/gopher-json/api.go create mode 100644 vendor/layeh.com/gopher-json/doc.go create mode 100644 vendor/layeh.com/gopher-json/json.go create mode 100644 vendor/layeh.com/gopher-json/json_test.go create mode 100644 vendor/layeh.com/gopher-json/util.go diff --git a/Gopkg.lock b/Gopkg.lock index dedb12c8..9f8b189c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -222,9 +222,15 @@ revision = "f92cdcd7dcdc69e81b2d7b338479a19a8723cfa3" version = "v1.6.0" +[[projects]] + branch = "master" + name = "layeh.com/gopher-json" + packages = ["."] + revision = "c128cc74278be889c4381681712931976fe0d88b" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "4766bb1bebb736256ed12e864e0810ebf21e26e9fee09fa1884cda8072982155" + inputs-digest = "d03f1946a6c426327998ec09c8ed935b234a8ca47e2bea55dd6f9d77424f0d60" solver-name = "gps-cdcl" solver-version = 1 diff --git a/controller/scripts.go b/controller/scripts.go index 4c200ac2..ea4ec6ca 100644 --- a/controller/scripts.go +++ b/controller/scripts.go @@ -16,6 +16,7 @@ import ( "github.com/tidwall/resp" "github.com/tidwall/tile38/controller/server" "github.com/yuin/gopher-lua" + luajson "layeh.com/gopher-json" ) const ( @@ -150,6 +151,9 @@ func (pl *lStatePool) New() *lua.LState { } L.SetGlobal("tile38", L.SetFuncs(L.NewTable(), exports)) + // Load json + L.SetGlobal("json", L.Get(luajson.Loader(L))) + // Prohibit creating new globals in this state lockNewGlobals := func(ls *lua.LState) int { ls.RaiseError("attempt to create global variable '%s'", ls.ToString(2)) diff --git a/vendor/layeh.com/gopher-json/LICENSE b/vendor/layeh.com/gopher-json/LICENSE new file mode 100644 index 00000000..b3dbff00 --- /dev/null +++ b/vendor/layeh.com/gopher-json/LICENSE @@ -0,0 +1,22 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/layeh.com/gopher-json/README.md b/vendor/layeh.com/gopher-json/README.md new file mode 100644 index 00000000..a0e7c344 --- /dev/null +++ b/vendor/layeh.com/gopher-json/README.md @@ -0,0 +1,7 @@ +# gopher-json [![GoDoc](https://godoc.org/layeh.com/gopher-json?status.svg)](https://godoc.org/layeh.com/gopher-json) + +Package json is a simple JSON encoder/decoder for [gopher-lua](https://github.com/yuin/gopher-lua). + +## License + +Public domain. diff --git a/vendor/layeh.com/gopher-json/api.go b/vendor/layeh.com/gopher-json/api.go new file mode 100644 index 00000000..5a1c2e47 --- /dev/null +++ b/vendor/layeh.com/gopher-json/api.go @@ -0,0 +1,40 @@ +package json // import "layeh.com/gopher-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 +} diff --git a/vendor/layeh.com/gopher-json/doc.go b/vendor/layeh.com/gopher-json/doc.go new file mode 100644 index 00000000..735beaa3 --- /dev/null +++ b/vendor/layeh.com/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 // import "layeh.com/gopher-json" diff --git a/vendor/layeh.com/gopher-json/json.go b/vendor/layeh.com/gopher-json/json.go new file mode 100644 index 00000000..a26642de --- /dev/null +++ b/vendor/layeh.com/gopher-json/json.go @@ -0,0 +1,21 @@ +package json // import "layeh.com/gopher-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 +} diff --git a/vendor/layeh.com/gopher-json/json_test.go b/vendor/layeh.com/gopher-json/json_test.go new file mode 100644 index 00000000..800b135b --- /dev/null +++ b/vendor/layeh.com/gopher-json/json_test.go @@ -0,0 +1,75 @@ +package json // import "layeh.com/gopher-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) + } +} diff --git a/vendor/layeh.com/gopher-json/util.go b/vendor/layeh.com/gopher-json/util.go new file mode 100644 index 00000000..2b4c10e8 --- /dev/null +++ b/vendor/layeh.com/gopher-json/util.go @@ -0,0 +1,111 @@ +package json // import "layeh.com/gopher-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 +}