Home Manual Reference Source

src/tokens.js

import assert from 'node:assert';
import Position from './Position.js';

const FIRST_LINE = 1;
const FIRST_POSITION = 1;

const CR = '\r';
const LF = '\n';

async function* _tokens(tape) {
	let line = FIRST_LINE;
	let position = FIRST_POSITION;

	let buffer = '';

	const flush = function* () {
		if (buffer !== '') {
			yield ['text', buffer, new Position(line, position)];
			position += buffer.length;
			buffer = '';
		}
	};

	while (true) {
		const c = await tape.read();

		if (c === tape.eof) break;

		if (c === '#' && position === FIRST_POSITION) {
			assert(buffer === '');
			let l = c;
			let token = '';
			while (l !== tape.eof && l !== CR && l !== LF) {
				token += l;
				++position;
				l = await tape.read();
			}

			tape.unread(l);
			const closing = token.length >= 3 && token[2] === '/';
			const kind = token.slice(0, 2) + (closing ? '/' : '');
			yield [kind, token, new Position(line, FIRST_POSITION)];
			continue;
		}

		switch (c) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9': {
				yield* flush();
				yield ['digit', c, new Position(line, position)];
				++position;
				break;
			}

			case CR:
			case '/':
			case '!': {
				yield* flush();
				yield [c, c, new Position(line, position)];
				++position;
				break;
			}

			case LF: {
				yield* flush();
				yield [c, c, new Position(line, position)];
				++line;
				position = FIRST_POSITION;
				break;
			}

			default: {
				buffer += c;
				break;
			}
		}
	}

	yield* flush();
}

export default async function* tokens(tape) {
	for await (const [terminal, buffer, position] of _tokens(tape)) {
		yield {
			type: 'leaf',
			terminal,
			buffer,
			position,
		};
	}
}