UNPKG

xast

Version:
234 lines (204 loc) 6.32 kB
import { Location } from './Location'; import { Token } from './Token'; import { Lexer } from './Lexer'; import { TokenKind, getTokenKindDescription } from './TokenKind'; import { SchemaNode, schemaParser } from './parsers/schema'; export interface Node { readonly kind: string; readonly loc?: Location; } export interface ParseOptions { enableLocation?: boolean | undefined; maxTokens?: number | undefined; } export interface Trigger { readonly kind: TokenKind; readonly keyword?: string; } export interface NodeParser<N extends Node> { readonly kind: N['kind']; readonly trigger?: Trigger; parse(parser: Parser): N | undefined; [key: string]: unknown; } type NodeParserResult<P extends NodeParser<Node>> = ReturnType<P["parse"]>; type MustNodeParserResult<P extends NodeParser<Node>> = NonNullable<NodeParserResult<P>>; const ANY_KEYWORD = Symbol('@@any'); export class Parser { lexer: Lexer; protected _options: ParseOptions; protected _tokenCounter: number; protected _parsersMap: Map<TokenKind, Map<string | symbol, NodeParser<any>[]>>; constructor(lexer: Lexer, options: ParseOptions = {}) { this.lexer = lexer; this._options = options; this._tokenCounter = 0; this._parsersMap = new Map; } parseSchema(): SchemaNode { return this.expectParse(schemaParser); } add<N extends Node>(nodeParser: NodeParser<N>): void { if (nodeParser.trigger) { if (!this._parsersMap.has(nodeParser.trigger.kind)) { this._parsersMap.set(nodeParser.trigger.kind, new Map); } const parsersKindMap = this._parsersMap.get(nodeParser.trigger.kind); const keyword = nodeParser.trigger.keyword ?? ANY_KEYWORD; if (!parsersKindMap?.has(keyword)) { parsersKindMap?.set(keyword, []); } parsersKindMap?.get(keyword)?.push(nodeParser); } } parseToken(token: Token): Node | undefined { if (this._parsersMap.has(token.kind)) { const parsersKindMap = this._parsersMap.get(token.kind); for (const keyword of [token.value, ANY_KEYWORD]) { const nodeParsers = parsersKindMap?.get(keyword) || []; for (const nodeParser of nodeParsers) { const node = nodeParser.parse(this); if (node) { return node; } } } } } optionalParse<P extends NodeParser<any>>(nodeParser: P): NodeParserResult<P> { return nodeParser.parse(this); } expectParse<P extends NodeParser<any>>(nodeParser: P): MustNodeParserResult<P> { const node = nodeParser.parse(this); if (node) { return node; } throw this.lexer.source.syntaxError( this.lexer.token.start, `Expected ${nodeParser.kind}, found ${this.lexer.token.getDescription()}.`, ); } node<T extends { loc?: Location | undefined }>( startToken: Token, node: T, ): T { if (this._options.enableLocation) { node.loc = new Location( startToken, this.lexer.lastToken, this.lexer.source, ); } return node; } peek(kind: TokenKind): boolean { return this.lexer.token.kind === kind; } expectToken(kind: TokenKind): Token { const token = this.lexer.token; if (token.kind === kind) { this.advanceLexer(); return token; } throw this.lexer.source.syntaxError( token.start, `Expected ${getTokenKindDescription(kind)}, found ${token.getDescription()}.`, ); } expectOptionalToken(kind: TokenKind): boolean { const token = this.lexer.token; if (token.kind === kind) { this.advanceLexer(); return true; } return false; } expectKeyword(value: string): void { const token = this.lexer.token; if (token.kind === TokenKind.NAME && token.value === value) { this.advanceLexer(); } else { throw this.lexer.source.syntaxError( token.start, `Expected "${value}", found ${token.getDescription()}.`, ); } } expectOptionalKeyword(value: string): boolean { const token = this.lexer.token; if (token.kind === TokenKind.NAME && token.value === value) { this.advanceLexer(); return true; } return false; } unexpected(atToken?: Token | null | undefined): Error { const token = atToken ?? this.lexer.token; return this.lexer.source.syntaxError( token.start, `Unexpected ${token.getDescription()}.`, ); } any<P extends NodeParser<any>>( openKind: TokenKind, nodeParser: P, closeKind: TokenKind, ): MustNodeParserResult<P>[] { this.expectToken(openKind); const nodes: MustNodeParserResult<P>[] = []; while (!this.expectOptionalToken(closeKind)) { nodes.push(this.expectParse(nodeParser)); } return nodes; } many<P extends NodeParser<N>, N extends Node>( openKind: TokenKind, nodeParser: P, closeKind: TokenKind, ): MustNodeParserResult<P>[] { this.expectToken(openKind); const nodes: MustNodeParserResult<P>[] = []; do { nodes.push(this.expectParse(nodeParser)); } while (!this.expectOptionalToken(closeKind)); return nodes; } optionalMany<P extends NodeParser<N>, N extends Node>( openKind: TokenKind, nodeParser: P, closeKind: TokenKind, ): MustNodeParserResult<P>[] { if (this.expectOptionalToken(openKind)) { const nodes: MustNodeParserResult<P>[] = []; do { nodes.push(this.expectParse(nodeParser)); } while (!this.expectOptionalToken(closeKind)); return nodes; } return []; } delimitedMany<P extends NodeParser<N>, N extends Node>( delimiterKind: TokenKind, nodeParser: P, ): MustNodeParserResult<P>[] { this.expectOptionalToken(delimiterKind); const nodes: MustNodeParserResult<P>[] = []; do { nodes.push(this.expectParse(nodeParser)); } while (this.expectOptionalToken(delimiterKind)); return nodes; } advanceLexer(): void { const { maxTokens } = this._options; const token = this.lexer.advance(); if (maxTokens !== undefined && token.kind !== TokenKind.EOF) { ++this._tokenCounter; if (this._tokenCounter > maxTokens) { throw this.lexer.source.syntaxError( token.start, `Schema contains more than ${maxTokens} tokens.`, ); } } } }