/* Copyright Rasmus Frederiksen 2017. Some rights reserved. Free for non-commercial use with attribution. */ class SyntaxError extends Error { constructor(message, position) { super(message); this.name = "SyntaxError"; this.position = position; } } var T = { NUM: 1, LPAREN: 2, RPAREN: 5, PIPE: 6, SLASH: 7, END: 8 } var REGEXP = { NUM: /^(\d+)(\[([^\]]*?)\])?(\[([^\]]*?)\])?/ } var tokenize = function(str) { var tokens = [], oStr = str, i, strL, delta, isNum, num; // Get rid of spaces str = str.replace(/[ ]*/g, ''); // Store initial length strL = str.length; // Convert str to tokens while (str.length) { isNum = REGEXP.NUM.test(str); if (isNum) { num = REGEXP.NUM.exec(str); tokens.push({type: T.NUM, index: num[1], width: num[3], height: num[5]}); str = str.substr(num[0].length); } else if (str.charAt(0) === '(') { tokens.push({type: T.LPAREN}); str = str.substr(1); } else if (str.charAt(0) === ')') { tokens.push({type: T.RPAREN}); str = str.substr(1); } else if (str.charAt(0) === '|') { tokens.push({type: T.PIPE}); str = str.substr(1); } else if (str.charAt(0) === '/') { tokens.push({type: T.SLASH}); str = str.substr(1); } else { delta = strL - str.length + 1; i = 0; while (delta) { if (oStr[i] !== ' ') delta--; i++; } throw new SyntaxError(`Illegal char: '${str.charAt(0)}' at position: ${i}`, i); } } tokens.push({type: T.END}); return tokens; } var div = function (opts, contents) { var i, keys, node; keys = Object.keys(opts); node = document.createElement('div'); for (i = 0; i < keys.length; i++) { if (opts.hasOwnProperty(keys[i])) { if (opts[keys[i]] !== undefined) { node.setAttribute(keys[i], opts[keys[i]]); } } } if (contents !== undefined) { if (!Array.isArray(contents)) contents = [contents]; for (i = 0; i < contents.length; i++) { node.appendChild(contents[i]); } } return node; } var parse = function (str) { var tokens = tokenize(str); var consume = function (type) { return (peek() === type) ? tokens.shift() : null; } var peek = function () { return tokens[0].type; } var rows = function () { var val = []; val.push(cols()); while (peek() === T.SLASH) { consume(T.SLASH); val.push(cols()); } return div({class: 'rows'}, val); } var cols = function () { var val = []; val.push(cell()); while (peek() === T.PIPE) { consume(T.PIPE); val.push(cell()); } return div({class: 'cols'}, val); } var cell = function () { var val, num; if (peek() === T.NUM) { num = consume(T.NUM); return div({class: 'cell', 'data-index': num.index, 'data-width': num.width, 'data-height': num.height}); } else if (peek() === T.LPAREN) { consume(T.LPAREN); val = rows(); consume(T.RPAREN); return val; } } return rows(); }