summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2019-07-26 09:23:24 +0800
committerGitHub <[email protected]>2019-07-26 09:23:24 +0800
commita77e8ad6db511c7adbb6b982cd5958febcb989cf (patch)
tree1fccf65b87e1d1ae89390d2292855fdd3c7deb2d
parentb03883547d69d2173ffc46f2c8b9e4d579247a60 (diff)
parent7d4023cf698bff82b2c0f2b4aad86286d4890b49 (diff)
Merge pull request #509 from OpportunityLiu/dev
Improve string.serialize; add unit test
-rw-r--r--tests/modules/string/serialize/test.lua45
-rw-r--r--tests/test_utils/check.lua3
-rw-r--r--xmake/core/base/deprecated.lua25
-rw-r--r--xmake/core/base/dump.lua23
-rw-r--r--xmake/core/base/math.lua69
-rw-r--r--xmake/core/base/serialize.lua312
-rw-r--r--xmake/core/base/string.lua147
-rw-r--r--xmake/core/sandbox/modules/math.lua2
8 files changed, 468 insertions, 158 deletions
diff --git a/tests/modules/string/serialize/test.lua b/tests/modules/string/serialize/test.lua
new file mode 100644
index 000000000..567bee87f
--- /dev/null
+++ b/tests/modules/string/serialize/test.lua
@@ -0,0 +1,45 @@
+
+function roundtrip(round0)
+ local round1 = string.serialize(round0, false):deserialize()
+ local round2 = string.serialize(round1, true):deserialize()
+ local round3 = string.serialize(round2, {binary=true}):deserialize()
+ local round4 = string.serialize(round3, {indent=16}):deserialize()
+ local round5 = string.serialize(round4, {indent=" \r\n\t"}):deserialize()
+ return round5
+end
+
+function test_number(t)
+ t:are_equal(roundtrip(12), 12)
+ t:are_equal(roundtrip(0), 0)
+ t:are_equal(roundtrip(-1), -1)
+ t:are_equal(roundtrip(7.25), 7.25)
+ t:are_equal(roundtrip(math.huge), math.huge)
+ t:are_equal(roundtrip(-math.huge), -math.huge)
+ t:are_equal(roundtrip(math.nan), math.nan)
+end
+
+function test_boolean(t)
+ t:are_equal(roundtrip(true), true)
+ t:are_equal(roundtrip(false), false)
+end
+
+function test_nil(t)
+ t:are_equal(roundtrip(nil), nil)
+end
+
+function test_table(t)
+ t:are_equal(roundtrip({}), {})
+ t:are_equal(roundtrip({1, 2, 3}), {1, 2, 3})
+ t:are_equal(roundtrip({1, "", 3}), {1, "", 3})
+ t:are_equal(roundtrip({{1, 2, 3, nil, 4}}), {{1, 2, 3, nil, 4}})
+ t:are_equal(roundtrip({{1, 2, 3, nil, 4, [100]=5}}), {{1, 2, 3, nil, 4, [100]=5}})
+ t:are_equal(roundtrip({{a=1, b=2, c=3, nil, 4}}), {{a=1, b=2, c=3, nil, 4}})
+end
+
+function test_function(t)
+ t:are_equal(roundtrip(function() return {} end)(), {})
+ t:are_equal(roundtrip(function() return {1, 2, 3} end)(), {1, 2, 3})
+ t:are_equal(roundtrip(function() return {{1, 2, 3, nil, 4}} end)(), {{1, 2, 3, nil, 4}})
+ t:are_equal(roundtrip({function() return {{1, 2, 3, nil, 4}} end})[1](), {{1, 2, 3, nil, 4}})
+ t:are_equal(roundtrip({{function() return {{1, 2, 3, nil, 4}} end}})[1][1](), {{1, 2, 3, nil, 4}})
+end
diff --git a/tests/test_utils/check.lua b/tests/test_utils/check.lua
index bc9ddb6b6..86fa5d6b5 100644
--- a/tests/test_utils/check.lua
+++ b/tests/test_utils/check.lua
@@ -13,6 +13,9 @@ function _get_rep(value)
end
function same(actual, expacted)
+ if actual ~= actual and expacted ~= expacted then
+ return true, _get_rep(actual), _get_rep(expacted)
+ end
return actual == expacted, _get_rep(actual), _get_rep(expacted)
end
diff --git a/xmake/core/base/deprecated.lua b/xmake/core/base/deprecated.lua
index b37851f20..467bc2964 100644
--- a/xmake/core/base/deprecated.lua
+++ b/xmake/core/base/deprecated.lua
@@ -21,12 +21,6 @@
-- define module
local deprecated = deprecated or {}
--- load modules
-local utils = require("base/utils")
-local table = require("base/table")
-local string = require("base/string")
-local option = require("base/option")
-
-- add deprecated entry
function deprecated.add(newformat, oldformat, ...)
@@ -44,6 +38,10 @@ end
-- dump all deprecated entries
function deprecated.dump()
+ -- lazy load modules to avoid loop
+ local utils = require("base/utils")
+ local option = require("base/option")
+
-- dump one or more ..
local index = 0
deprecated._ENTRIES = deprecated._ENTRIES or {}
@@ -53,17 +51,18 @@ function deprecated.dump()
if index == 0 then
print("")
end
- if new then
- utils.cprint("${bright color.warning}deprecated: ${clear}please uses %s instead of %s", new, old)
- else
- utils.cprint("${bright color.warning}deprecated: ${clear}please remove %s", old)
- end
-- show more?
- if not option.get("verbose") then
- utils.cprint("${bright color.warning}deprecated: ${clear}add -v for getting more ..")
+ if not option.get("verbose") and index > 0 then
+ utils.cprint("${bright color.warning}deprecated:${clear} add -v for getting more ..")
break
end
+
+ if new then
+ utils.cprint("${bright color.warning}deprecated:${clear} please uses %s instead of %s", new, old)
+ else
+ utils.cprint("${bright color.warning}deprecated:${clear} please remove %s", old)
+ end
index = index + 1
end
end
diff --git a/xmake/core/base/dump.lua b/xmake/core/base/dump.lua
index 199dc1e19..ce3b87b91 100644
--- a/xmake/core/base/dump.lua
+++ b/xmake/core/base/dump.lua
@@ -72,16 +72,19 @@ end
-- print function
function dump._print_function(func, as_key)
+ io.write(dump._translate("${reset}${color.dump.function}"))
if as_key then
- return dump._print_default(func)
- end
- local funcinfo = debug.getinfo(func)
- local srcinfo = funcinfo.short_src
- if funcinfo.linedefined >= 0 then
- srcinfo = srcinfo .. ":" .. funcinfo.linedefined
+ io.write(dump._format("text.dump.default_format", "%s", func))
+ else
+ local funcinfo = debug.getinfo(func)
+ local srcinfo = funcinfo.short_src
+ if funcinfo.linedefined >= 0 then
+ srcinfo = srcinfo .. ":" .. funcinfo.linedefined
+ end
+ local funcname = funcinfo.name and (funcinfo.name .. " ") or ""
+ io.write(dump._translate("function ${bright}"), funcname, dump._translate("${reset}${dim}"), srcinfo)
end
- local funcname = funcinfo.name and (funcinfo.name .. " ") or ""
- io.write(dump._translate("${reset}${color.dump.function}function ${bright}"), funcname, dump._translate("${reset}${dim}"), srcinfo)
+ io.write(dump._translate("${reset}"))
end
-- print value with default format
@@ -101,9 +104,7 @@ end
-- print scalar value
function dump._print_scalar(value, as_key)
- if type(value) == "nil" then
- dump._print_keyword("nil")
- elseif type(value) == "boolean" then
+ if type(value) == "nil" or type(value) == "boolean" then
dump._print_keyword(value)
elseif type(value) == "number" then
dump._print_number(value)
diff --git a/xmake/core/base/math.lua b/xmake/core/base/math.lua
new file mode 100644
index 000000000..d49e8a8b9
--- /dev/null
+++ b/xmake/core/base/math.lua
@@ -0,0 +1,69 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015 - 2019, TBOOX Open Source Group.
+--
+-- @author OpportunityLiu
+-- @file math.lua
+--
+
+-- define module
+local math = math or {}
+
+-- init constants
+math.nan = math.log(-1)
+math.e = math.exp(1)
+
+-- check a number is int
+--
+-- @returns true for int, otherwise false
+function math:isint()
+
+ -- check
+ assert(type(self) == "number", "number expacted")
+
+ return self == math.floor(self) and self ~= math.huge and self ~= -math.huge
+end
+
+-- check a number is inf or -inf
+--
+-- @returns 1 for inf, -1 for -inf, otherwise false
+function math:isinf()
+
+ -- check
+ assert(type(self) == "number", "number expacted")
+
+ if self == math.huge then
+ return 1
+ elseif self == -math.huge then
+ return -1
+ else
+ return false
+ end
+end
+
+-- check a number is nan
+--
+-- @returns true for nan, otherwise false
+function math:isnan()
+
+ -- check
+ assert(type(self) == "number", "number expacted")
+
+ return self ~= self
+end
+
+
+-- return module
+return math
diff --git a/xmake/core/base/serialize.lua b/xmake/core/base/serialize.lua
new file mode 100644
index 000000000..e9b435387
--- /dev/null
+++ b/xmake/core/base/serialize.lua
@@ -0,0 +1,312 @@
+
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015 - 2019, TBOOX Open Source Group.
+--
+-- @author OpportunityLiu
+-- @file serialize.lua
+--
+
+-- define module: serialize
+local serialize = serialize or {}
+
+-- load modules
+local math = require("base/math")
+
+-- save original interfaces
+serialize._dump = serialize._dump or string._dump or string.dump
+
+function serialize._makenumber(num, opt, level)
+ if math.isnan(num) then
+ return "math.nan"
+ end
+ local inf = math.isinf(num)
+ if inf == 1 then
+ return "math.huge"
+ elseif inf == -1 then
+ return "-math.huge"
+ end
+ return tostring(num)
+end
+
+function serialize._makestring(str, opt, level)
+ return string.format("%q", str)
+end
+
+function serialize._makekeyword(val, opt, level)
+ return tostring(val)
+end
+
+function serialize._maketable(object, opt, level)
+
+ local childlevel = level + 1
+
+ -- serialize child items
+ local serialized = {}
+ local numidxcount = 0
+ local isarr = true
+ local maxn = 0
+ for k, v in pairs(object) do
+ if type(k) == "number" then
+ numidxcount = numidxcount + 1
+ if k < 1 or not math.isint(k) then
+ isarr = false
+ elseif k > maxn then
+ maxn = k
+ end
+ elseif type(k) == "string" then
+ isarr = false
+ else
+ return nil, string.format("cannot serialize table with key of %s: <%s>", type(k), k)
+ end
+ local sval, err = serialize._make(v, opt, childlevel)
+ if err ~= nil then
+ return nil, err
+ end
+ serialized[k] = sval
+ end
+
+ -- too sparse
+ if numidxcount * 2 < maxn then
+ isarr = false
+ end
+
+ -- make indent
+ local indent = ""
+ if opt.indent then
+ indent = string.rep(opt.indent, level)
+ end
+
+ -- make head
+ local headstr = opt.indent and "{\n" or "{"
+
+ -- make tail
+ local tailstr
+ if opt.indent then
+ tailstr = "\n" .. indent .. "}"
+ else
+ tailstr = "}"
+ end
+
+ -- make body
+ local s = {}
+ if opt.indent then
+ indent = string.rep(opt.indent, level + 1)
+ end
+
+ if isarr then
+ local nilval
+ if maxn ~= numidxcount then
+ nilval = indent .. "nil"
+ end
+ for i = 1, maxn do
+ local val = serialized[i]
+ if val == nil then
+ s[i] = nilval
+ else
+ s[i] = indent .. val
+ end
+ end
+ else
+ local con = opt.indent and " = " or "="
+ for k, v in pairs(serialized) do
+ if type(k) == "string" and not k:match("^[%a_][%w_]*$") then
+ k = string.format("[%q]", k)
+ elseif type(k) == "number" then
+ local nval, err = serialize._makenumber(k, opt, childlevel)
+ if err ~= nil then
+ return nil, err
+ end
+ k = string.format("[%s]", nval)
+ end
+ table.insert(s, indent .. k .. con .. v)
+ end
+ end
+
+ if #s == 0 then
+ return opt.indent and "{ }" or "{}"
+ end
+ return headstr .. table.concat(s, opt.indent and ",\n" or ",") .. tailstr
+end
+
+function serialize._makefunction(func, opt, level)
+ local ok, funccode = pcall(serialize._dump, func, opt.strip)
+ if not ok then
+ return nil, string.format("%s: <%s>", funccode, func)
+ end
+ local chunkname = nil
+ local sep = ","
+ if opt.strip then
+ chunkname = "\"=(deserialized code)\""
+ end
+ if opt.indent then
+ sep = ", "
+ end
+ if chunkname then
+ return string.format("loadstring(%q%s%s)", funccode, sep, chunkname)
+ else
+ return string.format("loadstring(%q)", funccode)
+ end
+end
+
+-- make string with the level
+function serialize._make(object, opt, level)
+
+ -- call make* by type
+ if type(object) == "string" then
+ return serialize._makestring(object, opt, level)
+ elseif type(object) == "boolean" or type(object) == "nil" then
+ return serialize._makekeyword(object, opt, level)
+ elseif type(object) == "number" then
+ return serialize._makenumber(object, opt, level)
+ elseif type(object) == "table" then
+ return serialize._maketable(object, opt, level)
+ elseif type(object) == "function" then
+ return serialize._makefunction(object, opt, level)
+ else
+ return nil, string.format("cannot serialize %s: <%s>", type(object), object)
+ end
+end
+
+-- serialize to string from the given object
+--
+-- @param opt serialize options
+--
+-- @return string, errors
+--
+function serialize.save(object, opt)
+
+ -- init options
+ if opt == true then
+ opt = { strip = true, binary = false, indent = false }
+ elseif opt == false or opt == nil then
+ opt = { strip = false, binary = false, indent = true }
+ end
+
+ if not opt.indent then
+ opt.indent = false
+ elseif type(opt.indent) == "boolean" then
+ opt.indent = " "
+ elseif type(opt.indent) == "number" then
+ if opt.indent < 0 then
+ opt.indent = false
+ else
+ opt.indent = string.rep(" ", opt.indent)
+ end
+ elseif type(opt.indent) == "string" then
+ if not opt.indent:match("^%s+$") then
+ return nil, "invalid opt.indent, only whitespaces are accepted"
+ end
+ else
+ return nil, "invalid opt.indent, should be boolean, number or string"
+ end
+
+ -- make string
+ local ok, result, errors = pcall(serialize._make, object, opt, 0)
+
+ if not ok then
+ if result:find("stack overflow", 1, true) then
+ errors = "cannot serialize: reference loop found"
+ else
+ errors = "cannot serialize: " .. result
+ end
+ end
+
+ -- ok?
+ if errors ~= nil then
+ return nil, errors
+ end
+
+ if not opt.binary then
+ return result
+ end
+
+ -- binary mode
+ local func, lerr = loadstring("return " .. result)
+ if lerr ~= nil then
+ return nil, lerr
+ end
+
+ local dump, derr = serialize._dump(func, true)
+ if derr ~= nil then
+ return nil, derr
+ end
+
+ -- return shorter representation
+ return (#dump < #result) and dump or result
+end
+
+-- load table from string in table
+function serialize._load(str)
+
+ -- load table as script
+ local result = nil
+
+ local binary = str:startswith("\27LJ")
+
+ if not binary then
+ str = "return " .. str
+ end
+
+ local script, errors = loadstring(str)
+ if script then
+
+ -- load object
+ local ok, object = pcall(script)
+ if ok then
+ result = object
+ elseif object then
+ -- error
+ errors = object
+ else
+ local data
+ if binary then
+ data = "<binary data>"
+ elseif #str > 20 then
+ data = str:sub(8, 17) .. "..."
+ else
+ data = str:sub(8)
+ end
+ -- error
+ errors = string.format("cannot deserialize string: %s", data)
+ end
+ end
+
+ return result, errors
+end
+
+-- deserialize string to object
+--
+-- @param str the serialized string
+--
+-- @return object, errors
+--
+function serialize.load(str)
+
+ -- check
+ assert(str)
+
+ -- load string
+ local result, errors = serialize._load(str)
+
+ -- ok?
+ if errors ~= nil then
+ return nil, errors
+ end
+ return result
+end
+
+-- return module: serialize
+return serialize
diff --git a/xmake/core/base/string.lua b/xmake/core/base/string.lua
index b59706a6d..6579ef7de 100644
--- a/xmake/core/base/string.lua
+++ b/xmake/core/base/string.lua
@@ -21,111 +21,14 @@
-- define module: string
local string = string or {}
+-- load modules
+local deprecated = require("base/deprecated")
+local serialize = require("base/serialize")
+
-- save original interfaces
string._dump = string._dump or string.dump
string._trim = string._trim or string.trim
--- make string with the level
-function string._makestr(object, deflate, serialize, level)
- if type(object) == "string" then
- return serialize and string.format("%q", object) or object
- elseif type(object) == "boolean" or type(object) == "number" then
- return tostring(object)
- elseif not serialize and type(object) == "table" and (getmetatable(object) or {}).__tostring then
- return tostring(object)
- elseif type(object) == "table" then
-
- -- make head
- local s = ""
- if deflate then
- s = s .. "{"
- else
- if level > 0 then
- s = s .. "\n"
- end
- for l = 1, level do
- s = s .. " "
- end
- s = s .. "{\n"
- end
-
- -- make body
- local i = 0
- for k, v in pairs(object) do
-
- if deflate then
- s = s .. (i ~= 0 and "," or "")
- else
- for l = 1, level do
- s = s .. " "
- end
- if i == 0 then
- s = s .. " "
- else
- s = s .. ", "
- end
- end
-
- -- make key = value
- if type(k) == "string" then
- if serialize and not k:match("^%a[%w_]+$") then
- k = string.format("[%q]", k)
- end
- if deflate then
- s = s .. k .. "="
- else
- s = s .. k .. " = "
- end
- end
- local substr, errors = string._makestr(v, deflate, serialize, level + 1)
- if substr == nil then
- return nil, errors
- end
- s = s .. substr
-
- if not deflate then
- s = s .. "\n"
- end
- i = i + 1
- end
-
- -- make tail
- if not deflate then
- for l = 1, level do
- s = s .. " "
- end
- end
- s = s .. "}"
- return s
- elseif serialize and type(object) == "function" then
- return string.format("%q", string._dump(object))
- elseif serialize then
- return nil, "cannot serialize object: " .. type(object)
- elseif object ~= nil then
- return "<" .. tostring(object) .. ">"
- else
- return "nil"
- end
-end
-
--- load table from string in table
-function string._loadstr(object)
- -- only load luajit function data: e.g. "\27LJ\2\0\6=stdin"
- if type(object) == "string" and object:startswith("\27LJ") then
- return loadstring(object)
- elseif type(object) == "table" then
- for k, v in pairs(object) do
- local value, errors = string._loadstr(v)
- if value ~= nil then
- object[k] = value
- else
- return nil, errors
- end
- end
- end
- return object
-end
-
-- find the last substring with the given pattern
function string:find_last(pattern, plain)
@@ -286,6 +189,7 @@ function string.ipattern(pattern, brackets)
return table.concat(tmp)
end
+-- @deprecated
-- dump to string from the given object (more readable)
--
-- @param deflate deflate empty characters
@@ -293,17 +197,19 @@ end
-- @return string, errors
--
function string.dump(object, deflate)
- return string._makestr(object, deflate, false, 0)
+ deprecated.add("utils.dump() or string.serialize()", "string.dump()")
+ return string.serialize(object, deflate)
end
-- serialize to string from the given object
--
--- @param deflate deflate empty characters
+-- @param opt serialize options
+-- e.g. { strip = true, binary = false, indent = true }
--
-- @return string, errors
---
-function string.serialize(object, deflate)
- return string._makestr(object, deflate, true, 0)
+--
+function string.serialize(object, opt)
+ return serialize.save(object, opt)
end
-- deserialize string to object
@@ -311,34 +217,9 @@ end
-- @param str the serialized string
--
-- @return object, errors
---
+--
function string:deserialize()
-
- -- load table as script
- local result = nil
- local script, errors = loadstring("return " .. self)
- if script then
-
- -- load object
- local ok, object = pcall(script)
- if ok and object then
- result = object
- elseif object then
- -- error
- errors = object
- else
- -- error
- errors = string.format("cannot deserialize string: %s", self)
- end
- end
-
- -- load function from string in table
- if result then
- result, errors = string._loadstr(result)
- end
-
- -- ok?
- return result, errors
+ return serialize.load(self)
end
-- return module: string
diff --git a/xmake/core/sandbox/modules/math.lua b/xmake/core/sandbox/modules/math.lua
index ebaf989b0..a52b13310 100644
--- a/xmake/core/sandbox/modules/math.lua
+++ b/xmake/core/sandbox/modules/math.lua
@@ -19,5 +19,5 @@
--
-- load module
-return math
+return require("base/math")