1 | 'use strict';
|
2 |
|
3 | module.exports = (input, options = {}, fn) => {
|
4 | if (typeof input !== 'string') throw new TypeError('expected a string');
|
5 |
|
6 | if (typeof options === 'function') {
|
7 | fn = options;
|
8 | options = {};
|
9 | }
|
10 |
|
11 | let separator = options.separator || '.';
|
12 | let ast = { type: 'root', nodes: [], stash: [''] };
|
13 | let stack = [ast];
|
14 | let state = { input, separator, stack };
|
15 | let string = input;
|
16 | let value, node;
|
17 | let i = -1;
|
18 |
|
19 | state.bos = () => i === 0;
|
20 | state.eos = () => i === string.length;
|
21 | state.prev = () => string[i - 1];
|
22 | state.next = () => string[i + 1];
|
23 |
|
24 | let quotes = options.quotes || [];
|
25 | let openers = options.brackets || {};
|
26 |
|
27 | if (options.brackets === true) {
|
28 | openers = { '[': ']', '(': ')', '{': '}', '<': '>' };
|
29 | }
|
30 | if (options.quotes === true) {
|
31 | quotes = ['"', '\'', '`'];
|
32 | }
|
33 |
|
34 | let closers = invert(openers);
|
35 | let keep = options.keep || (value => value !== '\\');
|
36 |
|
37 | const block = () => (state.block = stack[stack.length - 1]);
|
38 | const peek = () => string[i + 1];
|
39 | const next = () => string[++i];
|
40 | const append = value => {
|
41 | state.value = value;
|
42 | if (value && keep(value, state) !== false) {
|
43 | state.block.stash[state.block.stash.length - 1] += value;
|
44 | }
|
45 | };
|
46 |
|
47 | const closeIndex = (value, startIdx) => {
|
48 | let idx = string.indexOf(value, startIdx);
|
49 | if (idx > -1 && string[idx - 1] === '\\') {
|
50 | idx = closeIndex(value, idx + 1);
|
51 | }
|
52 | return idx;
|
53 | };
|
54 |
|
55 | for (; i < string.length - 1;) {
|
56 | state.value = value = next();
|
57 | state.index = i;
|
58 | block();
|
59 |
|
60 |
|
61 | if (value === '\\') {
|
62 | if (peek() === '\\') {
|
63 | append(value + next());
|
64 | } else {
|
65 |
|
66 |
|
67 | append(value);
|
68 | append(next());
|
69 | }
|
70 | continue;
|
71 | }
|
72 |
|
73 |
|
74 | if (quotes.includes(value)) {
|
75 | let pos = i + 1;
|
76 | let idx = closeIndex(value, pos);
|
77 |
|
78 | if (idx > -1) {
|
79 | append(value);
|
80 | append(string.slice(pos, idx));
|
81 | append(string[idx]);
|
82 | i = idx;
|
83 | continue;
|
84 | }
|
85 |
|
86 | append(value);
|
87 | continue;
|
88 | }
|
89 |
|
90 |
|
91 | if (options.brackets !== false && openers[value]) {
|
92 | node = { type: 'bracket', nodes: [] };
|
93 | node.stash = keep(value) !== false ? [value] : [''];
|
94 | node.parent = state.block;
|
95 | state.block.nodes.push(node);
|
96 | stack.push(node);
|
97 | continue;
|
98 | }
|
99 |
|
100 |
|
101 | if (options.brackets !== false && closers[value]) {
|
102 | if (stack.length === 1) {
|
103 | append(value);
|
104 | continue;
|
105 | }
|
106 |
|
107 | append(value);
|
108 | node = stack.pop();
|
109 | block();
|
110 | append(node.stash.join(''));
|
111 | continue;
|
112 | }
|
113 |
|
114 |
|
115 | if (value === separator && state.block.type === 'root') {
|
116 | if (typeof fn === 'function' && fn(state) === false) {
|
117 | append(value);
|
118 | continue;
|
119 | }
|
120 | state.block.stash.push('');
|
121 | continue;
|
122 | }
|
123 |
|
124 |
|
125 | append(value);
|
126 | }
|
127 |
|
128 | node = stack.pop();
|
129 |
|
130 | while (node !== ast) {
|
131 | if (options.strict === true) {
|
132 | let column = i - node.stash.length + 1;
|
133 | throw new SyntaxError(`Unmatched: "${node.stash[0]}", at column ${column}`);
|
134 | }
|
135 |
|
136 | value = (node.parent.stash.pop() + node.stash.join('.'));
|
137 | node.parent.stash = node.parent.stash.concat(value.split('.'));
|
138 | node = stack.pop();
|
139 | }
|
140 |
|
141 | return node.stash;
|
142 | };
|
143 |
|
144 | function invert(obj) {
|
145 | let inverted = {};
|
146 | for (const key of Object.keys(obj)) inverted[obj[key]] = key;
|
147 | return inverted;
|
148 | }
|