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