You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
705 lines
15 KiB
705 lines
15 KiB
#include <limits>
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
#include "lua.hpp"
|
|
|
|
#include "i64lib.h"
|
|
|
|
// __SSE2__ and __SSE4_2__ are recognized by gcc, clang, and the Intel compiler.
|
|
// We use -march=native with gmake to enable -msse2 and -msse4.2, if supported.
|
|
#if defined(__SSE4_2__)
|
|
# define RAPIDJSON_SSE42
|
|
#elif defined(__SSE2__)
|
|
# define RAPIDJSON_SSE2
|
|
#endif
|
|
|
|
#include "rapidjson/document.h"
|
|
#include "rapidjson/encodedstream.h"
|
|
#include "rapidjson/encodings.h"
|
|
#include "rapidjson/error/en.h"
|
|
#include "rapidjson/error/error.h"
|
|
#include "rapidjson/filereadstream.h"
|
|
#include "rapidjson/filewritestream.h"
|
|
#include "rapidjson/rapidjson.h"
|
|
#include "rapidjson/reader.h"
|
|
#include "rapidjson/stringbuffer.h"
|
|
#include "rapidjson/writer.h"
|
|
#include "rapidjson/prettywriter.h"
|
|
|
|
using namespace rapidjson;
|
|
|
|
#ifndef LUA_RAPIDJSON_VERSION
|
|
#define LUA_RAPIDJSON_VERSION "scm"
|
|
#endif
|
|
|
|
static const char* JSON_TABLE_TYPE_FIELD = "__jsontype";
|
|
enum json_table_type {
|
|
JSON_TABLE_TYPE_OBJECT = 0,
|
|
JSON_TABLE_TYPE_ARRAY = 1,
|
|
JSON_TABLE_TYPE_MAX
|
|
};
|
|
|
|
static const char* JSON_TABLE_TYPE_NAMES[JSON_TABLE_TYPE_MAX] = { "object", "array" };
|
|
static const char* JSON_TABLE_TYPE_METAS[JSON_TABLE_TYPE_MAX] = { "json.object", "json.array" };
|
|
|
|
|
|
static void setfuncs(lua_State* L, const luaL_Reg *funcs)
|
|
{
|
|
#if LUA_VERSION_NUM >= 502 // LUA 5.2 or above
|
|
luaL_setfuncs(L, funcs, 0);
|
|
#else
|
|
luaL_register(L, NULL, funcs);
|
|
#endif
|
|
}
|
|
|
|
|
|
#if LUA_VERSION_NUM < 502
|
|
#define lua_rawlen lua_objlen
|
|
#endif
|
|
|
|
|
|
|
|
static FILE* openForRead(const char* filename)
|
|
{
|
|
FILE* fp = NULL;
|
|
#if WIN32
|
|
fopen_s(&fp, filename, "rb");
|
|
#else
|
|
fp = fopen(filename, "r");
|
|
#endif
|
|
|
|
return fp;
|
|
}
|
|
|
|
static FILE* openForWrite(const char* filename)
|
|
{
|
|
FILE* fp = NULL;
|
|
#if WIN32
|
|
fopen_s(&fp, filename, "wb");
|
|
#else
|
|
fp = fopen(filename, "w");
|
|
#endif
|
|
|
|
return fp;
|
|
}
|
|
|
|
|
|
static int null = LUA_NOREF;
|
|
|
|
/**
|
|
* Returns json.null.
|
|
*/
|
|
static int json_null(lua_State* L)
|
|
{
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, null);
|
|
return 1;
|
|
}
|
|
|
|
static void createSharedMeta(lua_State* L, json_table_type type)
|
|
{
|
|
luaL_newmetatable(L, JSON_TABLE_TYPE_METAS[type]); // [meta]
|
|
lua_pushstring(L, JSON_TABLE_TYPE_NAMES[type]); // [meta, name]
|
|
lua_setfield(L, -2, JSON_TABLE_TYPE_FIELD); // [meta]
|
|
lua_pop(L, 1); // []
|
|
}
|
|
|
|
static int makeTableType(lua_State* L, int idx, json_table_type type)
|
|
{
|
|
bool isnoarg = lua_isnoneornil(L, idx);
|
|
bool istable = lua_istable(L, idx);
|
|
if (!isnoarg && !istable)
|
|
return luaL_argerror(L, idx, "optional table excepted");
|
|
|
|
if (isnoarg)
|
|
lua_createtable(L, 0, 0); // [table]
|
|
else // is table.
|
|
{
|
|
lua_pushvalue(L, idx); // [table]
|
|
if (lua_getmetatable(L, -1))
|
|
{
|
|
// already have a metatable, just set the __jsontype field.
|
|
// [table, meta]
|
|
lua_pushstring(L, JSON_TABLE_TYPE_NAMES[type]); // [table, meta, name]
|
|
lua_setfield(L, -2, JSON_TABLE_TYPE_FIELD); // [table, meta]
|
|
lua_pop(L, 1); // [table]
|
|
return 1;
|
|
}
|
|
// else fall through
|
|
}
|
|
|
|
// Now we have a table without meta table
|
|
luaL_getmetatable(L, JSON_TABLE_TYPE_METAS[type]); // [table, meta]
|
|
lua_setmetatable(L, -2); // [table]
|
|
return 1;
|
|
}
|
|
|
|
static int json_object(lua_State* L)
|
|
{
|
|
return makeTableType(L, 1, JSON_TABLE_TYPE_OBJECT);
|
|
}
|
|
|
|
static int json_array(lua_State* L)
|
|
{
|
|
return makeTableType(L, 1, JSON_TABLE_TYPE_ARRAY);
|
|
}
|
|
|
|
|
|
struct Ctx {
|
|
Ctx() : fn_(&topFn){}
|
|
Ctx(const Ctx& rhs) : table_(rhs.table_), index(rhs.index), fn_(rhs.fn_)
|
|
{
|
|
}
|
|
const Ctx& operator=(const Ctx& rhs){
|
|
if (this != &rhs) {
|
|
table_ = rhs.table_;
|
|
index = rhs.index;
|
|
fn_ = rhs.fn_;
|
|
}
|
|
return *this;
|
|
}
|
|
static Ctx Object(int table) {
|
|
return Ctx(table, &objectFn);
|
|
}
|
|
static Ctx Array(int table)
|
|
{
|
|
return Ctx(table, &arrayFn);
|
|
}
|
|
void submit(lua_State* L)
|
|
{
|
|
fn_(L, this);
|
|
}
|
|
private:
|
|
Ctx(int table, void(*f)(lua_State* L, Ctx* ctx)) : table_(table), index(1), fn_(f) {}
|
|
|
|
int table_;
|
|
int index;
|
|
void(*fn_)(lua_State* L, Ctx* ctx);
|
|
|
|
static void objectFn(lua_State* L, Ctx* ctx)
|
|
{
|
|
lua_rawset(L, ctx->table_);
|
|
}
|
|
|
|
static void arrayFn(lua_State* L, Ctx* ctx)
|
|
{
|
|
lua_rawseti(L, ctx->table_, ctx->index++);
|
|
}
|
|
static void topFn(lua_State* L, Ctx* ctx)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct ToLuaHandler {
|
|
ToLuaHandler(lua_State* aL) : L(aL) { stack_.reserve(32); }
|
|
|
|
bool Null() {
|
|
json_null(L);
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool Bool(bool b) {
|
|
lua_pushboolean(L, b);
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool Int(int i) {
|
|
lua_pushinteger(L, i);
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool Uint(unsigned u) {
|
|
if (u <= static_cast<unsigned>(std::numeric_limits<lua_Integer>::max()))
|
|
lua_pushinteger(L, static_cast<lua_Integer>(u));
|
|
else
|
|
lua_pushnumber(L, static_cast<lua_Number>(u));
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool Int64(int64_t i) {
|
|
lua_pushint64(L, i);
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool Uint64(uint64_t u) {
|
|
lua_pushuint64(L, u);
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool Double(double d) {
|
|
lua_pushnumber(L, static_cast<lua_Number>(d));
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool String(const char* str, SizeType length, bool copy) {
|
|
lua_pushlstring(L, str, length);
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool StartObject() {
|
|
lua_createtable(L, 0, 0); // [..., object]
|
|
|
|
// mark as object.
|
|
luaL_getmetatable(L, "json.object"); //[..., object, json.object]
|
|
lua_setmetatable(L, -2); // [..., object]
|
|
|
|
stack_.push_back(current_);
|
|
current_ = Ctx::Object(lua_gettop(L));
|
|
return true;
|
|
}
|
|
bool Key(const char* str, SizeType length, bool copy) {
|
|
lua_pushlstring(L, str, length);
|
|
return true;
|
|
}
|
|
bool EndObject(SizeType memberCount) {
|
|
current_ = stack_.back();
|
|
stack_.pop_back();
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
bool StartArray() {
|
|
lua_createtable(L, 0, 0);
|
|
|
|
// mark as array.
|
|
luaL_getmetatable(L, "json.array"); //[..., array, json.array]
|
|
lua_setmetatable(L, -2); // [..., array]
|
|
|
|
stack_.push_back(current_);
|
|
current_ = Ctx::Array(lua_gettop(L));
|
|
return true;
|
|
}
|
|
bool EndArray(SizeType elementCount) {
|
|
current_ = stack_.back();
|
|
stack_.pop_back();
|
|
current_.submit(L);
|
|
return true;
|
|
}
|
|
private:
|
|
lua_State* L;
|
|
std::vector < Ctx > stack_;
|
|
Ctx current_;
|
|
};
|
|
|
|
template<typename Stream>
|
|
inline int decode(lua_State* L, Stream* s)
|
|
{
|
|
int top = lua_gettop(L);
|
|
ToLuaHandler handler(L);
|
|
Reader reader;
|
|
ParseResult r = reader.Parse(*s, handler);
|
|
|
|
if (!r) {
|
|
lua_settop(L, top);
|
|
lua_pushnil(L);
|
|
lua_pushfstring(L, "%s (%d)", GetParseError_En(r.Code()), r.Offset());
|
|
return 2;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int json_decode(lua_State* L)
|
|
{
|
|
size_t len = 0;
|
|
const char* contents = luaL_checklstring(L, 1, &len);
|
|
StringStream s(contents);
|
|
return decode(L, &s);
|
|
}
|
|
|
|
|
|
|
|
static int json_load(lua_State* L)
|
|
{
|
|
const char* filename = luaL_checklstring(L, 1, NULL);
|
|
FILE* fp = openForRead(filename);
|
|
if (fp == NULL)
|
|
luaL_error(L, "error while open file: %s", filename);
|
|
|
|
static const size_t BufferSize = 16 * 1024;
|
|
std::vector<char> readBuffer(BufferSize);
|
|
FileReadStream fs(fp, &readBuffer.front(), BufferSize);
|
|
AutoUTFInputStream<unsigned, FileReadStream> eis(fs);
|
|
|
|
int n = decode(L, &eis);
|
|
|
|
fclose(fp);
|
|
return n;
|
|
}
|
|
|
|
struct Key
|
|
{
|
|
Key(const char* k, SizeType l) : key(k), size(l) {}
|
|
bool operator<(const Key& rhs) const {
|
|
return strcmp(key, rhs.key) < 0;
|
|
}
|
|
const char* key;
|
|
SizeType size;
|
|
};
|
|
|
|
|
|
|
|
|
|
class Encoder {
|
|
bool pretty;
|
|
bool sort_keys;
|
|
int max_depth;
|
|
static const int MAX_DEPTH_DEFAULT = 128;
|
|
public:
|
|
Encoder(lua_State*L, int opt) : pretty(false), sort_keys(false), max_depth(MAX_DEPTH_DEFAULT)
|
|
{
|
|
if (lua_isnoneornil(L, opt))
|
|
return;
|
|
luaL_checktype(L, opt, LUA_TTABLE);
|
|
|
|
pretty = optBooleanField(L, opt, "pretty", false);
|
|
sort_keys = optBooleanField(L, opt, "sort_keys", false);
|
|
max_depth = optIntegerField(L, opt, "max_depth", MAX_DEPTH_DEFAULT);
|
|
}
|
|
|
|
private:
|
|
bool optBooleanField(lua_State* L, int idx, const char* name, bool def)
|
|
{
|
|
bool v = def;
|
|
lua_getfield(L, idx, name); // [field]
|
|
if (!lua_isnoneornil(L, -1))
|
|
v = lua_toboolean(L, -1) != 0;;
|
|
lua_pop(L, 1);
|
|
return v;
|
|
}
|
|
int optIntegerField(lua_State* L, int idx, const char* name, int def)
|
|
{
|
|
int v = def;
|
|
lua_getfield(L, idx, name); // [field]
|
|
if (lua_isnumber(L, -1))
|
|
v = lua_tointeger(L, -1);
|
|
lua_pop(L, 1);
|
|
return v;
|
|
}
|
|
static bool isJsonNull(lua_State* L, int idx)
|
|
{
|
|
lua_pushvalue(L, idx); // [value]
|
|
|
|
json_null(L); // [value, json.null]
|
|
|
|
bool is = lua_rawequal(L, -1, -2) != 0;
|
|
|
|
lua_pop(L, 2); // []
|
|
|
|
return is;
|
|
}
|
|
|
|
static bool isInteger(lua_State* L, int idx, int64_t* out)
|
|
{
|
|
#if LUA_VERSION_NUM >= 503
|
|
if (lua_isinteger(L, idx)) // but it maybe not detect all integers.
|
|
{
|
|
*out = lua_tointeger(L, idx);
|
|
return true;
|
|
}
|
|
#endif
|
|
double intpart;
|
|
if (modf(lua_tonumber(L, idx), &intpart) == 0.0)
|
|
{
|
|
if (std::numeric_limits<lua_Integer>::min() <= intpart
|
|
&& intpart <= std::numeric_limits<lua_Integer>::max())
|
|
{
|
|
*out = (int64_t)intpart;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool hasJsonType(lua_State* L, int idx, bool& isarray)
|
|
{
|
|
bool has = false;
|
|
if (lua_getmetatable(L, idx)){
|
|
// [metatable]
|
|
lua_getfield(L, -1, JSON_TABLE_TYPE_FIELD); // [metatable, metatable.__jsontype]
|
|
if (lua_isstring(L, -1))
|
|
{
|
|
size_t len;
|
|
const char* s = lua_tolstring(L, -1, &len);
|
|
isarray = (s != NULL && strncmp(s, "array", 6) == 0);
|
|
has = true;
|
|
}
|
|
lua_pop(L, 2); // []
|
|
}
|
|
|
|
return has;
|
|
}
|
|
|
|
static bool isArray(lua_State* L, int idx)
|
|
{
|
|
bool isarray = false;
|
|
if (hasJsonType(L, idx, isarray)) // any table with a meta field __jsontype set to 'array' are arrays
|
|
return isarray;
|
|
|
|
return (lua_rawlen(L, idx) > 0); // any table has length > 0 are treat as array.
|
|
}
|
|
|
|
template<typename Writer>
|
|
void encodeValue(lua_State* L, Writer* writer, int idx, int depth = 0)
|
|
{
|
|
size_t len;
|
|
const char* s;
|
|
int64_t integer;
|
|
int t = lua_type(L, idx);
|
|
switch (t) {
|
|
case LUA_TBOOLEAN:
|
|
writer->Bool(lua_toboolean(L, idx) != 0);
|
|
return;
|
|
case LUA_TNUMBER:
|
|
if (isInteger(L, idx, &integer))
|
|
writer->Int64(integer);
|
|
else
|
|
{
|
|
if (!writer->Double(lua_tonumber(L, idx)))
|
|
luaL_error(L, "error while encode double value.");
|
|
}
|
|
return;
|
|
case LUA_TSTRING:
|
|
s = lua_tolstring(L, idx, &len);
|
|
writer->String(s, static_cast<SizeType>(len));
|
|
return;
|
|
case LUA_TTABLE:
|
|
return encodeTable(L, writer, idx, depth + 1);
|
|
case LUA_TNIL:
|
|
writer->Null();
|
|
return;
|
|
case LUA_TFUNCTION:
|
|
if (isJsonNull(L, idx))
|
|
{
|
|
writer->Null();
|
|
return;
|
|
}
|
|
case LUA_TUSERDATA:
|
|
if (lua_isint64(L, idx))
|
|
{
|
|
writer->Int64(lua_toint64(L, idx));
|
|
return;
|
|
}
|
|
if (lua_isuint64(L, idx))
|
|
{
|
|
writer->Uint64(lua_touint64(L, idx));
|
|
return;
|
|
}
|
|
// otherwise fall thought
|
|
case LUA_TLIGHTUSERDATA: // fall thought
|
|
case LUA_TTHREAD: // fall thought
|
|
case LUA_TNONE: // fall thought
|
|
default:
|
|
luaL_error(L, "value type : %s", lua_typename(L, t));
|
|
return;
|
|
}
|
|
}
|
|
|
|
template<typename Writer>
|
|
void encodeTable(lua_State* L, Writer* writer, int idx, int depth)
|
|
{
|
|
if (depth > max_depth)
|
|
luaL_error(L, "nested too depth");
|
|
|
|
if (!lua_checkstack(L, 4)) // requires at least 4 slots in stack: table, key, value, key
|
|
luaL_error(L, "stack overflow");
|
|
|
|
lua_pushvalue(L, idx); // [table]
|
|
if (isArray(L, -1))
|
|
{
|
|
encodeArray(L, writer, depth);
|
|
lua_pop(L, 1); // []
|
|
return;
|
|
}
|
|
|
|
// is object.
|
|
if (!sort_keys)
|
|
{
|
|
encodeObject(L, writer, depth);
|
|
lua_pop(L, 1); // []
|
|
return;
|
|
}
|
|
|
|
lua_pushnil(L); // [table, nil]
|
|
std::vector<Key> keys;
|
|
|
|
while (lua_next(L, -2))
|
|
{
|
|
// [table, key, value]
|
|
|
|
if (lua_type(L, -2) == LUA_TSTRING)
|
|
{
|
|
size_t len = 0;
|
|
const char *key = lua_tolstring(L, -2, &len);
|
|
keys.push_back(Key(key, static_cast<SizeType>(len)));
|
|
}
|
|
|
|
// pop value, leaving original key
|
|
lua_pop(L, 1);
|
|
// [table, key]
|
|
}
|
|
// [table]
|
|
encodeObject(L, writer, depth, keys);
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
template<typename Writer>
|
|
void encodeObject(lua_State* L, Writer* writer, int depth)
|
|
{
|
|
writer->StartObject();
|
|
|
|
// [table]
|
|
lua_pushnil(L); // [table, nil]
|
|
while (lua_next(L, -2))
|
|
{
|
|
// [table, key, value]
|
|
if (lua_type(L, -2) == LUA_TSTRING)
|
|
{
|
|
size_t len = 0;
|
|
const char *key = lua_tolstring(L, -2, &len);
|
|
writer->Key(key, static_cast<SizeType>(len));
|
|
encodeValue(L, writer, -1, depth);
|
|
}
|
|
|
|
// pop value, leaving original key
|
|
lua_pop(L, 1);
|
|
// [table, key]
|
|
}
|
|
// [table]
|
|
writer->EndObject();
|
|
}
|
|
|
|
template<typename Writer>
|
|
void encodeObject(lua_State* L, Writer* writer, int depth, std::vector<Key> &keys)
|
|
{
|
|
// [table]
|
|
writer->StartObject();
|
|
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
std::vector<Key>::const_iterator i = keys.begin();
|
|
std::vector<Key>::const_iterator e = keys.end();
|
|
for (; i != e; ++i)
|
|
{
|
|
writer->Key(i->key, static_cast<SizeType>(i->size));
|
|
lua_pushlstring(L, i->key, i->size); // [table, key]
|
|
lua_gettable(L, -2); // [table, value]
|
|
encodeValue(L, writer, -1, depth);
|
|
lua_pop(L, 1); // [table]
|
|
}
|
|
// [table]
|
|
writer->EndObject();
|
|
}
|
|
|
|
template<typename Writer>
|
|
void encodeArray(lua_State* L, Writer* writer, int depth)
|
|
{
|
|
// [table]
|
|
writer->StartArray();
|
|
int MAX = static_cast<int>(lua_rawlen(L, -1)); // lua_rawlen always returns value >= 0
|
|
for (int n = 1; n <= MAX; ++n)
|
|
{
|
|
lua_rawgeti(L, -1, n); // [table, element]
|
|
encodeValue(L, writer, -1, depth);
|
|
lua_pop(L, 1); // [table]
|
|
}
|
|
writer->EndArray();
|
|
// [table]
|
|
}
|
|
|
|
public:
|
|
template<typename Stream>
|
|
void encode(lua_State* L, Stream* s, int idx)
|
|
{
|
|
if (pretty)
|
|
{
|
|
PrettyWriter<Stream> writer(*s);
|
|
encodeValue(L, &writer, idx);
|
|
}
|
|
else
|
|
{
|
|
Writer<Stream> writer(*s);
|
|
encodeValue(L, &writer, idx);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
static int json_encode(lua_State* L)
|
|
{
|
|
try{
|
|
Encoder encode(L, 2);
|
|
StringBuffer s;
|
|
encode.encode(L, &s, 1);
|
|
lua_pushlstring(L, s.GetString(), s.GetSize());
|
|
return 1;
|
|
}
|
|
catch (...) {
|
|
luaL_error(L, "error while encoding");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int json_dump(lua_State* L)
|
|
{
|
|
Encoder encoder(L, 3);
|
|
|
|
const char* filename = luaL_checkstring(L, 2);
|
|
FILE* fp = openForWrite(filename);
|
|
if (fp == NULL)
|
|
luaL_error(L, "error while open file: %s", filename);
|
|
|
|
static const size_t sz = 4 * 1024;
|
|
std::vector<char> buffer(sz);
|
|
FileWriteStream fs(fp, &buffer.front(), sz);
|
|
encoder.encode(L, &fs, 1);
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const luaL_Reg methods[] = {
|
|
// string <--> json
|
|
{ "decode", json_decode },
|
|
{ "encode", json_encode },
|
|
|
|
// file <--> json
|
|
{ "load", json_load },
|
|
{ "dump", json_dump },
|
|
|
|
// special tags place holder
|
|
{ "null", json_null },
|
|
{ "object", json_object },
|
|
{ "array", json_array },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
|
|
extern "C" {
|
|
|
|
LUALIB_API int luaopen_rapidjson(lua_State* L)
|
|
{
|
|
lua_newtable(L); // [rapidjson]
|
|
|
|
setfuncs(L, methods); // [rapidjson]
|
|
|
|
lua_pushliteral(L, "rapidjson"); // [rapidjson, name]
|
|
lua_setfield(L, -2, "_NAME"); // [rapidjson]
|
|
|
|
lua_pushliteral(L, LUA_RAPIDJSON_VERSION); // [rapidjson, version]
|
|
lua_setfield(L, -2, "_VERSION"); // [rapidjson]
|
|
|
|
lua_getfield(L, -1, "null"); // [rapidjson, json.null]
|
|
null = luaL_ref(L, LUA_REGISTRYINDEX); // [rapidjson]
|
|
|
|
createSharedMeta(L, JSON_TABLE_TYPE_OBJECT);
|
|
createSharedMeta(L, JSON_TABLE_TYPE_ARRAY);
|
|
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
|