661 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
			
		
		
	
	
			661 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
| /*
 | |
| 文件名:Json_Class.nut
 | |
| 路径:Base/_Tool/Json_Class.nut
 | |
| 创建日期:2024-09-27	23:54
 | |
| 文件用途:Json类
 | |
| */
 | |
| 
 | |
| class JSONParser {
 | |
| 
 | |
| 
 | |
|     static version = "1.0.1";
 | |
| 
 | |
| 
 | |
|     state = "";
 | |
|     stack = null;
 | |
|     container = null;
 | |
|     key = "";
 | |
|     value = "";
 | |
|     converter = null;
 | |
| 
 | |
|     constructor() {
 | |
|         stack = [];
 | |
|         container = {};
 | |
|     }
 | |
| 
 | |
|     function parse(str, ...) {
 | |
|         if (vargc > 0) converter = vargc[0];
 | |
| 
 | |
| 
 | |
| 
 | |
|         local string = {
 | |
|             go = function() {
 | |
|                 state = "ok";
 | |
|             }.bindenv(this),
 | |
|             firstokey = function() {
 | |
|                 key = value;
 | |
|                 state = "colon";
 | |
|             }.bindenv(this),
 | |
|             okey = function() {
 | |
|                 key = value;
 | |
|                 state = "colon";
 | |
|             }.bindenv(this),
 | |
|             ovalue = function() {
 | |
|                 value = this._convert(value, "string", converter);
 | |
|                 state = "ocomma";
 | |
|             }.bindenv(this),
 | |
|             firstavalue = function() {
 | |
|                 value = this._convert(value, "string", converter);
 | |
|                 state = "acomma";
 | |
|             }.bindenv(this),
 | |
|             avalue = function() {
 | |
|                 value = value;
 | |
|                 this._convert(value, "string", converter);
 | |
|                 state = "acomma";
 | |
|             }.bindenv(this)
 | |
|         };
 | |
| 
 | |
| 
 | |
|         local number = {
 | |
|             go = function() {
 | |
|                 state = "ok";
 | |
|             }.bindenv(this),
 | |
|             ovalue = function() {
 | |
|                 value = this._convert(value, "number", converter);
 | |
|                 state = "ocomma";
 | |
|             }.bindenv(this),
 | |
|             firstavalue = function() {
 | |
|                 value = this._convert(value, "number", converter);
 | |
|                 state = "acomma";
 | |
|             }.bindenv(this),
 | |
|             avalue = function() {
 | |
|                 value = this._convert(value, "number", converter);
 | |
|                 state = "acomma";
 | |
|             }.bindenv(this)
 | |
|         };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|         local action = {};
 | |
| 
 | |
| 
 | |
|         action["{"] <- {
 | |
|                 go = function() {
 | |
|                     stack.push({
 | |
|                         state = "ok"
 | |
|                     });
 | |
|                     container = {};
 | |
|                     state = "firstokey";
 | |
|                 }.bindenv(this),
 | |
|                 ovalue = function() {
 | |
|                     stack.push({
 | |
|                         container = container,
 | |
|                         state = "ocomma",
 | |
|                         key = key
 | |
|                     });
 | |
|                     container = {};
 | |
|                     state = "firstokey";
 | |
|                 }.bindenv(this),
 | |
|                 firstavalue = function() {
 | |
|                     stack.push({
 | |
|                         container = container,
 | |
|                         state = "acomma"
 | |
|                     });
 | |
|                     container = {};
 | |
|                     state = "firstokey";
 | |
|                 }.bindenv(this),
 | |
|                 avalue = function() {
 | |
|                     stack.push({
 | |
|                         container = container,
 | |
|                         state = "acomma"
 | |
|                     });
 | |
|                     container = {};
 | |
|                     state = "firstokey";
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action["}"] <- {
 | |
|                 firstokey = function() {
 | |
|                     local pop = stack.pop();
 | |
|                     value = container;
 | |
|                     container = ("container" in pop) ? pop.container : null;
 | |
|                     key = ("key" in pop) ? pop.key : null;
 | |
|                     state = pop.state;
 | |
|                 }.bindenv(this),
 | |
|                 ocomma = function() {
 | |
|                     local pop = stack.pop();
 | |
|                     container[key] <- value;
 | |
|                     value = container;
 | |
|                     container = ("container" in pop) ? pop.container : null;
 | |
|                     key = ("key" in pop) ? pop.key : null;
 | |
|                     state = pop.state;
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action["["] <- {
 | |
|                 go = function() {
 | |
|                     stack.push({
 | |
|                         state = "ok"
 | |
|                     });
 | |
|                     container = [];
 | |
|                     state = "firstavalue";
 | |
|                 }.bindenv(this),
 | |
|                 ovalue = function() {
 | |
|                     stack.push({
 | |
|                         container = container,
 | |
|                         state = "ocomma",
 | |
|                         key = key
 | |
|                     });
 | |
|                     container = [];
 | |
|                     state = "firstavalue";
 | |
|                 }.bindenv(this),
 | |
|                 firstavalue = function() {
 | |
|                     stack.push({
 | |
|                         container = container,
 | |
|                         state = "acomma"
 | |
|                     });
 | |
|                     container = [];
 | |
|                     state = "firstavalue";
 | |
|                 }.bindenv(this),
 | |
|                 avalue = function() {
 | |
|                     stack.push({
 | |
|                         container = container,
 | |
|                         state = "acomma"
 | |
|                     });
 | |
|                     container = [];
 | |
|                     state = "firstavalue";
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action["]"] <- {
 | |
|                 firstavalue = function() {
 | |
|                     local pop = stack.pop();
 | |
|                     value = container;
 | |
|                     container = ("container" in pop) ? pop.container : null;
 | |
|                     key = ("key" in pop) ? pop.key : null;
 | |
|                     state = pop.state;
 | |
|                 }.bindenv(this),
 | |
|                 acomma = function() {
 | |
|                     local pop = stack.pop();
 | |
|                     container.push(value);
 | |
|                     value = container;
 | |
|                     container = ("container" in pop) ? pop.container : null;
 | |
|                     key = ("key" in pop) ? pop.key : null;
 | |
|                     state = pop.state;
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action[":"] <- {
 | |
|                 colon = function() {
 | |
| 
 | |
| 
 | |
| 
 | |
|                     local err = false;
 | |
|                     foreach(akey, avalue in container) {
 | |
|                         if (akey == key) err = true;
 | |
|                         break
 | |
|                     }
 | |
|                     if (err) throw "Duplicate key \"" + key + "\"";
 | |
|                     state = "ovalue";
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action[","] <- {
 | |
|                 ocomma = function() {
 | |
|                     container[key] <- value;
 | |
|                     state = "okey";
 | |
|                 }.bindenv(this),
 | |
|                 acomma = function() {
 | |
|                     container.push(value);
 | |
|                     state = "avalue";
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action["true"] <- {
 | |
|                 go = function() {
 | |
|                     value = true;
 | |
|                     state = "ok";
 | |
|                 }.bindenv(this),
 | |
|                 ovalue = function() {
 | |
|                     value = true;
 | |
|                     state = "ocomma";
 | |
|                 }.bindenv(this),
 | |
|                 firstavalue = function() {
 | |
|                     value = true;
 | |
|                     state = "acomma";
 | |
|                 }.bindenv(this),
 | |
|                 avalue = function() {
 | |
|                     value = true;
 | |
|                     state = "acomma";
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action["false"] <- {
 | |
|                 go = function() {
 | |
|                     value = false;
 | |
|                     state = "ok";
 | |
|                 }.bindenv(this),
 | |
|                 ovalue = function() {
 | |
|                     value = false;
 | |
|                     state = "ocomma";
 | |
|                 }.bindenv(this),
 | |
|                 firstavalue = function() {
 | |
|                     value = false;
 | |
|                     state = "acomma";
 | |
|                 }.bindenv(this),
 | |
|                 avalue = function() {
 | |
|                     value = false;
 | |
|                     state = "acomma";
 | |
|                 }.bindenv(this)
 | |
|             },
 | |
| 
 | |
|             action["null"] <- {
 | |
|                 go = function() {
 | |
|                     value = null;
 | |
|                     state = "ok";
 | |
|                 }.bindenv(this),
 | |
|                 ovalue = function() {
 | |
|                     value = null;
 | |
|                     state = "ocomma";
 | |
|                 }.bindenv(this),
 | |
|                 firstavalue = function() {
 | |
|                     value = null;
 | |
|                     state = "acomma";
 | |
|                 }.bindenv(this),
 | |
|                 avalue = function() {
 | |
|                     value = null;
 | |
|                     state = "acomma";
 | |
|                 }.bindenv(this)
 | |
|             }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|         state = "go";
 | |
|         stack = [];
 | |
| 
 | |
| 
 | |
|         local start = 0;
 | |
| 
 | |
|         try {
 | |
| 
 | |
|             local result, token, tokenizer = _JSONTokenizer();
 | |
| 
 | |
|             while (token = tokenizer.nextToken(str, start)) {
 | |
| 
 | |
|                 if ("ptfn" == token.type) {
 | |
| 
 | |
|                     action[token.value][state]();
 | |
|                 } else if ("number" == token.type) {
 | |
| 
 | |
|                     value = token.value;
 | |
|                     number[state]();
 | |
|                 } else if ("string" == token.type) {
 | |
| 
 | |
|                     value = tokenizer.unescape(token.value);
 | |
|                     string[state]();
 | |
|                 }
 | |
| 
 | |
|                 start += token.length;
 | |
|             }
 | |
| 
 | |
|         } catch (e) {
 | |
|             state = e;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         if (state != "ok" || regexp("[^\\s]").capture(str, start)) {
 | |
|             local near = str.slice(start, GetMin(str.len(), start + 10));
 | |
|             throw "JSON Syntax Error near `" + near + "`";
 | |
|         }
 | |
| 
 | |
| 
 | |
|         return value;
 | |
|     }
 | |
| 
 | |
|     function GetMin(a, b) {
 | |
|         return a< b ? a : b;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function _convert(value, type, converter) {
 | |
|         if ("function" == typeof converter) {
 | |
| 
 | |
| 
 | |
| 
 | |
|             local parametercCount = 2;
 | |
| 
 | |
| 
 | |
|             if ("getinfos" in converter) {
 | |
|                 parametercCount = converter.getinfos().parameters.len() -
 | |
|                     1;
 | |
|             }
 | |
| 
 | |
|             if (parametercCount == 1) {
 | |
|                 return converter(value);
 | |
|             } else if (parametercCount == 2) {
 | |
|                 return converter(value, type);
 | |
|             } else {
 | |
|                 throw "Error: converter function must take 1 or 2 parameters"
 | |
|             }
 | |
| 
 | |
|         } else if ("number" == type) {
 | |
|             local Ret = (value.find(".") == null && value.find("e") == null && value.find("E") == null) ? value.tointeger() : value.tofloat();
 | |
|             return Ret;
 | |
|         } else {
 | |
|             return value;
 | |
|         }
 | |
| 
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| class _JSONTokenizer {
 | |
| 
 | |
|     _ptfnRegex = null;
 | |
|     _numberRegex = null;
 | |
|     _stringRegex = null;
 | |
|     _ltrimRegex = null;
 | |
|     _unescapeRegex = null;
 | |
| 
 | |
|     constructor() {
 | |
| 
 | |
|         this._ptfnRegex = regexp("^(?:\\,|\\:|\\[|\\]|\\{|\\}|true|false|null)");
 | |
| 
 | |
| 
 | |
|         this._numberRegex = regexp("^(?:\\-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)");
 | |
| 
 | |
| 
 | |
|         this._stringRegex = regexp("^(?:\\\"((?:[^\\r\\n\\t\\\\\\\"]|\\\\(?:[\"\\\\\\/trnfb]|u[0-9a-fA-F]{4}))*)\\\")");
 | |
| 
 | |
| 
 | |
|         this._ltrimRegex = regexp("^[\\s\\t\\n\\r]*");
 | |
| 
 | |
| 
 | |
|         this._unescapeRegex = regexp("\\\\(?:(?:u\\d{4})|[\\\"\\\\/bfnrt])");
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function nextToken(str, ...) {
 | |
|         local start = 0;
 | |
|         if (vargc > 0) start = vargv[0];
 | |
|         local
 | |
|         m,
 | |
|         type,
 | |
|         token,
 | |
|         value,
 | |
|         length,
 | |
|         whitespaces;
 | |
| 
 | |
| 
 | |
|         whitespaces = this._leadingWhitespaces(str, start);
 | |
|         start += whitespaces;
 | |
| 
 | |
|         if (m = this._ptfnRegex.capture(str, start)) {
 | |
| 
 | |
|             value = str.slice(m[0].begin, m[0].end);
 | |
|             type = "ptfn";
 | |
|         } else if (m = this._numberRegex.capture(str, start)) {
 | |
| 
 | |
|             value = str.slice(m[0].begin, m[0].end);
 | |
|             type = "number";
 | |
|         } else if (m = this._stringRegex.capture(str, start)) {
 | |
| 
 | |
|             value = str.slice(m[1].begin, m[1].end);
 | |
|             type = "string";
 | |
|         } else {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         token = {
 | |
|             type = type,
 | |
|             value = value,
 | |
|             length = m[0].end - m[0].begin + whitespaces
 | |
|         };
 | |
| 
 | |
|         return token;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function _leadingWhitespaces(str, start) {
 | |
|         local r = this._ltrimRegex.capture(str, start);
 | |
| 
 | |
|         if (r) {
 | |
|             return r[0].end - r[0].begin;
 | |
|         } else {
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     _unescapeReplacements = {
 | |
|         b = "\b",
 | |
|         f = "\f",
 | |
|         n = "\n",
 | |
|         r = "\r",
 | |
|         t = "\t"
 | |
|     };
 | |
| 
 | |
|     function unescape(str) {
 | |
| 
 | |
|         local start = 0;
 | |
|         local res = "";
 | |
| 
 | |
|         while (start< str.len()) {
 | |
|             local m = this._unescapeRegex.capture(str, start);
 | |
| 
 | |
|             if (m) {
 | |
|                 local token = str.slice(m[0].begin, m[0].end);
 | |
| 
 | |
| 
 | |
|                 local pre = str.slice(start, m[0].begin);
 | |
|                 res += pre;
 | |
| 
 | |
|                 if (token.len() == 6) {
 | |
|                     res += token;
 | |
|                 } else {
 | |
|                     local char = token.slice(1);
 | |
| 
 | |
|                     if (char in this._unescapeReplacements) {
 | |
|                         res += this._unescapeReplacements[char];
 | |
|                     } else {
 | |
|                         res += char;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|             } else {
 | |
|                 res += str.slice(start);
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             start = m[0].end;
 | |
|         }
 | |
| 
 | |
|         return res;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| class JSONEncoder {
 | |
| 
 | |
|     static VERSION = "2.0.0";
 | |
| 
 | |
| 
 | |
| 
 | |
|     static _maxDepth = 32;
 | |
| 
 | |
| 
 | |
|     function encode(value) {
 | |
|         return this._encode(value);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function _encode(val, ...) {
 | |
|         local depth = 0;
 | |
|         if (vargc > 0) depth = vargv[0];
 | |
| 
 | |
|         if (depth > this._maxDepth) {
 | |
|             throw "Possible cyclic reference";
 | |
|         }
 | |
| 
 | |
|         local
 | |
|         r = "",
 | |
|             s = "",
 | |
|             i = 0;
 | |
| 
 | |
|         switch (typeof val) {
 | |
| 
 | |
|             case "table":
 | |
|             case "class":
 | |
|                 s = "";
 | |
| 
 | |
| 
 | |
|                 foreach(k, v in val) {
 | |
|                     if (typeof v != "function") {
 | |
|                         s += ",\"" + k + "\":" + this._encode(v, depth + 1);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 s = s.len() > 0 ? s.slice(1) : s;
 | |
|                 r += "{" + s + "}";
 | |
|                 break;
 | |
| 
 | |
|             case "array":
 | |
|                 s = "";
 | |
| 
 | |
|                 for (i = 0; i< val.len(); i++) {
 | |
|                     s += "," + this._encode(val[i], depth + 1);
 | |
|                 }
 | |
| 
 | |
|                 s = (i > 0) ? s.slice(1) : s;
 | |
|                 r += "[" + s + "]";
 | |
|                 break;
 | |
| 
 | |
|             case "integer":
 | |
|             case "float":
 | |
|             case "bool":
 | |
|                 r += val;
 | |
|                 break;
 | |
| 
 | |
|             case "null":
 | |
|                 r += "null";
 | |
|                 break;
 | |
| 
 | |
|             case "instance":
 | |
| 
 | |
|                 if ("_serializeRaw" in val && typeof val._serializeRaw == "function") {
 | |
| 
 | |
| 
 | |
|                     r += val._serializeRaw().tostring();
 | |
| 
 | |
|                 } else if ("_serialize" in val && typeof val._serialize == "function") {
 | |
| 
 | |
| 
 | |
|                     r += this._encode(val._serialize(), depth + 1);
 | |
| 
 | |
|                 } else {
 | |
| 
 | |
|                     s = "";
 | |
| 
 | |
|                     try {
 | |
| 
 | |
| 
 | |
|                         foreach(k, v in val) {
 | |
|                             s += ",\"" + k + "\":" + this._encode(v, depth + 1);
 | |
|                         }
 | |
| 
 | |
|                     } catch (e) {
 | |
| 
 | |
| 
 | |
| 
 | |
|                         foreach(k, v in val.getclass()) {
 | |
|                             if (typeof v != "function") {
 | |
|                                 s += ",\"" + k + "\":" + this._encode(val[k], depth + 1);
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                     }
 | |
| 
 | |
|                     s = s.len() > 0 ? s.slice(1) : s;
 | |
|                     r += "{" + s + "}";
 | |
|                 }
 | |
| 
 | |
|                 break;
 | |
| 
 | |
|             case "blob":
 | |
| 
 | |
| 
 | |
| 
 | |
|                 r += "\"" + (val.len() ? this._escape(val.tostring()) : "") + "\"";
 | |
|                 break;
 | |
| 
 | |
| 
 | |
|             default:
 | |
|                 r += "\"" + this._escape(val.tostring()) + "\"";
 | |
|                 break;
 | |
|         }
 | |
| 
 | |
|         return r;
 | |
|     }
 | |
| 
 | |
|     function _escape(str) {
 | |
|         local res = "";
 | |
| 
 | |
|         for (local i = 0; i< str.len(); i++) {
 | |
| 
 | |
|             local ch1 = (str[i] & 0xFF);
 | |
| 
 | |
|             if ((ch1 & 0x80) == 0x00) {
 | |
| 
 | |
| 
 | |
|                 ch1 = format("%c", ch1);
 | |
| 
 | |
|                 if (ch1 == "\"") {
 | |
|                     res += "\\\"";
 | |
|                 } else if (ch1 == "\\") {
 | |
|                     res += "\\\\";
 | |
|                 } else if (ch1 == "/") {
 | |
|                     res += "\\/";
 | |
|                 } else if (ch1 == "\b") {
 | |
|                     res += "\\b";
 | |
|                 } else if (ch1 == "\f") {
 | |
|                     res += "\\f";
 | |
|                 } else if (ch1 == "\n") {
 | |
|                     res += "\\n";
 | |
|                 } else if (ch1 == "\r") {
 | |
|                     res += "\\r";
 | |
|                 } else if (ch1 == "\t") {
 | |
|                     res += "\\t";
 | |
|                 } else if (ch1 == "\0") {
 | |
|                     res += "\\u0000";
 | |
|                 } else {
 | |
|                     res += ch1;
 | |
|                 }
 | |
| 
 | |
|             } else {
 | |
| 
 | |
|                 if ((ch1 & 0xE0) == 0xC0) {
 | |
| 
 | |
|                     local ch2 = (str[++i] & 0xFF);
 | |
|                     res += format("%c%c", ch1, ch2);
 | |
|                 } else if ((ch1 & 0xF0) == 0xE0) {
 | |
| 
 | |
|                     local ch2 = (str[++i] & 0xFF);
 | |
|                     local ch3 = (str[++i] & 0xFF);
 | |
|                     res += format("%c%c%c", ch1, ch2, ch3);
 | |
|                 } else if ((ch1 & 0xF8) == 0xF0) {
 | |
| 
 | |
|                     local ch2 = (str[++i] & 0xFF);
 | |
|                     local ch3 = (str[++i] & 0xFF);
 | |
|                     local ch4 = (str[++i] & 0xFF);
 | |
|                     res += format("%c%c%c%c", ch1, ch2, ch3, ch4);
 | |
|                 }
 | |
| 
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return res;
 | |
|     }
 | |
| } |