diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/Neon.js b/Neon.js deleted file mode 100644 index 9682fb0..0000000 --- a/Neon.js +++ /dev/null @@ -1,477 +0,0 @@ -/** - * Copyright (c) 2004 David Grudl (http://davidgrudl.com) - * Copyright (c) 2014 David Matejka - * - */ -var Neon = {}; - -(function () { - Neon.CHAIN = '!!chain'; - - Neon.encode = function (xvar, options) { - if (typeof options === "undefined") { - options = null; - } - - var encoder = new Encoder; - return encoder.encode(xvar, options); - }; - Neon.decode = function (input) { - var decoder = new Neon.Decoder; - return decoder.decode(input); - }; - - /** - * Simple generator for Nette Object Notation. - * - * @author David Grudl - */ - Neon.Encoder = function () { - BLOCK = 1; - - /** - * Returns the NEON representation of a value. - * @param xvar mixed - * @param options int - * @return string - */ - this.encode = function (xvar, options) { - if (typeof options === "undefined") { - options = null; - } - - if (xvar instanceof DateTime) { - return xvar.format('Y-m-d H:i:s O'); - - } else if (xvar instanceof Neon.Entity) { - var attrs = ''; - if (xval.attributes instanceof Array) { - attrs = this.encode(xval.attributes); - attrs = attrs.substr(1, attrs.length - 2); - } - return this.encode(xvar.value) + "" + '(' + "" + attrs + "" + ')'; - } - - if (typeof xvar == 'object') { - var obj = xvar; - xvar = {}; - for (var k in obj) { - var v = obj[k]; - xvar[k] = v; - } - } - - if (xvar instanceof Array) { - var isList = !xvar || (function (arr) { - var i = 0; - for (key in arr) { - if (key != i) { - return false; - } - i++; - } - return true; - })(xvar); - var s = ''; - if (options & Neon.Encoder.BLOCK) { - if ((function (arr) { - for (key in arr) { - return false; - } - return true; - })(xvar)) { - return '[]'; - } - for (var k in xvar) { - var v = xvar[k]; - v = this.encode(v, Neon.Encoder.BLOCK); - s += (isList ? '-' : Encoder.encode(k) + "" + ':') - + "" + (v.indexOf("\n") === false ? ' ' + "" + v : "\n\t" + "" + v.replace("\n", "\n\t")) - + "" + "\n"; - } - return s; - - } else { - for (var k in xvar) { - var v = xvar[k]; - s += (isList ? '' : this.encode(k) + "" + ': ') + "" + this.encode(v) + "" + ', '; - } - return (isList ? '[' : '{') + "" + s.substr(0, s.length - 2) + "" + (isList ? ']' : '}'); - } - - } else if (typeof xvar == "string" && isNaN(xvar) - && !preg_match('~[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)\z~i', xvar) - && preg_match('~^' + "" + this.patterns[1] + "" + '\z~x', xvar) // 1 = literals - ) { - return xvar; - - } else if (is_float(xvar)) { - xvar = JSON.stringify(xvar); - return xvar.indexOf('.') === -1 ? xvar + "" + '.0' : xvar; - - } else { - return JSON.stringify(xvar); - } - }; - - - }; - - - /** - * Representation of 'foo(bar=1)' literal - */ - Neon.Entity = function (value, /*array*/ attrs) { - - this.value = null; - this.attributes = null; - - if (typeof value === "undefined") { - value = null; - } - - if (typeof attrs === "undefined") { - attrs = null; - } - - this.value = value; - this.attributes = attrs; - }; - - Neon.Result = function () { - this.data = null; - this.key = 0; - - this.add = function (key, value) { - if (this.data == null) { - this.data = {}; - } - if (key == null) { - this.data[this.key++] = value; - return true; - } - if (key in this.data) { - return false; - } - var number = parseInt(key * 1); - if (!isNaN(number) && number > this.key) { - this.key = number; - } - this.data[key] = value; - return true; - } - }; - - - /** - * Parser for Nette Object Notation. - * - * @author David Grudl - * @internal - */ - - Neon.Decoder = function () { - - /** @var array */ - var tokens; - - /** @var int */ - var pos; - - var input; - - - /** - * Decodes a NEON string. - * @param input string - * @return mixed - */ - this.decode = function (input) { - - if (typeof (input) != "string") { - throw 'Argument must be a string, ' + typeof input + ' given.'; - - } else if (input.substr(0, 3) == "\xEF\xBB\xBF") { // BOM - input = input.substr(3); - } - this.input = "\n" + "" + input.replace("\r", "") + "\n"; // \n forces indent detection - - var pattern = '~^(' + "" + Neon.Decoder.patterns.join(')|(') + "" + ')~mi'; - this.tokens = preg_split(pattern, this.input, -1, 1 | 2 | 4); - - var last = this.tokens[this.tokens.length - 1]; - if (this.tokens && !preg_match(pattern, last[0], [])) { - pos = this.tokens.length - 1; - this.error(); - } - - this.pos = 0; - var res = this.parse(null); - - while ((this.tokens[this.pos])) { - if (this.tokens[this.pos][0][0] === "\n") { - this.pos++; - } else { - this.error(); - } - } - return res; - }; - - - /** - * @param indent string indentation (for block-parser) - * @param key mixed - * @param hasKey bool - * @return array - */ - this.parse = function (indent, key, hasKey) { - if (typeof key === "undefined") { - key = null; - } - - if (typeof hasKey === "undefined") { - hasKey = false; - } - var result = new Neon.Result(); - var inlineParser = indent === false; - var value = null; - var hasValue = false; - var tokens = this.tokens; - var count = tokens.length; - var mainResult = result; - - for (; this.pos < count; this.pos++) { - var t = tokens[this.pos][0]; - - if (t === ',') { // ArrayEntry separator - if ((!hasKey && !hasValue) || !inlineParser) { - this.error(); - } - this.addValue(result, hasKey ? key : null, hasValue ? value : null); - hasKey = hasValue = false; - - } else if (t === ':' || t === '=') { // KeyValuePair separator - if (hasValue && (typeof value == "object")) { - this.error('Unacceptable key'); - - } else if (hasKey && key == null && hasValue && !inlineParser) { - this.pos++; - result[result.length + 1] = this.parse(indent + "" + ' ', value, true); - var newIndent = (tokens[this.pos], tokens[this.pos + 1]) ? tokens[this.pos][0].substr(1) : ''; // not last - if (newIndent.length > indent.length) { - this.pos++; - this.error('Bad indentation'); - } else if (newIndent.length < indent.length) { - return mainResult; // block parser exit point - } - hasKey = hasValue = false; - - } else if (hasKey || !hasValue) { - this.error(); - - } else { - key = value; - hasKey = true; - hasValue = false; - result = mainResult; - } - - } else if (t === '-') { // BlockArray bullet - if (hasKey || hasValue || inlineParser) { - this.error(); - } - key = null; - hasKey = true; - - } else if ((Neon.Decoder.brackets[t])) { // Opening bracket [ ( { - if (hasValue) { - if (t !== '(') { - this.error(); - } - this.pos++; - if (value instanceof Neon.Entity && value.value === Neon.CHAIN) { - end(value.attributes).attributes = this.parse(false); - } else { - value = new Neon.Entity(value, this.parse(false)); - } - } else { - this.pos++; - value = this.parse(false); - } - hasValue = true; - if (!(tokens[this.pos]) || tokens[this.pos][0] !== Neon.Decoder.brackets[t]) { // unexpected type of bracket or block-parser - this.error(); - } - - } else if (t === ']' || t === '}' || t === ')') { // Closing bracket ] ) } - if (!inlineParser) { - this.error(); - } - break; - - } else if (t[0] === "\n") { // Indent - if (inlineParser) { - if (hasKey || hasValue) { - this.addValue(result, hasKey ? key : null, hasValue ? value : null); - hasKey = hasValue = false; - } - - } else { - while ((tokens[this.pos + 1]) && tokens[this.pos + 1][0][0] === "\n") { - this.pos++; // skip to last indent - } - if (!(tokens[this.pos + 1])) { - break; - } - - newIndent = tokens[this.pos][0].substr(1); - if (indent === null) { // first iteration - indent = newIndent; - } - var minlen = Math.min(newIndent.length, indent.length); - if (minlen && newIndent.substr(0, minlen) !== indent.substr(0, minlen)) { - this.pos++; - this.error('Invalid combination of tabs and spaces'); - } - - if (newIndent.length > indent.length) { // open new block-array or hash - if (hasValue || !hasKey) { - this.pos++; - this.error('Bad indentation'); - } - this.addValue(result, key, this.parse(newIndent)); - newIndent = (tokens[this.pos], tokens[this.pos + 1]) ? tokens[this.pos][0].substr(1) : ''; // not last - if (newIndent.length > indent.length) { - this.pos++; - this.error('Bad indentation'); - } - hasKey = false; - - } else { - if (hasValue && !hasKey) { // block items must have "key"; NULL key means list item - break; - - } else if (hasKey) { - this.addValue(result, key, hasValue ? value : null); - if (key !== null && !hasValue && newIndent === indent) { - result = result[key]; - } - hasKey = hasValue = false; - } - } - - if (newIndent.length < indent.length) { // close block - return mainResult; // block parser exit point - } - } - - } else if (hasValue) { // Value - if (value instanceof Neon.Entity) { // Entity chaining - if (value.value !== Neon.CHAIN) { - value = new Neon.Entity(Neon.CHAIN, {0: value}); - } - value.attributes[value.attributes.length + 1] = new Neon.Entity(t); - } else { - this.error(); - } - } else { // Value - if (typeof this.parse.consts == 'undefined') - this.parse.consts = { - 'true': true, 'True': true, 'TRUE': true, 'yes': true, 'Yes': true, 'YES': true, 'on': true, 'On': true, 'ON': true, - 'false': false, 'False': false, 'FALSE': false, 'no': false, 'No': false, 'NO': false, 'off': false, 'Off': false, 'OFF': false, - 'null': 0, 'Null': 0, 'NULL': 0, - }; - if (t[0] === '"') { - value = preg_replace_callback("#\\\\(?:u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i", this.cbString, t.substr(1, t.length - 2)); - } else if (t[0] === "'") { - value = t.substr(1, t.length - 2); - } else if ((this.parse.consts[t]) && (!(tokens[this.pos + 1][0]) || (tokens[this.pos + 1][0] !== ':' && tokens[this.pos + 1][0] !== '='))) { - value = this.parse.consts[t] === 0 ? null : this.parse.consts[t]; - } else if (!isNaN(t)) { - value = t * 1; - } else if (preg_match('#^\\d\\d\\d\\d-\\d\\d?-\\d\\d?(?:(?:[Tt]| +)\\d\\d?:\\d\\d:\\d\\d(?:\\.\\d*)? *(?:Z|[-+]\\d\\d?(?::\\d\\d)?)?)?\\z#', t)) { - value = new Date(t); - } else { // literal - value = t; - } - hasValue = true; - } - } - - if (inlineParser) { - if (hasKey || hasValue) { - this.addValue(result, hasKey ? key : null, hasValue ? value : null); - } - } else { - if (hasValue && !hasKey) { // block items must have "key" - if (result.data == null) { - result.data = value; // simple value parser - } else { - this.error(); - } - } else if (hasKey) { - this.addValue(result, key, hasValue ? value : null); - } - } - return mainResult.data; - }; - - - this.addValue = function (result, key, value) { - if (!result.add(key, value)) { - this.error("Duplicated key '" + key + "'"); - } - }; - - - this.cbString = function (m) { - var mapping = {'t': "\t", 'n': "\n", 'r': "\r", 'f': "\x0C", 'b': "\x08", '"': '"', '\\': '\\', '/': '/', '_': "\xc2\xa0"}; - var sq = m[0]; - if ((mapping[sq[1]])) { - return mapping[sq[1]]; - } else if (sq[1] === 'u' && sq.length === 6) { - return String.fromCharCode(parseInt(sq.substr(2), 16)); - } else if (sq[1] === 'x' && sq.length === 4) { - return String.fromCharCode(parseInt(sq.substr(2), 16)); - } else { - this.error("Invalid escaping sequence " + sq + ""); - } - }; - - - this.error = function (message) { - if (typeof message === "undefined") { - message = "Unexpected '%s'"; - } - - var last = (this.tokens[pos]) ? this.tokens[pos] : null; - var offset = last ? last[1] : this.input.length; - var text = this.input.substr(0, offset); - var line = substr_count(text, "\n"); - var col = offset - ("\n" + "" + text).lastIndexOf("\n") + 1; - var token = last ? str_replace("\n", '', last[0].substr(0, 40)) : 'end'; - throw message.replace("%s", token) + "" + " on line " + line + ", column " + col + "."; - }; - - }; - - Neon.Decoder.patterns = [ - "'[^'\\n]*' |\t\"(?: \\\\. | [^\"\\\\\\n] )*\"", - "(?: [^#\"',:=[\\]{}()\\x00-\\x20!`-] | [:-][^\"',\\]})\\s] )(?:[^,:=\\]})(\\x00-\\x20]+ |:(?! [\\s,\\]})] | $ ) |[\\ \\t]+ [^#,:=\\]})(\\x00-\\x20])*", - "[,:=[\\]{}()-]", - "?:\\#.*", - "\\n[\\t\\ ]*", - "?:[\\t\\ ]+"]; - - Neon.Decoder.brackets = { - '[': ']', - '{': '}', - '(': ')' - }; - - -})(); - - -console.log(Neon.decode("[foo: bar, lorem]")); diff --git a/src/decoder.js b/src/decoder.js new file mode 100644 index 0000000..60b8dea --- /dev/null +++ b/src/decoder.js @@ -0,0 +1,359 @@ +var entity = require("./entity"); +var php = require("./php"); + + +function Result () { + this.key = 0; + this.value = null; + this.add = function (key, value) { + if (this.value === null) { + this.value = {}; + } + if (key === null) { + this.value[this.key++] = value; + return true; + } + if (key in this.value) { + return false; + + } + var number = parseInt(key * 1); + if (!isNaN(number) && number > this.key) { + this.key = number; + } + this.value[key] = value; + return true; + }; +}; + + +function decoder() { + + /** @var array */ + this.tokens = []; + + /** @var int */ + this.pos = 0; + + this.input = ""; + + + /** + * Decodes a NEON string. + * @param input string + * @return mixed + */ + this.decode = function (input) { + + if (typeof (input) != "string") { + throw 'Argument must be a string, ' + typeof input + ' given.'; + + } else if (input.substr(0, 3) == "\xEF\xBB\xBF") { // BOM + input = input.substr(3); + } + this.input = "\n" + "" + input.replace("\r", "") + "\n"; // \n forces indent detection + + var pattern = '~^(' + "" + decoder.patterns.join(')|(') + "" + ')~mi'; + this.tokens = php.preg_split(pattern, this.input, -1, 1 | 2 | 4); + + var last = this.tokens[this.tokens.length - 1]; + if (this.tokens && !php.preg_match(pattern, last[0], [])) { + pos = this.tokens.length - 1; + this.error(); + } + + this.pos = 0; + var res = this.parse(null); + + while ((this.tokens[this.pos])) { + if (this.tokens[this.pos][0][0] === "\n") { + this.pos++; + } else { + this.error(); + } + } + var flatten = function (res) { + if (res instanceof Result) { + return flatten(res.value); + } else if(res instanceof entity.Entity) { + res.attributes = flatten(res.attributes); + res.value = flatten(res.value); + } else if(res instanceof Object) { + var result = {}; + for(i in res) { + result[i] = flatten(res[i]); + } + return result; + } + return res; + + }; + return flatten(res); + + }; + + + /** + * @param indent string indentation (for block-parser) + * @param key mixed + * @param hasKey bool + * @return array + */ + this.parse = function (indent, defaultValue, key, hasKey) { + if (typeof key === "undefined") { + key = null; + } + if(typeof defaultValue === "undefined") { + defaultValue = null; + } + + if (typeof hasKey === "undefined") { + hasKey = false; + } + var result = new Result(); + result.value = defaultValue; + var inlineParser = indent === false; + var value = null; + var hasValue = false; + var tokens = this.tokens; + var count = tokens.length; + var mainResult = result; + + for (; this.pos < count; this.pos++) { + var t = tokens[this.pos][0]; + + if (t === ',') { // ArrayEntry separator + if ((!hasKey && !hasValue) || !inlineParser) { + this.error(); + } + this.addValue(result, hasKey ? key : null, hasValue ? value : null); + hasKey = hasValue = false; + + } else if (t === ':' || t === '=') { // KeyValuePair separator + if (hasValue && (typeof value == "object")) { + this.error('Unacceptable key'); + + } else if (hasKey && key == null && hasValue && !inlineParser) { + this.pos++; + this.addValue(result, null, this.parse(indent + "" + ' ', {}, value, true)); + var newIndent = (tokens[this.pos], tokens[this.pos + 1]) ? tokens[this.pos][0].substr(1) : ''; // not last + if (newIndent.length > indent.length) { + this.pos++; + this.error('Bad indentation'); + } else if (newIndent.length < indent.length) { + return mainResult; // block parser exit point + } + hasKey = hasValue = false; + + } else if (hasKey || !hasValue) { + this.error(); + + } else { + key = value; + hasKey = true; + hasValue = false; + result = mainResult; + } + + } else if (t === '-') { // BlockArray bullet + if (hasKey || hasValue || inlineParser) { + this.error(); + } + key = null; + hasKey = true; + + } else if ((decoder.brackets[t])) { // Opening bracket [ ( { + if (hasValue) { + if (t !== '(') { + this.error(); + } + this.pos++; + if (value instanceof entity.Entity && value.value === decoder.CHAIN) { + (function(obj) { + var last = null; + for(var i in obj) { + last = obj[i]; + } + return last; + })(value.attributes.value).attributes = this.parse(false, {}); + } else { + value = new entity.Entity(value, this.parse(false, {})); + } + } else { + this.pos++; + value = this.parse(false, {}); + } + hasValue = true; + if (tokens[this.pos] === undefined || tokens[this.pos][0] !== decoder.brackets[t]) { // unexpected type of bracket or block-parser + this.error(); + } + + } else if (t === ']' || t === '}' || t === ')') { // Closing bracket ] ) } + if (!inlineParser) { + this.error(); + } + break; + + } else if (t[0] === "\n") { // Indent + if (inlineParser) { + if (hasKey || hasValue) { + this.addValue(result, hasKey ? key : null, hasValue ? value : null); + hasKey = hasValue = false; + } + + } else { + while (tokens[this.pos + 1] !== undefined && tokens[this.pos + 1][0][0] === "\n") { + this.pos++; // skip to last indent + } + if (tokens[this.pos + 1] === undefined) { + break; + } + + newIndent = tokens[this.pos][0].substr(1); + if (indent === null) { // first iteration + indent = newIndent; + } + var minlen = Math.min(newIndent.length, indent.length); + if (minlen && newIndent.substr(0, minlen) !== indent.substr(0, minlen)) { + this.pos++; + this.error('Invalid combination of tabs and spaces'); + } + + if (newIndent.length > indent.length) { // open new block-array or hash + if (hasValue || !hasKey) { + this.pos++; + this.error('Bad indentation'); + } + this.addValue(result, key, this.parse(newIndent, {})); + newIndent = (tokens[this.pos] !== undefined && tokens[this.pos + 1] !== undefined) ? tokens[this.pos][0].substr(1) : ''; // not last + if (newIndent.length > indent.length) { + this.pos++; + this.error('Bad indentation'); + } + hasKey = false; + + } else { + if (hasValue && !hasKey) { // block items must have "key"; NULL key means list item + break; + + } else if (hasKey) { + this.addValue(result, key, hasValue ? value : null); + if (key !== null && !hasValue && newIndent === indent) { + result = result.value[key] = new Result; + } + + hasKey = hasValue = false; + } + } + + if (newIndent.length < indent.length) { // close block + return mainResult; // block parser exit point + } + } + + } else if (hasValue) { // Value + if (value instanceof entity.Entity) { // Entity chaining + if (value.value !== decoder.CHAIN) { + var attributes = new Result(); + attributes.add(null, value); + value = new entity.Entity(decoder.CHAIN, attributes); + } + value.attributes.add(null, new entity.Entity(t)); + } else { + this.error(); + } + } else { // Value + if (typeof this.parse.consts == 'undefined') + this.parse.consts = { + 'true': true, 'True': true, 'TRUE': true, 'yes': true, 'Yes': true, 'YES': true, 'on': true, 'On': true, 'ON': true, + 'false': false, 'False': false, 'FALSE': false, 'no': false, 'No': false, 'NO': false, 'off': false, 'Off': false, 'OFF': false, + 'null': 0, 'Null': 0, 'NULL': 0 + }; + if (t[0] === '"') { + value = t.substr(1, t.length - 2).replace(/#\\\\(?:u[0-9a-f]{4}|x[0-9a-f]{2}|.)/i, function(whole, sq) { + var mapping = {'t': "\t", 'n': "\n", 'r': "\r", 'f': "\x0C", 'b': "\x08", '"': '"', '\\': '\\', '/': '/', '_': "\xc2\xa0"}; + if (mapping[sq[1]] !== undefined) { + return mapping[sq[1]]; + } else if (sq[1] === 'u' && sq.length === 6) { + return String.fromCharCode(parseInt(sq.substr(2), 16)); + } else if (sq[1] === 'x' && sq.length === 4) { + return String.fromCharCode(parseInt(sq.substr(2), 16)); + } else { + this.error("Invalid escaping sequence " + sq + ""); + } + }); + } else if (t[0] === "'") { + value = t.substr(1, t.length - 2); + } else if ((this.parse.consts[t]) !== undefined && (tokens[this.pos + 1][0] === undefined || (tokens[this.pos + 1][0] !== ':' && tokens[this.pos + 1][0] !== '='))) { + value = this.parse.consts[t] === 0 ? null : this.parse.consts[t]; + } else if (!isNaN(t)) { + value = t * 1; + } else if (php.preg_match('#^\\d\\d\\d\\d-\\d\\d?-\\d\\d?(?:(?:[Tt]| +)\\d\\d?:\\d\\d:\\d\\d(?:\\.\\d*)? *(?:Z|[-+]\\d\\d?(?::\\d\\d)?)?)?\\z#', t)) { + value = new Date(t); + } else { // literal + value = t; + } + hasValue = true; + } + } + + if (inlineParser) { + if (hasKey || hasValue) { + this.addValue(result, hasKey ? key : null, hasValue ? value : null); + } + } else { + if (hasValue && !hasKey) { // block items must have "key" + if (result.value === null) { //if empty + return value; // simple value parser + } else { + this.error(); + } + } else if (hasKey) { + this.addValue(result, key, hasValue ? value : null); + } + } + return mainResult; + }; + + + this.addValue = function (result, key, value) { + if (result.add(key, value) === false) { + this.error("Duplicated key '" + key + "'"); + } + }; + + + + + this.error = function (message) { + if (typeof message === "undefined") { + message = "Unexpected '%s'"; + } + + var last = this.tokens[this.pos] !== undefined ? this.tokens[this.pos] : null; + var offset = last ? last[1] : this.input.length; + var text = this.input.substr(0, offset); + var line = text.split("\n").length - 1; + var col = offset - ("\n" + "" + text).lastIndexOf("\n") + 1; + var token = last ? last[0].substr(0, 40).replace("\n", '') : 'end'; + throw new Error(message.replace("%s", token) + "" + " on line " + line + ", column " + col + "."); + }; + +} + +decoder.patterns = [ + "'[^'\\n]*' |\t\"(?: \\\\. | [^\"\\\\\\n] )*\"", + "(?: [^#\"',:=[\\]{}()\\x00-\\x20!`-] | [:-][^\"',\\]})\\s] )(?:[^,:=\\]})(\\x00-\\x20]+ |:(?! [\\s,\\]})] | $ ) |[\\ \\t]+ [^#,:=\\]})(\\x00-\\x20])*", + "[,:=[\\]{}()-]", + "?:\\#.*", + "\\n[\\t\\ ]*", + "?:[\\t\\ ]+"]; + +decoder.brackets = { + '[': ']', + '{': '}', + '(': ')' +}; +decoder.CHAIN = '!!chain'; + +module.exports = decoder; diff --git a/src/encoder.js b/src/encoder.js new file mode 100644 index 0000000..7837ce1 --- /dev/null +++ b/src/encoder.js @@ -0,0 +1,92 @@ +function encoder() { + BLOCK = 1; + + /** + * Returns the NEON representation of a value. + * @param xvar mixed + * @param options int + * @return string + */ + this.encode = function (xvar, options) { + if (typeof options === "undefined") { + options = null; + } + + if (xvar instanceof DateTime) { + return xvar.format('Y-m-d H:i:s O'); + + } else if (xvar instanceof Neon.Entity) { + var attrs = ''; + if (xval.attributes instanceof Array) { + attrs = this.encode(xval.attributes); + attrs = attrs.substr(1, attrs.length - 2); + } + return this.encode(xvar.value) + "" + '(' + "" + attrs + "" + ')'; + } + + if (typeof xvar == 'object') { + var obj = xvar; + xvar = {}; + for (var k in obj) { + var v = obj[k]; + xvar[k] = v; + } + } + + if (xvar instanceof Array) { + var isList = !xvar || (function (arr) { + var i = 0; + for (key in arr) { + if (key != i) { + return false; + } + i++; + } + return true; + })(xvar); + var s = ''; + if (options & encoder.BLOCK) { + if ((function (arr) { + for (key in arr) { + return false; + } + return true; + })(xvar)) { + return '[]'; + } + for (var k in xvar) { + var v = xvar[k]; + v = this.encode(v, encoder.BLOCK); + s += (isList ? '-' : encoder.encode(k) + "" + ':') + + "" + (v.indexOf("\n") === false ? ' ' + "" + v : "\n\t" + "" + v.replace("\n", "\n\t")) + + "" + "\n"; + } + return s; + + } else { + for (var k in xvar) { + var v = xvar[k]; + s += (isList ? '' : this.encode(k) + "" + ': ') + "" + this.encode(v) + "" + ', '; + } + return (isList ? '[' : '{') + "" + s.substr(0, s.length - 2) + "" + (isList ? ']' : '}'); + } + + } else if (typeof xvar == "string" && isNaN(xvar) + && !preg_match('~[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)\z~i', xvar) + && preg_match('~^' + "" + this.patterns[1] + "" + '\z~x', xvar) // 1 = literals + ) { + return xvar; + + } else if (is_float(xvar)) { + xvar = JSON.stringify(xvar); + return xvar.indexOf('.') === -1 ? xvar + "" + '.0' : xvar; + + } else { + return JSON.stringify(xvar); + } + }; + + +} + +module.exports.encoder = encoder; diff --git a/src/entity.js b/src/entity.js new file mode 100644 index 0000000..192c5ab --- /dev/null +++ b/src/entity.js @@ -0,0 +1,16 @@ +module.exports.Entity = function (value, attrs) { + + this.value = null; + this.attributes = null; + + if (typeof value === "undefined") { + value = null; + } + + if (typeof attrs === "undefined") { + attrs = null; + } + + this.value = value; + this.attributes = attrs; +}; diff --git a/src/neon.js b/src/neon.js new file mode 100644 index 0000000..88adcb4 --- /dev/null +++ b/src/neon.js @@ -0,0 +1,19 @@ +//var encoder = require("./encoder"); +var encode = function (xvar, options) { + if (typeof options === "undefined") { + options = null; + } + + var encoder = new encoder(); + return encoder.encode(xvar, options); +}; + + +module.exports.encode = encode; +module.exports.decode = function (input) { + var decoder = new (require("./decoder"))(); + + return decoder.decode(input); +}; +module.exports.Entity = require('./entity').Entity; +module.exports.CHAIN = require('./decoder').CHAIN; diff --git a/src/php.js b/src/php.js new file mode 100644 index 0000000..a5a573f --- /dev/null +++ b/src/php.js @@ -0,0 +1,305 @@ +function preg_split(pattern, subject, limit, flags) { + // http://kevin.vanzonneveld.net + // + original by: Marco MarchiĆ² + // * example 1: preg_split(/[\s,]+/, 'hypertext language, programming'); + // * returns 1: ['hypertext', 'language', 'programming'] + // * example 2: preg_split('//', 'string', -1, 'PREG_SPLIT_NO_EMPTY'); + // * returns 2: ['s', 't', 'r', 'i', 'n', 'g'] + // * example 3: var str = 'hypertext language programming'; + // * example 3: preg_split('/ /', str, -1, 'PREG_SPLIT_OFFSET_CAPTURE'); + // * returns 3: [['hypertext', 0], ['language', 10], ['programming', 19]] + // * example 4: preg_split('/( )/', '1 2 3 4 5 6 7 8', 4, 'PREG_SPLIT_DELIM_CAPTURE'); + // * returns 4: ['1', ' ', '2', ' ', '3', ' ', '4 5 6 7 8'] + // * example 5: preg_split('/( )/', '1 2 3 4 5 6 7 8', 4, (2 | 4)); + // * returns 5: [['1', 0], [' ', 1], ['2', 2], [' ', 3], ['3', 4], [' ', 5], ['4 5 6 7 8', 6]] + + limit = limit || 0; + flags = flags || ''; // Limit and flags are optional + pattern = pattern.toString(); + var result, ret = [], index = 0, i = 0, + noEmpty = false, delim = false, offset = false, + OPTS = {}, optTemp = 0, + delimiter = pattern[0], + lastIndex = pattern.lastIndexOf(delimiter), + regexpBody = pattern.substring(1, lastIndex), + regexpFlags = pattern.substring(lastIndex + 1); + // Non-global regexp causes an infinite loop when executing the while, + // so if it's not global, copy the regexp and add the "g" modifier. + pattern = pattern.global && typeof pattern !== 'string' ? pattern : + new RegExp(regexpBody, regexpFlags + (regexpFlags.indexOf('g') !== -1 ? '' : 'g')); + + OPTS = { + 'PREG_SPLIT_NO_EMPTY': 1, + 'PREG_SPLIT_DELIM_CAPTURE': 2, + 'PREG_SPLIT_OFFSET_CAPTURE': 4 + }; + if (typeof flags !== 'number') { // Allow for a single string or an array of string flags + flags = [].concat(flags); + for (i = 0; i < flags.length; i++) { + // Resolve string input to bitwise e.g. 'PREG_SPLIT_OFFSET_CAPTURE' becomes 4 + if (OPTS[flags[i]]) { + optTemp = optTemp | OPTS[flags[i]]; + } + } + flags = optTemp; + } + noEmpty = flags & OPTS.PREG_SPLIT_NO_EMPTY; + delim = flags & OPTS.PREG_SPLIT_DELIM_CAPTURE; + offset = flags & OPTS.PREG_SPLIT_OFFSET_CAPTURE; + + var _filter = function (str, strindex) { + // If the match is empty and the PREG_SPLIT_NO_EMPTY flag is set don't add it + if (noEmpty && !str.length) { + return; + } + // If the PREG_SPLIT_OFFSET_CAPTURE flag is set + // transform the match into an array and add the index at position 1 + if (offset) { + str = [str, strindex]; + } + ret.push(str); + }; + // Special case for empty regexp + if (!regexpBody) { + result = subject.split(''); + for (i = 0; i < result.length; i++) { + _filter(result[i], i); + } + return ret; + } + // Exec the pattern and get the result + while (result = pattern.exec(subject)) { + // Stop if the limit is 1 + if (limit === 1) { + break; + } + // Take the correct portion of the string and filter the match + _filter(subject.slice(index, result.index), index); + index = result.index + result[0].length; + // If the PREG_SPLIT_DELIM_CAPTURE flag is set, every capture match must be included in the results array + if (delim) { + // Convert the regexp result into a normal array + var resarr = Array.prototype.slice.call(result); + for (i = 1; i < resarr.length; i++) { + if (result[i] !== undefined) { + _filter(result[i], result.index + result[0].indexOf(result[i])); + } + } + } + limit--; + } + // Filter last match + _filter(subject.slice(index, subject.length), index); + return ret; +} +function preg_match(pattern, subject, matches, flags, offset) { + // http://kevin.vanzonneveld.net + // + original by: Francis Lewis + // + improved by: Brett Zamir (http://brett-zamir.me) + // * example 1: matches = []; + // * example 1: preg_match(/(\w+)\W([\W\w]+)/, 'this is some text', matches); + // * matches 1: matches[1] == 'this' + // * returns 1: 1 + + // UNFINISHED + // Just found something we should take a very serious look at Steve Levithan's XRegExp which implements Unicode classes and two extra flags: http://blog.stevenlevithan.com/archives/xregexp-javascript-regex-constructor + // Before finding this, I was working on a script to search through an SQLite database to build our Unicode expressions automatically; I may finish that as it should be expandable for the future, and be an extra eye to confirm Steve's work + // Also need to look at/integrate with Michael Grier's http://mgrier.com/te5t/preg_match_all.js ; http://mgrier.com/te5t/testpma.html ; http://mgrier.com/te5t/testpma.php + + var i = 0, lastDelimPos = -1, flag = '', patternPart = '', flagPart = '', array = [], regexpFlags = '', subPatternNames = []; + var getFuncName = function (fn) { + var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn); + if (!name) { + return '(Anonymous)'; + } + return name[1]; + }; + + var join = function (arr) { + return '(?:' + arr.join('|') + ')'; + }; + + if (typeof pattern === 'string') { + if (pattern === '') { + // Handle how? + } + + lastDelimPos = pattern.lastIndexOf(pattern[0]); + if (lastDelimPos === 0) { // convenience to allow raw string without delimiters // || a-zA-Z/.test(pattern[0]) || pattern.length === 1) { // The user is probably not using letters for delimiters (not recommended, but could be convenient for non-flagged expressions) + pattern = new RegExp(pattern); + } + else { + patternPart = pattern.slice(1, lastDelimPos); + flagPart = pattern.slice(lastDelimPos + 1); + // Fix: Need to study http://php.net/manual/en/regexp.reference.php more thoroughly + // e.g., internal options i, m, s, x, U, X, J; conditional subpatterns?, comments, recursive subpatterns, + for (i = 0; i < flagPart.length; i++) { + flag = flagPart[i]; + switch (flag) { + case 'g': // We don't use this in preg_match, but it's presumably not an error + case 'm': + case 'i': + regexpFlags += flag; + break; + case 'e': // used in preg_replace only but ignored elsewhere; "does normal substitution of backreferences in the replacement string, evaluates it as PHP code, and uses the result for replacing the search string". "Single quotes, double quotes, backslashes and NULL chars will be escaped by backslashes in substituted backreferences." + // Safely ignorable + break; + case 's': // "dot metacharacter in the pattern matches all characters, including newlines. Without it, newlines are excluded... A negative class such as [^a] always matches a newline character" + case 'x': // "whitespace data characters in the pattern are totally ignored except when escaped or inside a character class, and characters between an unescaped # outside a character class and the next newline character, inclusive, are also ignored"; "Whitespace characters may never appear within special character sequences in a pattern" + case 'A': // pattern is "constrained to match only at the start of the string which is being searched" + case 'D': // "a dollar metacharacter in the pattern matches only at the end of the subject string" (ignored if 'm' set) + case 'U': // "makes not greedy by default, but become greedy if followed by "?"" + case 'J': // "changes the local PCRE_DUPNAMES option. Allow duplicate names for subpatterns" + case 'u': // "turns on additional functionality of PCRE that is incompatible with Perl. Pattern strings are treated as UTF-8." + throw 'The passed flag "' + flag + '" is presently unsupported in ' + getFuncName(arguments.callee); + case 'X': // "additional functionality of PCRE that is incompatible with Perl. Any backslash in a pattern that is followed by a letter that has no special meaning causes an error, thus reserving these combinations for future expansion"; not in use in PHP presently + throw 'X flag is unimplemented at present'; + if (/\/([^\\^$.[\]|()?*+{}aefnrtdDhHsSvVwWbBAZzGCcxkgpPX\d])/.test(patternPart)) { // can be 1-3 \d together after backslash (as one unit) + // \C = single byte (useful in 'u'/UTF8 mode) + // CcxpPXkg are all special uses; + //c. (any character after 'c' for control character) + // x[a-fA-F\d][a-fA-F\d] (hex) + // "Back references to the named subpatterns can be achieved by (?P=name) or, since PHP 5.2.4, also by \k, \k'name', \k{name} or \g{name}" + // Unicode classes (with u flag only) + // p{} | P{} (case insensitive does not affect) + // [CLMNPSZ] + // C|Cc|Cf|Cn|Co|Cs|L|Ll|Lm|Lo|Lt|Lu|M|Mc|Me|Mn|N|Nd|Nl|No|P|Pc|Pd|Pe|Pf|Pi|Po|Ps|S|Sc|Sk|Sm|So|Z|Zl|Zp|Zs + + // Other, Control + // Cc = '[\u0000-\u001f\u007f-\u009f]'; + // Other, Format + // Cf = '(?:[\u00ad\u0600-\u0603\u06dd\u070f\u17b4-\u17b5\u200b-\u200f\u202a-\u202e\u2060-\u2064\u206a-\u206f\ufeff\ufff9-\ufffb]|[\ud834][\udd73-\udd7a]|[\udb40][\udc01\udc20-\udc58]'); /* latter surrogates represent 1d173-1d17a, e0001, e0020-e0058 */ + // Other, Unassigned + // Cn = TO-DO; + // Other, Private use + // Co = '(?:[\ue000-\uf8ff]|[\udb80-\udbbe][\udc00-\udfff]|[\udbff][\udc00-\udffd]|[\udbc0-\udbfe][\udc00-\udfff]|[\udbff][\udc00-\udffd])'; // f0000-ffffd, 100000-10fffd + // Other, Surrogate + // Cs = '[\ud800-\udb7f\udb80-\udbff\udc00-\udfff]'; + + // Need to finish Cn (above) and Ll-Sm here below + // Letter, Lower case + // Ll = '[]'; + // Letter, Modifier + // Lm = + // Letter, Other + // Lo = + // Letter, Title case + // Lt = + // Letter, Upper case + // Lu = + // Mark, Spacing + // Mc = + // Mark, Enclosing + // Me = + // Mark, Non-spacing + // Mn = + // Number, Decimal + // Nd = + // Number, letter + // Nl = + // Number, Other + // No = + // Punctuation, Connector + // Pc = + // Punctuation, Dash + // Pd = + // Punctuation, Close + // Pe = + // Punctuation, Final + // Pf = + // Punctuation, Initial + // Pi = + // Punctuation, Other + // Po = + // Punctuation, Open + // Ps = + // Symbol, Currency + // Sc = + // Symbol, Modifier + // Sk = + // Symbol, Mathematical + // Sm ='\u002b\u003c-\u003e\u007c\u007e\u00ac\u00b1\u00d7\u00f7\u03f6\u0606-\u0608\u2044\u2052\u207a-\u207c\u208a-\u208c\u2140-\u2144\u214b\u2190-\u2194\u219a\u219b\u21a0\u21a3\u21a6\u21ae\u21ce\u21cf\u21d2\u21d4\u21f4-\u22ff\u2308-\u230b\u2320\u2321\u237c\u239b-\u23b3\u23dc-\u23e1\u25b7\u25c1\u25f8-\u25ff\u266f\u27c0-\u27c4\u27c7-\u27ca\u27cc\u27d0-\u27e5\u27f0-\u27ff\u2900-\u2982\u2999-\u29d7\u29dc-\u29fb\u29fe-\u2aff\u2b30-\u2b44\u2b47-\u2b4c\ufb29\ufe62\ufe64-\ufe66\uff0b\uff1c-\uff1e\uff5c\uff5e\uffe2\uffe9-\uffec + + // 1d6c1 1d6db 1d6fb 1d715 1d735 1d74f 1d76f 1d789 1d7a9 1d7c3 + + // Symbol, Other + // latter alternates are surrogate pairs comprising 10102, 10137-1013f, 10179-10189, 10190-1019b, 101d0-101fc, 1d000-1d0f5, 1d100-1d126, 1d129-1d164, 1d16a-1d16c, 1d183-1d184, 1d18c-1d1a9, 1d1ae-1d1dd, 1d200-1d241, 1d245, 1d300-1d356, 1f000-1f02b, 1f030-1f093 + // So = '(?:[\u00a6\u00a7\u00a9\u00ae\u00b0\u00b6\u0482\u060e\u060f\u06e9\u06fd\u06fe\u07f6\u09fa\u0b70\u0bf3-\u0bf8\u0bfa\u0c7f\u0cf1\u0cf2\u0d79\u0f01-\u0f03\u0f13-\u0f17\u0f1a-\u0f1f\u0f34\u0f36\u0f38\u0fbe-\u0fc5\u0fc7-\u0fcc\u0fce\u0fcf\u109e\u109f\u1360\u1390-\u1399\u1940\u19e0-\u19ff\u1b61-\u1b6a\u1b74-\u1b7c\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211e-\u2123\u2125\u2127\u2129\u212e\u213a\u213b\u214a\u214c\u214d\u214f\u2195-\u2199\u219c-\u219f\u21a1\u21a2\u21a4\u21a5\u21a7-\u21ad\u21af-\u21cd\u21d0\u21d1\u21d3\u21d5-\u21f3\u2300-\u2307\u230c-\u231f\u2322-\u2328\u232b-\u237b\u237d-\u239a\u23b4-\u23db\u23e2-\u23e7\u2400-\u2426\u2440-\u244a\u249c-\u24e9\u2500-\u25b6\u25b8-\u25c0\u25c2-\u25f7\u2600-\u266e\u2670-\u269d\u26a0-\u26bc\u26c0-\u26c3\u2701-\u2704\u2706-\u2709\u270c-\u2727\u2729-\u274b\u274d\u274f-\u2752\u2756\u2758-\u275e\u2761-\u2767\u2794\u2798-\u27af\u27b1-\u27be\u2800-\u28ff\u2b00-\u2b2f\u2b45\u2b46\u2b50-\u2b54\u2ce5-\u2cea\u2e80-\u2e99\u2e9b-\u2ef3\u2f00-\u2fd5\u2ff0-\u2ffb\u3004\u3012\u3013\u3020\u3036\u3037\u303e\u303f\u3190\u3191\u3196-\u319f\u31c0-\u31e3\u3200-\u321e\u322a-\u3243\u3250\u3260-\u327f\u328a-\u32b0\u32c0-\u32fe\u3300-\u33ff\u4dc0-\u4dff\ua490-\ua4c6\ua828-\ua82b\ufdfd\uffe4\uffe8\uffed\uffee\ufffc\ufffd]|(?:\ud800[\udd02\udd37-\udd3f\udd79-\udd89\udd90-\udd9b\uddd0-\uddfc])|(?:\ud834[\udc00-\udcf5\udd00-\udd26\udd29-\udd64\udd6a-\udd6c\udd83-\udd84\udd8c-\udda9\uddae-\udddd\ude00-\ude41\ude45\udf00-\udf56])|(?:\ud83c[\udc00-\udc2b\udc30-\udc93]))'; + + // Separator, Line + // Zl = '[\u2028]'; + // Separator, Paragraph + // Zp = '[\u2029]'; + // Separator, Space + // Zs = '[\u0020\u00a0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]'; + + // Form broader groups + // C = join([Cc, Cf, Cn, Co, Cs]); + // L = join([Ll, Lm, Lo, Lt, Lu]); + // M = join([Mc, Me, Mn]); + // N = join([Nd, Nl, No]); + // P = join([Pc, Pd, Pe, Pf, Pi, Po, Ps]); + // S = join([Sc, Sk, Sm, So]); + // Z = join([Zl, Zp, Zs]); + + + // \X = (?>\PM\pM*) + // "Extended properties such as "Greek" or "InMusicalSymbols" are not supported by PCRE." + throw 'You are in "X" (PCRE_EXTRA) mode, using a reserved and presently unused escape sequence in ' + getFuncName(arguments.callee); + } + break; + case 'S': // spends "more time analyzing pattern in order to speed up the time taken for matching" (for subsequent matches) + throw 'The passed flag "' + flag + '" to ' + getFuncName(arguments.callee) + ' cannot be implemented in JavaScript'; // Could possibly optimize inefficient expressions, however + case 'y': + throw 'Flag "y" is a non-cross-browser, non-PHP flag, not supported in ' + getFuncName(arguments.callee); + default: + throw 'Unrecognized flag "' + flag + '" passed to ' + getFuncName(arguments.callee); + } + } + } + } + else { + patternPart = pattern.source; // Allow JavaScript type expressions to take advantage of named subpatterns, so temporarily convert to string + regexpFlags += pattern.global ? 'g' : ''; + regexpFlags += pattern.ignoreCase ? 'i' : ''; + regexpFlags += pattern.multiline ? 'm' : ''; + } + + patternPart = patternPart.replace(/\(\?<(.*?)>(.*?)\)/g, function (namedSubpattern, name, pattern) { + subPatternNames.push(name); + return '(' + pattern + ')'; + }); + + pattern = new RegExp(patternPart, regexpFlags); + + // store the matches in the first index of the array + array[0] = pattern.exec(subject); + + if (!array[0]) { + return 0; + } + + // If the user passed in a RegExp object or literal, we will probably need to reflect on + // its source, ignoreCase, global, and multiline properties to form a new expression (as above?), + // and use lastIndex + if (offset) { + // Not implemented + } + if (flags === 'PREG_OFFSET_CAPTURE' || flags === 256) { // Fix: make flags as number and allow bitwise AND checks against flags; see pathinfo() + // Not implemented + return 1; // matches will need to be different, so we return early here + } + // loop through the first indice of the array and store the values in the $matches array + for (i = 0; i < array[0].length; i++) { + matches[i] = array[0][i]; + if (i > 0 && subPatternNames[i - 1] !== undefined) { + matches[subPatternNames] = array[0][i]; // UNTESTED + } + } + + return 1; +} + + +module.exports.preg_split = preg_split; +module.exports.preg_match = preg_match; diff --git a/test/Decoder.inline.array.js b/test/Decoder.inline.array.js new file mode 100644 index 0000000..f379f79 --- /dev/null +++ b/test/Decoder.inline.array.js @@ -0,0 +1,79 @@ +var assert = require('assert'); +var neon = require('../src/neon'); +suite('Decoder.array', function () { + test('{"foo": "bar"}', function () { + assert.deepEqual({ + foo: "bar" + }, neon.decode('{"foo":"bar"}')); + }); + test('true, false, null constants', function () { + assert.deepEqual({ + 0: true, 1: 'tRuE', 2: true, 3: false, 4: false, 5: true, 6: true, 7: false, 8: false, 9: null, 10: null + }, neon.decode('[true, tRuE, TRUE, false, FALSE, yes, YES, no, NO, null, NULL,]') + ) + }); + test('on, off, false, numbers', function () { + assert.deepEqual({ + "false": false, + "on": true, + "-5": 1, + "5.3": 1 + }, neon.decode('{false: off, "on": true, -5: 1, 5.3: 1}')); + }); + + test('long inline', function () { + assert.deepEqual({ + 0: "a", + 1: "b", + 2: {c: "d"}, + e: "f", + g: null, + h: null + }, neon.decode('{a, b, {c: d}, e: f, g:,h:}')) + }); + + test('5', function () { + assert.deepEqual({ + 0: "a", + 1: "b", + c: 1, + d: 1, + e: 1, + f: null + }, neon.decode("{a,\nb\nc: 1,\nd: 1,\n\ne: 1\nf:\n}")); + }); + test('entity', function () { + assert.ok(neon.decode("@item(a, b)") instanceof neon.Entity); + }); + test('entity', function () { + assert.deepEqual(new neon.Entity("@item", {0: "a", 1: "b"}), neon.decode("@item(a, b)")); + }); + test('entity', function () { + assert.deepEqual(new neon.Entity("@item", {0: "a", 1: "b"}), neon.decode("@item(a, b)")); + }); + test('entity', function () { + assert.deepEqual(new neon.Entity("@item", {0: "a", 1: "b"}), neon.decode("@item (a, b)")); + }); + test('entity', function () { + assert.deepEqual(new neon.Entity({}, {}), neon.decode("[]()")); + }); + test('entity', function () { + assert.deepEqual(new neon.Entity(neon.CHAIN, { + 0: new neon.Entity("first", {0: "a", 1: "b"}), + 1: new neon.Entity("second") + }), neon.decode("first(a, b)second")); + }); + test('entity', function () { + assert.deepEqual(new neon.Entity(neon.CHAIN, { + 0: new neon.Entity("first", {0: "a", 1: "b"}), + 1: new neon.Entity("second", {0: 1, 1: 2}) + }), neon.decode("first(a, b)second(1,2)")); + }); + test('entity', function () { + assert.deepEqual(new neon.Entity(neon.CHAIN, { + 0: new neon.Entity("first", {0: "a", 1: "b"}), + 1: new neon.Entity("second", {0: 1, 1: 2}), + 2: new neon.Entity("third", {x: "foo", y: "bar"}) + }), neon.decode("first(a, b)second(1,2)third(x:foo, y=bar)")); + }); +}); diff --git a/test/Decored.array.js b/test/Decored.array.js new file mode 100644 index 0000000..4676807 --- /dev/null +++ b/test/Decored.array.js @@ -0,0 +1,179 @@ +var assert = require('assert'); +var neon = require('../src/neon'); +suite('Decoder.array', function () { + test('empty list', function () { + assert.deepEqual({ + a: {} + }, neon.decode( + "a: []\n" + )) + }); + test('empty value', function () { + assert.deepEqual({ + a: null, + b: 1 + }, neon.decode( + "a: \n" + + "b:1\n" + )) + }); + test('1', function () { + assert.deepEqual({ + a: {0: 1, 1: 2}, + b: 1 + }, neon.decode( + "\n" + + "a: {1, 2, }\n" + + "b:1" + )); + }); + test('2', function () { + assert.deepEqual({ + a: 1, + b: 2 + }, neon.decode( + " a: 1\n" + + " b: 2" + )); + }); + test('3', function () { + assert.deepEqual({ + a: "x", + 0: "x" + }, neon.decode( + "\n" + + "a: x\n" + + "- x" + )); + }); + test('4', function () { + assert.deepEqual({ + 0: "x", + a: "x" + }, neon.decode( + "\n" + + "- x\n" + + "a: x" + )); + }); + test('5', function () { + assert.deepEqual({ + a: {0: 1, 1: {0: 2}}, + b: {0: 3}, + c: null, + 0: 4 + }, neon.decode( + "\n" + + "a:\n" + + "- 1\n" + + "-\n" + + " - 2\n" + + "b:\n" + + "- 3\n" + + "c: null\n" + + "- 4" + )); + }); + test('5', function () { + assert.deepEqual({ + x: {0: "x", a: "x"} + }, neon.decode("\n" + + "x:\n" + + " - x\n" + + " a: x\n" + )); + }); + test('6', function () { + assert.deepEqual({ + x: {y: {0: null}}, + a: "x" + }, neon.decode( + "x:\n" + + " y:\n" + + " -\n" + + "a: x" + )); + }); + test('7', function () { + assert.deepEqual({ + x: {a: 1, b: 2} + }, neon.decode( + "\n" + + "x: {\n" + + " a: 1\n" + + "b: 2\n" + + "}\n" + )); + }); + test('8', function () { + assert.deepEqual({ + 0: "one", + 1: "two" + }, neon.decode( + "\n" + + "{\n" + + " one\n" + + "two\n" + + "}\n" + )); + }); + test('9', function () { + assert.deepEqual({ + 0: {x: 20, 0: {a: 10, b: 10}}, + 1: {arr: {0: 10, 1: 20}}, + 2: "y" + }, neon.decode( + "\n" + + "- x: 20\n" + + " - a: 10\n" + + " b: 10\n" + + "- arr:\n" + + " - 10\n" + + " - 20\n" + + "- y\n" + )); + }); + test('10', function () { + assert.deepEqual({ + root: {0: {key1: null, key3: 123}} + }, neon.decode( + "\n" + + "root:\n" + + "\t- key1:\n" + + "\t key3: 123\n" + + "\t" + )) + }); + test('11', function () { + assert.deepEqual({ + 0: {x: {a: 10}} + }, neon.decode( + "\n" + + "- x:\n" + + " a: 10\n" + )); + }); + test('12', function () { + assert.deepEqual({ + x: {a: 10}, + y: {b: 20} + }, neon.decode( + "\n" + + "x:\n" + + "\t a: 10\n" + + "y:\n" + + " \tb: 20\n" + )) + }); + test('13', function() { + assert.deepEqual({ + 0: {"null": 42}, + "null": 42 + }, neon.decode( + "\n" + + "- {null= 42}\n" + + "null : 42\n" + )) + }); +}) +;