702 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
		
		
			
		
	
	
			702 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
|  | /** | |||
|  |  * JSON Parser | |||
|  |  * | |||
|  |  * @author Mikhail Yurasov <mikhail@electricimp.com> | |||
|  |  * @package JSONParser | |||
|  |  * @version 1.0.1 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  |  * JSON Parser | |||
|  |  * @package JSONParser | |||
|  |  */ | |||
|  | class JSONParser { | |||
|  | 
 | |||
|  |     // should be the same for all components within JSONParser package | |||
|  |     static version = "1.0.1"; | |||
|  | 
 | |||
|  |     /** | |||
|  |      * Parse JSON string into data structure | |||
|  |      * | |||
|  |      * @param {string} str | |||
|  |      * @param {function({string} value[, "number"|"string"])|null} converter | |||
|  |      * @return {*} | |||
|  |      */ | |||
|  |     function parse(str, converter = null) { | |||
|  | 
 | |||
|  |         local state; | |||
|  |         local stack = [] | |||
|  |         local container; | |||
|  |         local key; | |||
|  |         local value; | |||
|  | 
 | |||
|  |         // actions for string tokens | |||
|  |         local string = { | |||
|  |             go = function() { | |||
|  |                 state = "ok"; | |||
|  |             }, | |||
|  |             firstokey = function() { | |||
|  |                 key = value; | |||
|  |                 state = "colon"; | |||
|  |             }, | |||
|  |             okey = function() { | |||
|  |                 key = value; | |||
|  |                 state = "colon"; | |||
|  |             }, | |||
|  |             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 = this._convert(value, "string", converter); | |||
|  |                 state = "acomma"; | |||
|  |             }.bindenv(this) | |||
|  |         }; | |||
|  | 
 | |||
|  |         // the actions for number tokens | |||
|  |         local number = { | |||
|  |             go = function() { | |||
|  |                 state = "ok"; | |||
|  |             }, | |||
|  |             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) | |||
|  |         }; | |||
|  | 
 | |||
|  |         // action table | |||
|  |         // describes where the state machine will go from each given state | |||
|  |         local action = { | |||
|  | 
 | |||
|  |             "{": { | |||
|  |                 go = function() { | |||
|  |                     stack.push({ | |||
|  |                         state = "ok" | |||
|  |                     }); | |||
|  |                     container = {}; | |||
|  |                     state = "firstokey"; | |||
|  |                 }, | |||
|  |                 ovalue = function() { | |||
|  |                     stack.push({ | |||
|  |                         container = container, | |||
|  |                         state = "ocomma", | |||
|  |                         key = key | |||
|  |                     }); | |||
|  |                     container = {}; | |||
|  |                     state = "firstokey"; | |||
|  |                 }, | |||
|  |                 firstavalue = function() { | |||
|  |                     stack.push({ | |||
|  |                         container = container, | |||
|  |                         state = "acomma" | |||
|  |                     }); | |||
|  |                     container = {}; | |||
|  |                     state = "firstokey"; | |||
|  |                 }, | |||
|  |                 avalue = function() { | |||
|  |                     stack.push({ | |||
|  |                         container = container, | |||
|  |                         state = "acomma" | |||
|  |                     }); | |||
|  |                     container = {}; | |||
|  |                     state = "firstokey"; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             "}": { | |||
|  |                 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; | |||
|  |                 }, | |||
|  |                 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; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             "[": { | |||
|  |                 go = function() { | |||
|  |                     stack.push({ | |||
|  |                         state = "ok" | |||
|  |                     }); | |||
|  |                     container = []; | |||
|  |                     state = "firstavalue"; | |||
|  |                 }, | |||
|  |                 ovalue = function() { | |||
|  |                     stack.push({ | |||
|  |                         container = container, | |||
|  |                         state = "ocomma", | |||
|  |                         key = key | |||
|  |                     }); | |||
|  |                     container = []; | |||
|  |                     state = "firstavalue"; | |||
|  |                 }, | |||
|  |                 firstavalue = function() { | |||
|  |                     stack.push({ | |||
|  |                         container = container, | |||
|  |                         state = "acomma" | |||
|  |                     }); | |||
|  |                     container = []; | |||
|  |                     state = "firstavalue"; | |||
|  |                 }, | |||
|  |                 avalue = function() { | |||
|  |                     stack.push({ | |||
|  |                         container = container, | |||
|  |                         state = "acomma" | |||
|  |                     }); | |||
|  |                     container = []; | |||
|  |                     state = "firstavalue"; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             "]": { | |||
|  |                 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; | |||
|  |                 }, | |||
|  |                 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; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             ":": { | |||
|  |                 colon = function() { | |||
|  |                     // Check if the key already exists | |||
|  |                     // NOTE previous code used 'if (key in container)...' | |||
|  |                     //      but this finds table ('container') member methods too | |||
|  |                     local err = false; | |||
|  |                     foreach(akey, avalue in container) { | |||
|  |                         if (akey == key) err = true; | |||
|  |                         break | |||
|  |                     } | |||
|  |                     if (err) throw "Duplicate key \"" + key + "\""; | |||
|  |                     state = "ovalue"; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             ",": { | |||
|  |                 ocomma = function() { | |||
|  |                     container[key] <- value; | |||
|  |                     state = "okey"; | |||
|  |                 }, | |||
|  |                 acomma = function() { | |||
|  |                     container.push(value); | |||
|  |                     state = "avalue"; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             "true": { | |||
|  |                 go = function() { | |||
|  |                     value = true; | |||
|  |                     state = "ok"; | |||
|  |                 }, | |||
|  |                 ovalue = function() { | |||
|  |                     value = true; | |||
|  |                     state = "ocomma"; | |||
|  |                 }, | |||
|  |                 firstavalue = function() { | |||
|  |                     value = true; | |||
|  |                     state = "acomma"; | |||
|  |                 }, | |||
|  |                 avalue = function() { | |||
|  |                     value = true; | |||
|  |                     state = "acomma"; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             "false": { | |||
|  |                 go = function() { | |||
|  |                     value = false; | |||
|  |                     state = "ok"; | |||
|  |                 }, | |||
|  |                 ovalue = function() { | |||
|  |                     value = false; | |||
|  |                     state = "ocomma"; | |||
|  |                 }, | |||
|  |                 firstavalue = function() { | |||
|  |                     value = false; | |||
|  |                     state = "acomma"; | |||
|  |                 }, | |||
|  |                 avalue = function() { | |||
|  |                     value = false; | |||
|  |                     state = "acomma"; | |||
|  |                 } | |||
|  |             }, | |||
|  | 
 | |||
|  |             "null": { | |||
|  |                 go = function() { | |||
|  |                     value = null; | |||
|  |                     state = "ok"; | |||
|  |                 }, | |||
|  |                 ovalue = function() { | |||
|  |                     value = null; | |||
|  |                     state = "ocomma"; | |||
|  |                 }, | |||
|  |                 firstavalue = function() { | |||
|  |                     value = null; | |||
|  |                     state = "acomma"; | |||
|  |                 }, | |||
|  |                 avalue = function() { | |||
|  |                     value = null; | |||
|  |                     state = "acomma"; | |||
|  |                 } | |||
|  |             } | |||
|  |         }; | |||
|  | 
 | |||
|  |         // | |||
|  | 
 | |||
|  |         state = "go"; | |||
|  |         stack = []; | |||
|  | 
 | |||
|  |         // current tokenizeing position | |||
|  |         local start = 0; | |||
|  | 
 | |||
|  |         try { | |||
|  | 
 | |||
|  |             local | |||
|  |             result, | |||
|  |             token, | |||
|  |             tokenizer = _JSONTokenizer(); | |||
|  | 
 | |||
|  |             while (token = tokenizer.nextToken(str, start)) { | |||
|  | 
 | |||
|  |                 if ("ptfn" == token.type) { | |||
|  |                     // punctuation/true/false/null | |||
|  |                     action[token.value][state](); | |||
|  |                 } else if ("number" == token.type) { | |||
|  |                     // number | |||
|  |                     value = token.value; | |||
|  |                     number[state](); | |||
|  |                 } else if ("string" == token.type) { | |||
|  |                     // string | |||
|  |                     value = tokenizer.unescape(token.value); | |||
|  |                     string[state](); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 start += token.length; | |||
|  |             } | |||
|  | 
 | |||
|  |         } catch (e) { | |||
|  |             state = e; | |||
|  |         } | |||
|  | 
 | |||
|  |         // check is the final state is not ok | |||
|  |         // or if there is somethign left in the str | |||
|  |         if (state != "ok" || regexp("[^\\s]").capture(str, start)) { | |||
|  |             local min = @(a, b) a< b ? a : b; | |||
|  |             local near = str.slice(start, min(str.len(), start + 10)); | |||
|  |             throw "JSON Syntax Error near `" + near + "`"; | |||
|  |         } | |||
|  | 
 | |||
|  |         return value; | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * Convert strings/numbers | |||
|  |      * Uses custom converter function | |||
|  |      * | |||
|  |      * @param {string} value | |||
|  |      * @param {string} type | |||
|  |      * @param {function|null} converter | |||
|  |      */ | |||
|  |     function _convert(value, type, converter) { | |||
|  |         if ("function" == typeof converter) { | |||
|  | 
 | |||
|  |             // # of params for converter function | |||
|  | 
 | |||
|  |             local parametercCount = 2; | |||
|  | 
 | |||
|  |             // .getinfos() is missing on ei platform | |||
|  |             if ("getinfos" in converter) { | |||
|  |                 parametercCount = converter.getinfos().parameters.len() - | |||
|  |                     1 /* "this" is also included */ ; | |||
|  |             } | |||
|  | 
 | |||
|  |             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) { | |||
|  |             return (value.find(".") == null && value.find("e") == null && value.find("E") == null) ? value.tointeger() : value.tofloat(); | |||
|  |         } else { | |||
|  |             return value; | |||
|  |         } | |||
|  |     } | |||
|  | } | |||
|  | 
 | |||
|  | /** | |||
|  |  * JSON Tokenizer | |||
|  |  * @package JSONParser | |||
|  |  */ | |||
|  | class _JSONTokenizer { | |||
|  | 
 | |||
|  |     _ptfnRegex = null; | |||
|  |     _numberRegex = null; | |||
|  |     _stringRegex = null; | |||
|  |     _ltrimRegex = null; | |||
|  |     _unescapeRegex = null; | |||
|  | 
 | |||
|  |     constructor() { | |||
|  |         // punctuation/true/false/null | |||
|  |         this._ptfnRegex = regexp("^(?:\\,|\\:|\\[|\\]|\\{|\\}|true|false|null)"); | |||
|  | 
 | |||
|  |         // numbers | |||
|  |         this._numberRegex = regexp("^(?:\\-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)"); | |||
|  | 
 | |||
|  |         // strings | |||
|  |         this._stringRegex = regexp("^(?:\\\"((?:[^\\r\\n\\t\\\\\\\"]|\\\\(?:[\"\\\\\\/trnfb]|u[0-9a-fA-F]{4}))*)\\\")"); | |||
|  | 
 | |||
|  |         // ltrim pattern | |||
|  |         this._ltrimRegex = regexp("^[\\s\\t\\n\\r]*"); | |||
|  | 
 | |||
|  |         // string unescaper tokenizer pattern | |||
|  |         this._unescapeRegex = regexp("\\\\(?:(?:u\\d{4})|[\\\"\\\\/bfnrt])"); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * Get next available token | |||
|  |      * @param {string} str | |||
|  |      * @param {integer} start | |||
|  |      * @return {{type,value,length}|null} | |||
|  |      */ | |||
|  |     function nextToken(str, start = 0) { | |||
|  | 
 | |||
|  |         local | |||
|  |         m, | |||
|  |         type, | |||
|  |         token, | |||
|  |         value, | |||
|  |         length, | |||
|  |         whitespaces; | |||
|  | 
 | |||
|  |         // count # of left-side whitespace chars | |||
|  |         whitespaces = this._leadingWhitespaces(str, start); | |||
|  |         start += whitespaces; | |||
|  | 
 | |||
|  |         if (m = this._ptfnRegex.capture(str, start)) { | |||
|  |             // punctuation/true/false/null | |||
|  |             value = str.slice(m[0].begin, m[0].end); | |||
|  |             type = "ptfn"; | |||
|  |         } else if (m = this._numberRegex.capture(str, start)) { | |||
|  |             // number | |||
|  |             value = str.slice(m[0].begin, m[0].end); | |||
|  |             type = "number"; | |||
|  |         } else if (m = this._stringRegex.capture(str, start)) { | |||
|  |             // string | |||
|  |             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; | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * Count # of left-side whitespace chars | |||
|  |      * @param {string} str | |||
|  |      * @param {integer} start | |||
|  |      * @return {integer} number of leading spaces | |||
|  |      */ | |||
|  |     function _leadingWhitespaces(str, start) { | |||
|  |         local r = this._ltrimRegex.capture(str, start); | |||
|  | 
 | |||
|  |         if (r) { | |||
|  |             return r[0].end - r[0].begin; | |||
|  |         } else { | |||
|  |             return 0; | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  |     // unesacape() replacements table | |||
|  |     _unescapeReplacements = { | |||
|  |         "b": "\b", | |||
|  |         "f": "\f", | |||
|  |         "n": "\n", | |||
|  |         "r": "\r", | |||
|  |         "t": "\t" | |||
|  |     }; | |||
|  | 
 | |||
|  |     /** | |||
|  |      * Unesacape string escaped per JSON standard | |||
|  |      * @param {string} str | |||
|  |      * @return {string} | |||
|  |      */ | |||
|  |     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); | |||
|  | 
 | |||
|  |                 // append chars before match | |||
|  |                 local pre = str.slice(start, m[0].begin); | |||
|  |                 res += pre; | |||
|  | 
 | |||
|  |                 if (token.len() == 6) { | |||
|  |                     // unicode char in format \uhhhh, where hhhh is hex char code | |||
|  |                     // todo: convert \uhhhh chars | |||
|  |                     res += token; | |||
|  |                 } else { | |||
|  |                     // escaped char | |||
|  |                     // @see http://www.json.org/ | |||
|  |                     local char = token.slice(1); | |||
|  | 
 | |||
|  |                     if (char in this._unescapeReplacements) { | |||
|  |                         res += this._unescapeReplacements[char]; | |||
|  |                     } else { | |||
|  |                         res += char; | |||
|  |                     } | |||
|  |                 } | |||
|  | 
 | |||
|  |             } else { | |||
|  |                 // append the rest of the source string | |||
|  |                 res += str.slice(start); | |||
|  |                 break; | |||
|  |             } | |||
|  | 
 | |||
|  |             start = m[0].end; | |||
|  |         } | |||
|  | 
 | |||
|  |         return res; | |||
|  |     } | |||
|  | } | |||
|  | 
 | |||
|  | 
 | |||
|  | // Copyright (c) 2017 Electric Imp | |||
|  | // This file is licensed under the MIT License | |||
|  | // http://opensource.org/licenses/MIT | |||
|  | 
 | |||
|  | class JSONEncoder { | |||
|  | 
 | |||
|  |     static VERSION = "2.0.0"; | |||
|  | 
 | |||
|  |     // max structure depth | |||
|  |     // anything above probably has a cyclic ref | |||
|  |     static _maxDepth = 32; | |||
|  | 
 | |||
|  |     /** | |||
|  |      * Encode value to JSON | |||
|  |      * @param {table|array|*} value | |||
|  |      * @returns {string} | |||
|  |      */ | |||
|  |     function encode(value) { | |||
|  |         return this._encode(value); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @param {table|array} val | |||
|  |      * @param {integer=0} depth – current depth level | |||
|  |      * @private | |||
|  |      */ | |||
|  |     function _encode(val, depth = 0) { | |||
|  | 
 | |||
|  |         // detect cyclic reference | |||
|  |         if (depth > this._maxDepth) { | |||
|  |             throw "Possible cyclic reference"; | |||
|  |         } | |||
|  |         local | |||
|  |         r = "", | |||
|  |             s = "", | |||
|  |             i = 0; | |||
|  | 
 | |||
|  |         switch (typeof val) { | |||
|  | 
 | |||
|  |             case "table": | |||
|  |             case "class": | |||
|  |                 s = ""; | |||
|  | 
 | |||
|  |                 // serialize properties, but not functions | |||
|  |                 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") { | |||
|  | 
 | |||
|  |                     // include value produced by _serializeRaw() | |||
|  |                     r += val._serializeRaw().tostring(); | |||
|  | 
 | |||
|  |                 } else if ("_serialize" in val && typeof val._serialize == "function") { | |||
|  | 
 | |||
|  |                     // serialize instances by calling _serialize method | |||
|  |                     r += this._encode(val._serialize(), depth + 1); | |||
|  | 
 | |||
|  |                 } else { | |||
|  | 
 | |||
|  |                     s = ""; | |||
|  | 
 | |||
|  |                     try { | |||
|  | 
 | |||
|  |                         // iterate through instances which implement _nexti meta-method | |||
|  |                         foreach(k, v in val) { | |||
|  |                             s += ",\"" + k + "\":" + this._encode(v, depth + 1); | |||
|  |                         } | |||
|  | 
 | |||
|  |                     } catch (e) { | |||
|  | 
 | |||
|  |                         // iterate through instances w/o _nexti | |||
|  |                         // serialize properties, but not functions | |||
|  |                         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": | |||
|  |                 // This is a workaround for a known bug: | |||
|  |                 // on device side Blob.tostring() returns null | |||
|  |                 // (instaead of an empty string) | |||
|  |                 r += "\"" + (val.len() ? this._escape(val.tostring()) : "") + "\""; | |||
|  |                 break; | |||
|  | 
 | |||
|  |                 // strings and all other | |||
|  |             case "string": | |||
|  |                 r += "\"" + this._escape(val) + "\""; | |||
|  |                 break; | |||
|  |             default: | |||
|  |                 r += "\"" + this._escape(val.tostring()) + "\""; | |||
|  |                 break; | |||
|  |         } | |||
|  | 
 | |||
|  |         return r; | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * Escape strings according to http://www.json.org/ spec | |||
|  |      * @param {string} str | |||
|  |      */ | |||
|  |     function _escape(str) { | |||
|  |         local res = ""; | |||
|  | 
 | |||
|  |         for (local i = 0; i< str.len(); i++) { | |||
|  | 
 | |||
|  |             local ch1 = (str[i] & 0xFF); | |||
|  | 
 | |||
|  |             if ((ch1 & 0x80) == 0x00) { | |||
|  |                 // 7-bit Ascii | |||
|  | 
 | |||
|  |                 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) { | |||
|  |                     // 110xxxxx = 2-byte unicode | |||
|  |                     local ch2 = (str[++i] & 0xFF); | |||
|  |                     res += format("%c%c", ch1, ch2); | |||
|  |                 } else if ((ch1 & 0xF0) == 0xE0) { | |||
|  |                     // 1110xxxx = 3-byte unicode | |||
|  |                     local ch2 = (str[++i] & 0xFF); | |||
|  |                     local ch3 = (str[++i] & 0xFF); | |||
|  |                     res += format("%c%c%c", ch1, ch2, ch3); | |||
|  |                     // return str; | |||
|  |                 } else if ((ch1 & 0xF8) == 0xF0) { | |||
|  |                     // 11110xxx = 4 byte unicode | |||
|  |                     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; | |||
|  |     } | |||
|  | } |