"use strict"; function compile(nodes) { var assignedPaths = []; var valueAssignments = []; var currentPath = ""; var data = Object.create(null); var context = data; var arrayMode = false; return reduce(nodes); function reduce(nodes) { var node; for (var i = 0; i < nodes.length; i++) { node = nodes[i]; switch (node.type) { case "Assign": assign(node); break; case "ObjectPath": setPath(node); break; case "ArrayPath": addTableArray(node); break; } } return data; } function genError(err, line, col) { var ex = new Error(err); ex.line = line; ex.column = col; throw ex; } function assign(node) { var key = node.key; var value = node.value; var line = node.line; var column = node.column; var fullPath; if (currentPath) { fullPath = currentPath + "." + key; } else { fullPath = key; } if (typeof context[key] !== "undefined") { genError("Cannot redefine existing key '" + fullPath + "'.", line, column); } context[key] = reduceValueNode(value); if (!pathAssigned(fullPath)) { assignedPaths.push(fullPath); valueAssignments.push(fullPath); } } function pathAssigned(path) { return assignedPaths.indexOf(path) !== -1; } function reduceValueNode(node) { if (node.type === "Array") { return reduceArrayWithTypeChecking(node.value); } else if (node.type === "InlineTable") { return reduceInlineTableNode(node.value); } else { return node.value; } } function reduceInlineTableNode(values) { var obj = Object.create(null); for (var i = 0; i < values.length; i++) { var val = values[i]; if (val.value.type === "InlineTable") { obj[val.key] = reduceInlineTableNode(val.value.value); } else if (val.type === "InlineTableValue") { obj[val.key] = reduceValueNode(val.value); } } return obj; } function setPath(node) { var path = node.value; var quotedPath = path.map(quoteDottedString).join("."); var line = node.line; var column = node.column; if (pathAssigned(quotedPath)) { genError("Cannot redefine existing key '" + path + "'.", line, column); } assignedPaths.push(quotedPath); context = deepRef(data, path, Object.create(null), line, column); currentPath = path; } function addTableArray(node) { var path = node.value; var quotedPath = path.map(quoteDottedString).join("."); var line = node.line; var column = node.column; if (!pathAssigned(quotedPath)) { assignedPaths.push(quotedPath); } assignedPaths = assignedPaths.filter(function(p) { return p.indexOf(quotedPath) !== 0; }); assignedPaths.push(quotedPath); context = deepRef(data, path, [], line, column); currentPath = quotedPath; if (context instanceof Array) { var newObj = Object.create(null); context.push(newObj); context = newObj; } else { genError("Cannot redefine existing key '" + path + "'.", line, column); } } // Given a path 'a.b.c', create (as necessary) `start.a`, // `start.a.b`, and `start.a.b.c`, assigning `value` to `start.a.b.c`. // If `a` or `b` are arrays and have items in them, the last item in the // array is used as the context for the next sub-path. function deepRef(start, keys, value, line, column) { var traversed = []; var traversedPath = ""; var path = keys.join("."); var ctx = start; for (var i = 0; i < keys.length; i++) { var key = keys[i]; traversed.push(key); traversedPath = traversed.join("."); if (typeof ctx[key] === "undefined") { if (i === keys.length - 1) { ctx[key] = value; } else { ctx[key] = Object.create(null); } } else if (i !== keys.length - 1 && valueAssignments.indexOf(traversedPath) > -1) { // already a non-object value at key, can't be used as part of a new path genError("Cannot redefine existing key '" + traversedPath + "'.", line, column); } ctx = ctx[key]; if (ctx instanceof Array && ctx.length && i < keys.length - 1) { ctx = ctx[ctx.length - 1]; } } return ctx; } function reduceArrayWithTypeChecking(array) { // Ensure that all items in the array are of the same type var firstType = null; for (var i = 0; i < array.length; i++) { var node = array[i]; if (firstType === null) { firstType = node.type; } else { if (node.type !== firstType) { genError("Cannot add value of type " + node.type + " to array of type " + firstType + ".", node.line, node.column); } } } // Recursively reduce array of nodes into array of the nodes' values return array.map(reduceValueNode); } function quoteDottedString(str) { if (str.indexOf(".") > -1) { return "\"" + str + "\""; } else { return str; } } } module.exports = { compile: compile };