Skip to content

Instantly share code, notes, and snippets.

@mrjacobbloom
Last active July 23, 2021 16:41
Show Gist options
  • Save mrjacobbloom/6909bd408700178198f4d50579f5d43b to your computer and use it in GitHub Desktop.
Save mrjacobbloom/6909bd408700178198f4d50579f5d43b to your computer and use it in GitHub Desktop.
A way to structure your Lox AST file in TypeScript that sidesteps code generation and still uses the visitor pattern
/**
* Here's a way to structure your Lox AST file in TypeScript that sidesteps code generation
* (Lox is the language you build in "Crafting Interpreters" by Bob Nystrom)
* Would it have been more TSy to have the AST nodes just be interfaces? Probably, but
* I wanted to follow the book and its visitor pattern.
*/
export abstract class Expr {
public accept<T>(visitor: ExprVisitor<T>): T {
// Decide what the correct visitXExpr method name should be using the class's name
// This is a hack, but it keeps us from having to write an `accept` method in each subclass
return (visitor as any)[`visit${this.constructor.name}Expr`].call(visitor, this);
}
// This is all we need to write to create the new AST node, and out ExprVisitor type updates itself
public static Binary = class Binary extends Expr {
constructor(public left: Expr, public operator: Token, public right: Expr) { super(); }
};
// ...
}
type ExprNames = Exclude<keyof typeof Expr, 'prototype'>;
/*
* TypeScript struggles with edge cases in "type expressions" (is that a thing?) regarding static
* properties of a class, especially when that static property is another class. So these are both broken:
* function makeALiteral(): Expr.Literal { ... }
* function makeALiteral(): Expr["Literal"] { ... }
* Instead, we use ExprT to grab the right property off of Expr:
* function makeALiteral(): ExprT<'Literal'> { ... }
*/
export type ExprT<T extends ExprNames> = InstanceType<(typeof Expr)[T]>;
export type ExprVisitor<T> = {
// This uses the new template literal types to generate strings in type space!!!
[key in ExprNames as `visit${key}Expr`]: (expr: ExprT<key>) => T;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment