I am working on a compiler in TypeScript at the moment and I have an Enum to represent Token Types and a class for the actual Token:
enum TokenType {
String,
Integer,
Float,
Identifier,
// ... elided
}
class Token {
type: TokenType
lexeme: string
lineNo: number
columnNo: number
constructor(
type: TokenType,
lexeme: string,
lineNo: number,
columnNo: number
) {
this.type = type
this.lexeme = lexeme
this.lineNo = lineNo
this.columnNo = columnNo
}
toString(): string {
return (
'Token{' +
[this.type, this.lexeme, this.lineNo, this.columnNo].join(',') +
'}'
)
}
}
In my AST node's types I would like to specify that the Token hold a specific type, like for example in the FunctionDeclaration
type:
type FunctionDeclaration = {
ident: Token with type = TokenType.identifier
// ^ Imaginary syntax, but this is what I'm trying to do
}
I've tried using extend
like:
interface IdentifierToken extends Token {
type: TokenType.Identifier
}
However, this makes me cast a new Token(TokenType.Identifier, ...)
as IdentifierToken
even though the Token's type is TokenType.Identifier
.
Also I would prefer to not have to declare new separate types for all the different TokenTypes (as there are ~25). So, would an inline way to enforce the values of class properties be possible?
You may want to consider making Token
a generic class with a type parameter corresponding to the particular subtype of TokenType
you are using:
class Token<T extends TokenType = TokenType> {
type: T
lexeme: string
lineNo: number
columnNo: number
constructor(
type: T,
lexeme: string,
lineNo: number,
columnNo: number
) {
this.type = type
this.lexeme = lexeme
this.lineNo = lineNo
this.columnNo = columnNo
}
}
Then you can easily refer to "a Token
with a type
equal to XXX
as Token<XXX>
:
type FunctionDeclaration = {
ident: Token<TokenType.Identifier>
}
And additionally when you use the Token
constructor, the compiler will infer T
based on the construct parameters:
const identifierToken = new Token(TokenType.Identifier, "", 1, 2);
// const identifierToken: Token<TokenType.Identifier>
const f: FunctionDeclaration = { ident: identifierToken }; // okay
const floatToken = new Token(TokenType.Float, "", 3, 4);
// const floatToken: Token<TokenType.Float>
const g: FunctionDeclaration = { ident: floatToken }; // error!
// Type 'Token<TokenType.Float>' is not assignable to type 'Token<TokenType.Identifier>'.