UNPKG

@stackpress/idea-parser

Version:

Parses ideas to AST and readable JSON.

106 lines (105 loc) 3.19 kB
import Exception from './Exception.js'; export default class Lexer { _code = ''; _index = 0; _dictionary = {}; get dictionary() { return { ...this._dictionary }; } get index() { return this._index; } clone() { const lexer = new Lexer(); lexer.load(this._code, this._index); for (const key in this._dictionary) { lexer.define(key, this._dictionary[key].reader); } return lexer; } define(key, reader) { this._dictionary[key] = { key, reader }; } expect(keys) { if (!Array.isArray(keys)) { keys = [keys]; } const definitions = keys.map(key => { const reader = this.get(key); if (!reader) { throw Exception.for('Unknown definition %s', key); } return reader; }).filter(Boolean); if (!definitions.length) { throw Exception.for('Unknown definitions %s', keys.join(', ')); } const match = this.match(this._code, this._index, keys); if (!match) { if (this._code[this._index + 10]) { throw Exception.for('Unexpected %s ... expecting %s', this._code .substring(this._index, this._index + 10) .replace(/[\n\r]/g, ' ') .trim(), keys.join(' or ')).withPosition(this._index, this.nextSpace()); } else { throw Exception.for('Unexpected %s expecting %s', this._code.substring(this._index, this._index + 10), keys.join(' or ')).withPosition(this._index, this.nextSpace()); } } this._index = match.end; return match; } get(key) { return this._dictionary[key]; } load(code, index = 0) { this._code = code; this._index = index; return this; } match(code, start, keys) { keys = keys || Object.keys(this._dictionary); for (let i = 0; i < keys.length; i++) { if (!this._dictionary[keys[i]]) { throw Exception.for('Unknown definition %s', keys[i]); } const results = this._dictionary[keys[i]].reader(code, start, this); if (results && results.end > start) { return results; } } return null; } next(names) { const start = this._index; try { this.expect(names); this._index = start; return true; } catch (error) { this._index = start; return false; } } optional(names) { const start = this._index; try { return this.expect(names); } catch (error) { this._index = start; return undefined; } } read() { return this.optional(Object.keys(this.dictionary)); } substring(start, end) { return this._code.substring(start, end); } nextSpace() { const index = this._code.indexOf(' ', this._index); return index === -1 ? this._code.length : index; } }