UNPKG

@scinorandex/sparse

Version:

Yet another parser generator

98 lines (80 loc) 3.53 kB
import { Slex, Token } from "@scinorandex/slex"; import { Result } from "./utils/Result"; export type Production = { lhs: GrammarToken; rhs: { type: "terminal" | "variable"; token: GrammarToken }[] }; export type GrammarTokenMetadata = {}; export enum GrammarTokenType { PRODUCTION_NAME, TOKEN_NAME, COLON, SEMICOLON, EOF, } export type GrammarToken = Token<GrammarTokenType, GrammarTokenMetadata>; export const grammarLexerGenerator = new Slex<GrammarTokenType, GrammarTokenMetadata>({ EOF_TYPE: GrammarTokenType.EOF, isHigherPrecedence: () => false, }); grammarLexerGenerator.addRule( "lowercase", "a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z" ); grammarLexerGenerator.addRule( "uppercase", "A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z" ); grammarLexerGenerator.addRule("letter", "${lowercase} | ${uppercase}"); grammarLexerGenerator.addRule("digit", "0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9"); grammarLexerGenerator.addRule("alphanumeric", "${letter} | ${digit}"); grammarLexerGenerator.addRule("identifier", "(${letter} | ${digit} | $_)*"); grammarLexerGenerator.addRule("colon", "$:", GrammarTokenType.COLON); grammarLexerGenerator.addRule("semicolon", "$;", GrammarTokenType.SEMICOLON); grammarLexerGenerator.addRule("production_name", "$< ${identifier} $>", GrammarTokenType.PRODUCTION_NAME); grammarLexerGenerator.addRule("token_name", "$[ ${identifier} $]", GrammarTokenType.TOKEN_NAME); interface LexerInterface { peekNextToken(): GrammarToken; getNextToken(): GrammarToken; hasNextToken(): boolean; } export const tryBuildProductions = (lexer: LexerInterface | string): Result<Production[]> => { if (typeof lexer === "string") lexer = grammarLexerGenerator.generate(lexer, () => ({})); const productions: Production[] = []; const expect = (type: GrammarTokenType) => { const token = lexer.peekNextToken(); if (token.type !== type) throw new Error(`Expected ${type}, got ${token.type}`); lexer.getNextToken(); }; const buildProduction = (): Result<Production> => { const productionLHS = lexer.getNextToken(); expect(GrammarTokenType.COLON); const production: Production = { lhs: productionLHS, rhs: [] }; while (lexer.peekNextToken().type != GrammarTokenType.SEMICOLON) { const productionRHS = lexer.getNextToken(); if (productionRHS.type == GrammarTokenType.PRODUCTION_NAME) production.rhs.push({ type: "variable", token: productionRHS }); else if (productionRHS.type == GrammarTokenType.TOKEN_NAME) production.rhs.push({ type: "terminal", token: productionRHS }); else return { success: false, reason: `Unexpected token type: ${GrammarTokenType[productionRHS.type]}`, token: productionRHS, }; } expect(GrammarTokenType.SEMICOLON); return { success: true, value: production }; }; while (lexer.hasNextToken()) { const result = buildProduction(); if (result.success === false) return result; productions.push(result.value); } return { success: true, value: productions }; }; export const buildProductions = (lexer: LexerInterface | string): Production[] => { const result = tryBuildProductions(lexer); if (result.success === false) throw new Error( `Encountered error "${result.reason}" while parsing grammar at ${result.token.line}:${result.token.column}` ); return result.value; };