/ lib / source.js
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));