import { EarlyEndOfParseError, NoParsletFoundError } from './errors.js' import type { Lexer } from './lexer/Lexer.js' import type { Grammar } from './grammars/Grammar.js' import { assertRootResult } from './assertTypes.js' import { Precedence } from './Precedence.js' import type { RootResult } from './result/RootResult.js' import type { IntermediateResult } from './result/IntermediateResult.js' import type { TokenType } from './lexer/Token.js' export class Parser { public readonly grammar: Grammar private _lexer: Lexer public readonly baseParser?: Parser public readonly externalParsers?: Record unknown)|undefined> public readonly module?: boolean public readonly strictMode?: boolean public readonly asyncFunctionBody?: boolean public readonly classContext?: boolean private rangeStart: number private readonly range: boolean private readonly locStart: { column: number, line: number } private readonly loc: boolean constructor (grammar: Grammar, lexer: Lexer, baseParser?: Parser, { module, strictMode, asyncFunctionBody, classContext, range = false, rangeStart = 0, loc = false, locStart = { line: 1, column: 0 }, externalParsers }: { module?: boolean, strictMode?: boolean, asyncFunctionBody?: boolean, classContext?: boolean, range?: boolean, rangeStart?: number, loc?: boolean, locStart?: { column: number, line: number }, externalParsers?: Record unknown)|undefined> } = {}) { this.grammar = grammar this._lexer = lexer this.baseParser = baseParser this.externalParsers = externalParsers this.module = module this.strictMode = strictMode this.asyncFunctionBody = asyncFunctionBody this.classContext = classContext this.rangeStart = rangeStart this.range = range this.locStart = locStart this.loc = loc } get lexer (): Lexer { return this._lexer } /** * Parses a given string and throws an error if the parse ended before the end of the string. */ parse (): RootResult { const result = this.parseType(Precedence.ALL) if (this.lexer.current.type !== 'EOF') { throw new EarlyEndOfParseError(this.lexer.current) } return result } /** * Parses with the current lexer and asserts that the result is a {@link RootResult}. */ public parseType (precedence: Precedence): RootResult { return assertRootResult(this.parseIntermediateType(precedence)) } /** * The main parsing function. First it tries to parse the current state in the prefix step, and then it continues * to parse the state in the infix step. */ public parseIntermediateType (precedence: Precedence): IntermediateResult { const result = this.tryParslets(null, precedence) if (result === null) { throw new NoParsletFoundError(this.lexer.current) } return this.parseInfixIntermediateType(result, precedence) } /** * In the infix parsing step the parser continues to parse the current state with all parslets until none returns * a result. */ public parseInfixIntermediateType (left: IntermediateResult, precedence: Precedence): IntermediateResult { let result = this.tryParslets(left, precedence) while (result !== null) { left = result result = this.tryParslets(left, precedence) } return left } /** * Tries to parse the current state with all parslets in the grammar and returns the first non null result. */ private tryParslets (left: IntermediateResult | null, precedence: Precedence): IntermediateResult | null { for (const parslet of this.grammar) { const rangeStart = this.rangeStart const locStartLine = this.locStart.line const locStartColumn = this.locStart.column const result = parslet(this, precedence, left) if (result !== null) { if (this.range) { result.range = [ rangeStart, this.rangeStart ] } if (this.loc) { result.loc = { end: { line: this.locStart.line, column: this.locStart.column }, start: { line: locStartLine, column: locStartColumn } } } return result } } return null } /** * If the given type equals the current type of the {@link Lexer} advance the lexer. Return true if the lexer was * advanced. */ public consume (types: TokenType | TokenType[]): boolean { if (!Array.isArray(types)) { types = [types] } if (types.includes(this.lexer.current.type)) { if (this.range) { /* c8 ignore next -- Default is for TS */ this.rangeStart += this.lexer.current?.reduced ?? 0 } if (this.loc) { /* c8 ignore next 4 -- Defaults are for TS */ this.locStart.line += this.lexer.current?.line ?? 0 this.locStart.column = (this.lexer.current?.line ?? 0) > 0 ? this.lexer.current?.column ?? 0 : this.locStart.column + (this.lexer.current?.column ?? 0) } this._lexer = this.lexer.advance() return true } else { return false } } public acceptLexerState (parser: Parser): void { this._lexer = parser.lexer } }