AK-21/Graphite-Industrial-Intelligence
0
1'use strict';2 3const stringify = require('./stringify');4 5/**6 * Constants7 */8 9const {10 MAX_LENGTH,11 CHAR_BACKSLASH, /* \ */12 CHAR_BACKTICK, /* ` */13 CHAR_COMMA, /* , */14 CHAR_DOT, /* . */15 CHAR_LEFT_PARENTHESES, /* ( */16 CHAR_RIGHT_PARENTHESES, /* ) */17 CHAR_LEFT_CURLY_BRACE, /* { */18 CHAR_RIGHT_CURLY_BRACE, /* } */19 CHAR_LEFT_SQUARE_BRACKET, /* [ */20 CHAR_RIGHT_SQUARE_BRACKET, /* ] */21 CHAR_DOUBLE_QUOTE, /* " */22 CHAR_SINGLE_QUOTE, /* ' */23 CHAR_NO_BREAK_SPACE,24 CHAR_ZERO_WIDTH_NOBREAK_SPACE25} = require('./constants');26 27/**28 * parse29 */30 31const parse = (input, options = {}) => {32 if (typeof input !== 'string') {33 throw new TypeError('Expected a string');34 }35 36 const opts = options || {};37 const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;38 if (input.length > max) {39 throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`);40 }41 42 const ast = { type: 'root', input, nodes: [] };43 const stack = [ast];44 let block = ast;45 let prev = ast;46 let brackets = 0;47 const length = input.length;48 let index = 0;49 let depth = 0;50 let value;51 52 /**53 * Helpers54 */55 56 const advance = () => input[index++];57 const push = node => {58 if (node.type === 'text' && prev.type === 'dot') {59 prev.type = 'text';60 }61 62 if (prev && prev.type === 'text' && node.type === 'text') {63 prev.value += node.value;64 return;65 }66 67 block.nodes.push(node);68 node.parent = block;69 node.prev = prev;70 prev = node;71 return node;72 };73 74 push({ type: 'bos' });75 76 while (index < length) {77 block = stack[stack.length - 1];78 value = advance();79 80 /**81 * Invalid chars82 */83 84 if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) {85 continue;86 }87 88 /**89 * Escaped chars90 */91 92 if (value === CHAR_BACKSLASH) {93 push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() });94 continue;95 }96 97 /**98 * Right square bracket (literal): ']'99 */100 101 if (value === CHAR_RIGHT_SQUARE_BRACKET) {102 push({ type: 'text', value: '\\' + value });103 continue;104 }105 106 /**107 * Left square bracket: '['108 */109 110 if (value === CHAR_LEFT_SQUARE_BRACKET) {111 brackets++;112 113 let next;114 115 while (index < length && (next = advance())) {116 value += next;117 118 if (next === CHAR_LEFT_SQUARE_BRACKET) {119 brackets++;120 continue;121 }122 123 if (next === CHAR_BACKSLASH) {124 value += advance();125 continue;126 }127 128 if (next === CHAR_RIGHT_SQUARE_BRACKET) {129 brackets--;130 131 if (brackets === 0) {132 break;133 }134 }135 }136 137 push({ type: 'text', value });138 continue;139 }140 141 /**142 * Parentheses143 */144 145 if (value === CHAR_LEFT_PARENTHESES) {146 block = push({ type: 'paren', nodes: [] });147 stack.push(block);148 push({ type: 'text', value });149 continue;150 }151 152 if (value === CHAR_RIGHT_PARENTHESES) {153 if (block.type !== 'paren') {154 push({ type: 'text', value });155 continue;156 }157 block = stack.pop();158 push({ type: 'text', value });159 block = stack[stack.length - 1];160 continue;161 }162 163 /**164 * Quotes: '|"|`165 */166 167 if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) {168 const open = value;169 let next;170 171 if (options.keepQuotes !== true) {172 value = '';173 }174 175 while (index < length && (next = advance())) {176 if (next === CHAR_BACKSLASH) {177 value += next + advance();178 continue;179 }180 181 if (next === open) {182 if (options.keepQuotes === true) value += next;183 break;184 }185 186 value += next;187 }188 189 push({ type: 'text', value });190 continue;191 }192 193 /**194 * Left curly brace: '{'195 */196 197 if (value === CHAR_LEFT_CURLY_BRACE) {198 depth++;199 200 const dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true;201 const brace = {202 type: 'brace',203 open: true,204 close: false,205 dollar,206 depth,207 commas: 0,208 ranges: 0,209 nodes: []210 };211 212 block = push(brace);213 stack.push(block);214 push({ type: 'open', value });215 continue;216 }217 218 /**219 * Right curly brace: '}'220 */221 222 if (value === CHAR_RIGHT_CURLY_BRACE) {223 if (block.type !== 'brace') {224 push({ type: 'text', value });225 continue;226 }227 228 const type = 'close';229 block = stack.pop();230 block.close = true;231 232 push({ type, value });233 depth--;234 235 block = stack[stack.length - 1];236 continue;237 }238 239 /**240 * Comma: ','241 */242 243 if (value === CHAR_COMMA && depth > 0) {244 if (block.ranges > 0) {245 block.ranges = 0;246 const open = block.nodes.shift();247 block.nodes = [open, { type: 'text', value: stringify(block) }];248 }249 250 push({ type: 'comma', value });251 block.commas++;252 continue;253 }254 255 /**256 * Dot: '.'257 */258 259 if (value === CHAR_DOT && depth > 0 && block.commas === 0) {260 const siblings = block.nodes;261 262 if (depth === 0 || siblings.length === 0) {263 push({ type: 'text', value });264 continue;265 }266 267 if (prev.type === 'dot') {268 block.range = [];269 prev.value += value;270 prev.type = 'range';271 272 if (block.nodes.length !== 3 && block.nodes.length !== 5) {273 block.invalid = true;274 block.ranges = 0;275 prev.type = 'text';276 continue;277 }278 279 block.ranges++;280 block.args = [];281 continue;282 }283 284 if (prev.type === 'range') {285 siblings.pop();286 287 const before = siblings[siblings.length - 1];288 before.value += prev.value + value;289 prev = before;290 block.ranges--;291 continue;292 }293 294 push({ type: 'dot', value });295 continue;296 }297 298 /**299 * Text300 */301 302 push({ type: 'text', value });303 }304 305 // Mark imbalanced braces and brackets as invalid306 do {307 block = stack.pop();308 309 if (block.type !== 'root') {310 block.nodes.forEach(node => {311 if (!node.nodes) {312 if (node.type === 'open') node.isOpen = true;313 if (node.type === 'close') node.isClose = true;314 if (!node.nodes) node.type = 'text';315 node.invalid = true;316 }317 });318 319 // get the location of the block on parent.nodes (block's siblings)320 const parent = stack[stack.length - 1];321 const index = parent.nodes.indexOf(block);322 // replace the (invalid) block with it's nodes323 parent.nodes.splice(index, 1, ...block.nodes);324 }325 } while (stack.length > 0);326 327 push({ type: 'eos' });328 return ast;329};330 331module.exports = parse;332 