source.js
1 import slice from 'iter-tools-es/methods/slice'; 2 import { streamFromTree } from '@bablr/agast-helpers/tree'; 3 import { StreamIterable, getStreamIterator } from '@bablr/agast-helpers/stream'; 4 import { ShiftTag, GapTag, LiteralTag } from './symbols.js'; 5 6 const escapables = { 7 n: '\n', 8 r: '\r', 9 t: '\t', 10 0: '\0', 11 }; 12 13 function* __readFromStream(stream) { 14 let iter = stream[Symbol.asyncIterator](); 15 let step; 16 17 for (;;) { 18 step = yield iter.next(); 19 20 if (step.done) break; 21 22 yield* step.value; 23 } 24 25 if (!step.done) { 26 iter?.return(); 27 } 28 } 29 30 export const readFromStream = (stream) => new StreamIterable(__readFromStream(stream)); 31 32 const gapStr = '<//>'; 33 34 function* __embeddedSourceFrom(iterable) { 35 let iter = getStreamIterator(iterable); 36 let step; 37 let escape = false; 38 let gapMatchIdx = 0; 39 let quote = null; 40 41 for (;;) { 42 step = iter.next(); 43 44 if (step instanceof Promise) { 45 step = yield step; 46 } 47 48 if (step.done) break; 49 50 const chr = step.value; 51 52 if (escape) { 53 if (chr === "'" || chr === '"' || chr === '\\') { 54 yield chr; 55 } else if (escapables[chr]) { 56 yield escapables[chr]; 57 } else { 58 throw new Error(); 59 } 60 escape = false; 61 } else { 62 if (!quote && chr === gapStr[gapMatchIdx]) { 63 gapMatchIdx++; 64 if (gapMatchIdx === gapStr.length) { 65 yield null; 66 gapMatchIdx = 0; 67 } 68 } else { 69 if (gapMatchIdx > 0) { 70 throw new Error(); 71 } 72 73 if (!quote && (chr === '"' || chr === "'")) { 74 quote = chr; 75 } else if (quote && chr === quote) { 76 quote = null; 77 } else if (quote && chr === '\\') { 78 escape = true; 79 } else if (quote) { 80 yield chr; 81 } else if (!/[\s]/.test(chr)) { 82 throw new Error('unkown syntax'); 83 } 84 } 85 } 86 } 87 88 if (!step.done) { 89 iter?.return(); 90 } 91 } 92 93 export const embeddedSourceFrom = (iterable) => new StreamIterable(__embeddedSourceFrom(iterable)); 94 95 function* __printEmbeddedSource(chrs) { 96 let iter = getStreamIterator(chrs); 97 let part = ''; 98 99 let step; 100 for (;;) { 101 step = iter.next(); 102 103 if (step instanceof Promise) { 104 step = yield step; 105 } 106 107 if (step.done) break; 108 109 const chr = step.value; 110 111 if (chr === null) { 112 if (part) { 113 yield `'`; 114 yield* part; 115 yield `'<//>`; 116 117 part = ''; 118 } 119 } else if (chr === '\\' || chr === "'") { 120 part += `\\${chr}`; 121 } else { 122 part += chr; 123 } 124 } 125 126 if (part) { 127 yield `'`; 128 yield* part; 129 yield `'`; 130 } 131 } 132 133 export const printEmbeddedSource = (chrs) => { 134 return __printEmbeddedSource(chrs); 135 }; 136 137 function* __sourceFromTokenStream(tags) { 138 let iter = getStreamIterator(tags); 139 let step; 140 141 for (;;) { 142 step = iter.next(); 143 144 if (step instanceof Promise) { 145 yield step; 146 } 147 148 if (step.done) break; 149 150 const tag = step.value; 151 152 if (tag.type === LiteralTag) { 153 yield* tag.value; 154 } else if (tag.type === GapTag) { 155 yield null; 156 } 157 } 158 } 159 160 export const sourceFromTokenStream = (tags) => new StreamIterable(__sourceFromTokenStream(tags)); 161 162 function* __sourceFromQuasis(quasis) { 163 let first = true; 164 let iter = getStreamIterator(quasis) || quasis[Symbol.iterator](); 165 let step; 166 167 for (;;) { 168 step = iter.next(); 169 170 if (step instanceof Promise) { 171 step = yield step; 172 } 173 174 if (step.done) break; 175 176 const quasi = step.value; 177 178 if (!first) yield null; 179 yield* quasi; 180 first = false; 181 } 182 } 183 184 export const sourceFromQuasis = (quasis) => new StreamIterable(__sourceFromQuasis(quasis)); 185 186 export function* fillGapsWith(expressions, iterable) { 187 let exprIdx = 0; 188 let iter = getStreamIterator(iterable); 189 let holding = false; 190 191 for (;;) { 192 let step = iter.next(); 193 194 if (step instanceof Promise) { 195 step = yield step; 196 } 197 198 if (step.done) break; 199 200 const token = step.value; 201 202 if (token.type === ShiftTag) { 203 holding = true; 204 } 205 206 if (token.type === GapTag) { 207 if (holding) { 208 holding = false; 209 yield token; 210 } else { 211 if (exprIdx >= expressions.length) throw new Error('not enough gaps for expressions'); 212 yield* slice(2, -1, streamFromTree(expressions[exprIdx])); 213 exprIdx++; 214 } 215 } else { 216 yield token; 217 } 218 } 219 220 if (exprIdx !== expressions.length) { 221 throw new Error('too many expressions for gaps'); 222 } 223 } 224 225 const none = Symbol('none'); 226 227 function* __stripTrailingNewline(iterable) { 228 const iter = getStreamIterator(iterable); 229 let step = iter.next(); 230 let lastValue = none; 231 232 for (;;) { 233 if (step instanceof Promise) { 234 step = yield step; 235 } 236 237 // TODO: handle \r\n line endings 238 if (step.done && lastValue === '\n') { 239 return; 240 } 241 242 if (lastValue !== none) { 243 yield lastValue; 244 } 245 246 if (step.done) break; 247 248 lastValue = step.value; 249 250 step = iter.next(); 251 } 252 253 if (lastValue !== none) { 254 yield lastValue; 255 } 256 } 257 258 export const stripTrailingNewline = (iterable) => 259 new StreamIterable(__stripTrailingNewline(iterable));