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;
 | 
						||
    }
 | 
						||
} |