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