ebnf-railroad-visualizer
Version:
A web-based EBNF railroad diagram visualizer
136 lines • 4.78 kB
JavaScript
/*
* 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