UNPKG

ebnf-railroad-visualizer

Version:
136 lines 4.78 kB
/* * This work © 2024 by Alexander Voglsperger is licensed under CC BY 4.0. * To view a copy of this license, see the provided LICENSE file or visit https://creativecommons.org/licenses/by/4.0/ */ import { Token, Kind } from "./Token.js"; import { Syntax } from "../wsn/Syntax.js"; import { Production } from "../wsn/Production.js"; import { Term } from "../wsn/Term.js"; import { Identifier } from "../wsn/Identifier.js"; import { Expression } from "../wsn/Expression.js"; import { Factor, FactorType } from "../wsn/Factor.js"; import { Literal } from "../wsn/Literal.js"; export class Parser { constructor(scanner) { this.scanner = scanner; this.t = new Token(Kind.unknown); this.la = new Token(Kind.unknown); this.sym = Kind.unknown; this.idCounter = 0; } parse() { this.scan(); return this.Syntax(); } Syntax() { const productions = []; while (this.sym !== Kind.eof) { productions.push(this.Production()); } this.scan(); return new Syntax(productions, this.idCounter++); } Production() { const ident = this.Identifier(); this.check(Kind.assign); const expr = this.Expression(); this.check(Kind.period); return new Production(ident, expr, this.idCounter++); } Identifier() { if (this.sym !== Kind.ident) { this.throwError(`expected identifier but found '${this.t}'`); } this.scan(); const letters = []; for (const c of this.t.str) { letters.push(c); } return new Identifier(letters, this.idCounter++); } Expression() { const terms = []; terms.push(this.Term()); while (this.sym === Kind.pipe) { this.scan(); terms.push(this.Term()); } return new Expression(terms, this.idCounter++); } Term() { const factors = [this.Factor()]; while (this.sym === Kind.ident || this.sym === Kind.literal || this.sym === Kind.quote || this.sym === Kind.lpar || this.sym === Kind.lbrace || this.sym === Kind.lbrack) { factors.push(this.Factor()); } return new Term(factors, this.idCounter++); } Factor() { let factor; let expr; switch (this.sym) { case Kind.ident: // IDENTIFIER factor = new Factor(FactorType.Identifier, this.Identifier(), this.idCounter++); break; case Kind.quote: // "\"" LITERAL "\"" factor = new Factor(FactorType.Identifier, this.Literal(), this.idCounter++); break; case Kind.lpar: // "(" EXPRESSION ")" this.scan(); expr = this.Expression(); this.check(Kind.rpar); factor = new Factor(FactorType.Group, expr, this.idCounter++); break; case Kind.lbrace: // "{" EXPRESSION "}" this.scan(); expr = this.Expression(); this.check(Kind.rbrace); factor = new Factor(FactorType.Repetition, expr, this.idCounter++); break; case Kind.lbrack: // "[" EXPRESSION "]" this.scan(); expr = this.Expression(); this.check(Kind.rbrack); factor = new Factor(FactorType.Optionally, expr, this.idCounter++); break; default: this.throwError(`expected identifier, '"', '(', '{' or '[' but found '${this.t}'`); } return factor; } Literal() { let characters = ""; this.check(Kind.quote); if (this.sym !== Kind.literal) { this.throwError(`expected literal but found '${this.la}'`); } characters = this.la.str; this.scan(); this.check(Kind.quote); return new Literal(characters, this.idCounter++); } /** * Checks if the lookahead token has the expected {@link Kind} and scans the next.4 * @param expected expeced kind of lookahead token or unexpected end */ check(expected) { if (this.sym !== expected) { this.throwError(`expected '${expected}' but found '${this.t}'`); } this.scan(); } /** * Scans the next token from the input and stores it into {@link la}. */ scan() { this.t = this.la; this.la = this.scanner.next(); this.sym = this.la.kind; } throwError(message) { const [line, col] = this.scanner.getPosition(); throw new Error(`(line ${line}, column ${col}) - ${message}`); } } //# sourceMappingURL=Parser.js.map