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 === '?') { 102 opts.index = depth; 103 opts.abort = js`break ${group}`; 104 105 child = js` 106 ${group}: { 107 ${assignIndex(depth)} 108 ${astChild(ast, depth, opts)} 109 } 110 `; 111 } else { 112 child = astChild(ast, depth, opts); 113 } 114 115 if (ast.capture === '!') { 116 return js` 117 ${invert}: { 118 ${assignIndex(depth)} 119 ${child} 120 ${restoreIndex(index)} 121 ${abort} 122 } 123 `; 124 } else if (ast.capture === '=') { 125 return js` 126 ${assignIndex(depth)} 127 ${child} 128 ${restoreIndex(depth)} 129 `; 130 } else { 131 return child; 132 } 133}; 134 135const astSequence = (ast, depth, opts) => { 136 const alternation = ast.alternation ? `alt_${depth}` : ''; 137 138 let body = ''; 139 for (; ast; ast = ast.alternation) { 140 const block = `block_${depth}`; 141 142 let childOpts = opts; 143 if (ast.alternation) { 144 childOpts = copy(opts); 145 childOpts.index = depth; 146 childOpts.abort = js`break ${block};`; 147 } 148 149 let sequence = ''; 150 for (let i = 0; i < ast.length; i++) 151 sequence += astQuantifier(ast[i], depth, childOpts); 152 153 if (!ast.alternation) { 154 body += sequence; 155 } else { 156 body += js` 157 ${block}: { 158 ${assignIndex(depth)} 159 ${sequence} 160 break ${alternation}; 161 } 162 `; 163 } 164 } 165 166 if (!alternation) return body; 167 168 return js` 169 ${alternation}: { 170 ${body} 171 } 172 `; 173}; 174 175const astRoot = (ast, name, transform) => { 176 return js` 177 (function (${_state}) { 178 ${assignIndex(1)} 179 var ${_node} = []; 180 var ${_match}; 181 182 ${astSequence(ast, 2, { 183 index: 1, 184 length: 0, 185 abort: js`return;`, 186 capture: true, 187 })} 188 189 ${_node}.tag = ${name}; 190 return ${transform ? js`(${transform})(${_node})` : _node}; 191 }) 192 `; 193}; 194 195export { astRoot };