Home > OS >  How to use Typescript generics to parameterize parsing grammars?
How to use Typescript generics to parameterize parsing grammars?

Time:08-20

I would like to write in (current) Typescript a general parser parameterized over symbols particular to language grammars.

Below is the simplest of several tried approaches of parameterizing Sym, the type for the symbols that varies per language grammar, but the first line in Parser.js fails type checking:

Type Sym does not satisfy the constraint string | number | symbol.

Every way I tried to adjust the definition of GrammarRules<Sym> that resolved the issue unfortunately resulted in further downstream problems, only some of portion of which were themselves resolvable – couldn't find a full solution.

How can I define the symbol type, Sym, differently for each parser language and handle generically in Parser.js? I'm open to substantially re-working some or all of the declarations and code to get the cleanest overall solution.

Parser.ts

export type GrammarRules<Sym> = Record<Sym, GrammarRule<Sym>>
export type GrammarRule<Sym> = { lhs: Sym /* further uses of Sym type */ }

export class Parser<Sym> {
  rules: GrammarRules<Sym>
  private str: string
  /* Various other datastructure declarations with types dependent on Sym */

  constructor(rules: GrammarRules<Sym>) {
    this.rules = rules
  }
  parse(startSym: Sym, str: string) {
    this.str = str
    console.log(this.rules[startSym].lhs)
    // ...
  }
}

ParserLangABC.ts

import { Parser, GrammarRules } from "./Parser"

type Sym = 'A' | 'B' | 'C'
const rulesABC: GrammarRules<Sym> = {
  A: { lhs: 'A' /* rhs with further data of type Sym */ },
  B: { lhs: 'B' /* rhs with further data of type Sym */ },
  C: { lhs: 'C' /* rhs with further data of type Sym */ }
}
class ParserLangABC<Sym> extends Parser<Sym> {
  static parse(str: string) {
    const parser = new Parser(rulesABC)
    parser.parse('A', str)
  }
  // Other supporting methods parameterized to Sym
}

ParserLangDEF.ts

import { Parser, GrammarRules } from "./Parser"

type Sym = 'D' | 'E' | 'F'
const rulesDEF: GrammarRules<Sym> = {
  D: { lhs: 'D' /* rhs with further data of type Sym */ },
  E: { lhs: 'E' /* rhs with further data of type Sym */ },
  F: { lhs: 'F' /* rhs with further data of type Sym */ }
}
class ParserLangDEF<Sym> extends Parser<Sym> {
  static parse(str: string) {
    const parser = new Parser(rulesDEF)
    parser.parse('D', str)
  }
  // Other supporting methods parameterized to Sym
}

CodePudding user response:

Not sure what you are trying to do with parsers but I think this is what you want regarding templates. The key is to define export type SymBase = string | number | symbol; and replace Sym with Sym extends SymBase where needed.

diff b/Parser.ts a/Parser.ts
1c1
< export type GrammarRules<Sym> = Record<Sym, GrammarRule<Sym>>
---
> export type SymBase = string | number | symbol;
> export type GrammarRules<Sym extends SymBase> = Record<Sym, GrammarRule<Sym>>
4c4
< export class Parser<Sym> {
---
> export class Parser<Sym extends SymBase> {
diff b/ParserLangABC.ts a/ParserLangABC.ts
1c1
< import { Parser, GrammarRules } from "./Parser"
---
> import { SymBase, Parser, GrammarRules } from "./Parser"
9c9
< class ParserLangABC<Sym> extends Parser<Sym> {
---
> class ParserLangABC<Sym extends SymBase> extends Parser<Sym> {
diff b/ParserLangDEF.ts a/ParserLangDEF.ts
1c1
< import { Parser, GrammarRules } from "./Parser"
---
> import { SymBase, Parser, GrammarRules } from "./Parser"
9c9
< class ParserLangABC<Sym> extends Parser<Sym> {
---
> class ParserLangABC<Sym extends SymBase> extends Parser<Sym> {

  • Related