Mirror: The magical sticky regex-based parser generator 馃
1export const _private = '__private'; 2 3const _state = 'state'; 4const _node = 'node'; 5const _match = 'x'; 6 7function js(/* arguments */) { 8 let body = arguments[0][0]; 9 for (let i = 1; i < arguments.length; i++) 10 body = body + arguments[i] + arguments[0][i]; 11 return body.trim(); 12} 13 14const copy = (prev) => { 15 const next = {}; 16 for (const key in prev) next[key] = prev[key]; 17 return next; 18}; 19 20const assignIndex = (depth) => js` 21 var y${depth} = ${_state}.y, 22 x${depth} = ${_state}.x; 23`; 24 25const restoreIndex = (depth) => js` 26 ${_state}.y = y${depth}; 27 ${_state}.x = x${depth}; 28`; 29 30const astExpression = (ast, depth, opts) => { 31 const restoreLength = 32 (opts.length && opts.abort && js`${_node}.length = ln${opts.length};`) || 33 ''; 34 const expression = ast.expression.fn 35 ? `${ast.expression.id}(${_state})` 36 : `${_private}.exec(${_state}, ${ast.expression.id})`; 37 38 return js` 39 if (${_match} = ${expression}) { 40 ${opts.capture ? js`${_node}.push(${_match})` : ''} 41 } else { 42 ${opts.onAbort} 43 ${restoreIndex(opts.index)} 44 ${restoreLength} 45 ${opts.abort} 46 } 47 `; 48}; 49 50const astGroup = (ast, depth, opts) => { 51 const capture = !!opts.capture && !ast.capture; 52 53 opts = copy(opts); 54 opts.capture = capture; 55 56 if (!opts.length && capture) { 57 opts.length = depth; 58 return js` 59 ${js`var ln${depth} = ${_node}.length;`} 60 ${astSequence(ast.sequence, depth + 1, opts)} 61 `; 62 } 63 64 return astSequence(ast.sequence, depth + 1, opts); 65}; 66 67const astChild = (ast, depth, opts) => 68 ast.expression ? astExpression(ast, depth, opts) : astGroup(ast, depth, opts); 69 70const astQuantifier = (ast, depth, opts) => { 71 const { index, abort, onAbort } = opts; 72 const invert = `inv_${depth}`; 73 const loop = `loop_${depth}`; 74 const count = `j${depth}`; 75 76 opts = copy(opts); 77 if (ast.capture === '!') { 78 opts.index = depth; 79 opts.abort = js`break ${invert}`; 80 } 81 82 let child; 83 if (ast.quantifier === '+') { 84 opts.onAbort = js` 85 if (${count}) { 86 ${restoreIndex(depth)} 87 break ${loop}; 88 } else { 89 ${onAbort} 90 } 91 `; 92 93 child = js` 94 ${loop}: for (var ${count} = 0; 1; ${count}++) { 95 ${assignIndex(depth)} 96 ${astChild(ast, depth, opts)} 97 } 98 `; 99 } else if (ast.quantifier === '*') { 100 opts.length = 0; 101 opts.index = depth; 102 opts.abort = js`break ${loop};`; 103 opts.onAbort = ''; 104 105 child = js` 106 ${loop}: for (;;) { 107 ${assignIndex(depth)} 108 ${astChild(ast, depth, opts)} 109 } 110 `; 111 } else if (ast.quantifier === '?') { 112 opts.index = depth; 113 opts.abort = ''; 114 opts.onAbort = ''; 115 116 child = js` 117 ${assignIndex(depth)} 118 ${astChild(ast, depth, opts)} 119 `; 120 } else { 121 child = astChild(ast, depth, opts); 122 } 123 124 if (ast.capture === '!') { 125 return js` 126 ${invert}: { 127 ${assignIndex(depth)} 128 ${child} 129 ${restoreIndex(index)} 130 ${abort} 131 } 132 `; 133 } else if (ast.capture === '=') { 134 return js` 135 ${assignIndex(depth)} 136 ${child} 137 ${restoreIndex(depth)} 138 `; 139 } else { 140 return child; 141 } 142}; 143 144const astSequence = (ast, depth, opts) => { 145 const alternation = ast.alternation ? `alt_${depth}` : ''; 146 147 let body = ''; 148 for (; ast; ast = ast.alternation) { 149 const block = `block_${depth}`; 150 151 let childOpts = opts; 152 if (ast.alternation) { 153 childOpts = copy(opts); 154 childOpts.index = depth; 155 childOpts.abort = js`break ${block};`; 156 childOpts.onAbort = ''; 157 } 158 159 let sequence = ''; 160 for (let i = 0; i < ast.length; i++) 161 sequence += astQuantifier(ast[i], depth, childOpts); 162 163 if (!ast.alternation) { 164 body += sequence; 165 } else { 166 body += js` 167 ${block}: { 168 ${assignIndex(depth)} 169 ${sequence} 170 break ${alternation}; 171 } 172 `; 173 } 174 } 175 176 if (!alternation) return body; 177 178 return js` 179 ${alternation}: { 180 ${body} 181 } 182 `; 183}; 184 185const astRoot = (ast, name, transform) => { 186 return js` 187 (function (${_state}) { 188 ${assignIndex(1)} 189 var ${_node} = []; 190 var ${_match}; 191 192 ${astSequence(ast, 2, { 193 index: 1, 194 length: 0, 195 onAbort: '', 196 abort: js`return;`, 197 capture: true, 198 })} 199 200 ${_node}.tag = ${name}; 201 return ${transform ? js`(${transform})(${_node})` : _node}; 202 }) 203 `; 204}; 205 206export { astRoot };