Tokenizer
Token 类型
定义一个名为 TokenType 的枚举和一个名为 Token 的接口。
export const enum TokenType {...}是一个常量的,并且在编译时被内联,以减少运行时的开销。这里定义了四个令牌类型:PAREN、NUMBER、STRING和NAME,它们分别代表括号、数字、字符串和名称。在 Tokenizer 类中,这些类型可以用来表示不同类型的标记,以便于对输入的代码进行分类和解析。export type Token = {...}是一个类型别名,它定义了一个名为Token的接口。这个接口有两个属性:type和value。type属性的类型是TokenType,这意味着它只能是枚举TokenType中的四个值之一。value属性是一个字符串,表示令牌的值。在使用这个接口时,你可以创建符合这个接口定义的对象,这些对象可以被TypeScript的类型系统理解和约束。
export const enum TokenType {
PAREN,
NUMBER,
STRING,
NAME,
}
export type Token = {
type: TokenType,
value: string,
}
工具类
先实现一些工具类以方便后面使用。
TokenList
首先定义了一个名为 TokenList 的类。这个类的主要功能是管理一个名为 _tokens 的私有属性,这个属性是一个数组,用来存储类型为 T 的标记(tokens)。
get value(): T[] {...}:这是一个 getter 方法,允许其他代码片段访问_tokens数组。constructor(tokens?: T[]){...}:这是类的构造函数。它接受一个可选的tokens参数。如果在创建TokenList实例时提供了tokens数组,它将复制这个数组来初始化_tokens属性。addToken(token: T){...}:上添加新的标记。新的标记会被添加到_tokens数组的末尾。clear(){...}:这个方法重置_tokens数组为空。
export class TokenList<T> {
private _tokens: T[] = []
get value() {
return this._tokens
}
constructor(tokens?: T[]) {
if (tokens) {
this._tokens = [...tokens]
}
}
addToken(token: T) {
this._tokens.push(token)
}
clear() {
this._tokens = []
}
}
此 TokenList 类提供了一个灵活的接口,让我们可以轻松地管理一组标记,在实际开发中可以帮助你组织和操作标记化的代码或数据。
Char
编写的一个名为 Char 的类。它有一个私有属性 _value,用于存储一个字符。类中定义了 getter 和 setter 方法,允许外部代码片段访问和修改 _value 属性。Char 类提供了一种清晰的方式来处理字符数据,通过封装字符值和相关操作,使得代码更加模块化和可维护。
value 获取器允许外部读取 Char 实例的 _value 属性,is 和 not 方法用于检查 _value 属性是否等于或不等于某个特定字符。这种设计模式使得 Char 类可以作为一个封装字符的容器,并且可以灵活地与其他部分代码交互。例如,你可以创建一个 Char 实例来表示某个特定的字符,然后使用它的方法来执行各种逻辑,例如比较、匹配或转换。
export class Char {
private _value: string
get value() {
return this._value
}
set value(c: string) {
this._value = c
}
constructor(c?: string) {
if (c) {
this._value = c
}
}
is(c: string) {
return this._value === c
}
not(c: string) {
return !this.is(c)
}
}
具体实现
这里写了一个 BaseTokenizer 的抽象类,后面的 Tokenizer 仅需实现里面的 tokenize() 方法即可。这个类作为一个基类,用于创建特定的标记生成器,这些标记生成器可以处理不同类型的标记。
-
export abstract class BaseTokenizer<T> {...}:这行代码声明了一个可导出的抽象基类BaseTokenizer,并引入了一个泛型类型T。这意味着任何继承自BaseTokenizer的类都需要指定T的具体类型,以表示标记的类型。 -
private _tokens: T[] = []:这个私有属性是一个泛型数组,用于存储标记。它被初始化为一个空数组,以便在tokenize方法中生成的标记可以被存储在这里。 -
private _rawSourceCode: string = ```:另一个私有属性,用于保存原始源代码字符串。这个字符串将由tokenize` 方法解析以生成标记。 -
constructor(rawSourceCode: string) {...}:构造函数用于初始化BaseTokenizer类的实例,它接收一个字符串参数rawSourceCode。这个字符串代表了需要被标记化的源代码。 -
public run(): T[] {...}:run方法作为一个公共方法,允许外部代码片段调用它来执行标记化过程。当调用run方法时,它调用了受保护的抽象方法tokenize,并将返回的标记数组存储在_tokens属性中。然后,run方法返回这个标记数组,使得BaseTokenizer的用户可以访问生成的标记。 -
protected abstract tokenize(rawSourceCode: string): T[] {...}:tokenize方法是一个抽象的受保护方法,它必须在继承BaseTokenizer的子类中被实现。这个方法负责解析原始源代码字符串,并返回一个类型为T的标记数组。因为它是抽象的,所以BaseTokenizer类本身并不提供这个方法的具体实现,而是留给子类来完成。
BaseTokenizer 类提供了一个框架,让开发者可以创建自定义的标记器,这些标记器可以解析和生成不同类型的标记。通过继承 BaseTokenizer 并实现 tokenize 方法,开发者可以根据具体的需求来定制标记化逻辑。run 方法提供了一个标准化的接口,使得标记化过程可以很容易地被触发和访问。这种设计模式在软件开发中非常有用,特别是在需要处理和分析代码或文本数据的领域,如编译器、文本编辑器、自然语言处理等。
export abstract class BaseTokenizer<T> {
private _tokens: T[] = []
private _rawSourceCode: string = ``
constructor(rawSourceCode: string) {
this._rawSourceCode = rawSourceCode
}
public run(): T[] {
this._tokens = this.tokenize(this._rawSourceCode)
return this._tokens
}
protected abstract tokenize(rawSourceCode: string): T[]
}
最后重点是实现 BaseTokenizer 中的 tokenize() 方法。
定义了一个名为 Tokenizer 的类,它继承自 BaseTokenizer 类,并实现了 tokenize 方法。这个 Tokenizer 类用于将原始源代码字符串转换为 Token 对象的数组。
import { Token, TokenType } from '../types': 导入Token类型和TokenType枚举。这允许Tokenizer类访问这些类型,以便创建表示不同类型标记的Token对象。import { Char, TokenList } from "../utils": 导入Char和TokenList。Char类允许Tokenizer类在处理源代码时创建和检查单个字符。TokenList类提供了管理生成的Token对象的数组的功能。import { BaseTokenizer } from '../utils/baseTokenizer': 导入BaseTokenizer类作为超类。Tokenizer类将继承BaseTokenizer的属性和方法,并需要实现tokenize方法。const RegExpHelper = {...}': 创建一个名为RegExpHelper的常量对象,其中包含用于检查空格、数字和字母的正则表达式。这些正则表达式将帮助Tokenizer类识别标记的类型。export class: 定义Tokenizer类。它继承自BaseTokenizer,并使用Token类型进行泛型。通过继承BaseTokenizer,Tokenizer` 类获得了处理标记的基本功能。protected tokenize(rawSourceCode: string): Token[] {...}: 实现tokenize方法。这是一个受保护的抽象方法,它负责将原始源代码字符串分解成Token对象的数组。Tokenizer类中的实现对源代码进行逐字分析,根据字符的类型或值创建不同类型的Token。while (current < codeLen) {...}: 一个while循环,用于遍历原始源代码字符串的每个字符。current变量表示当前正在检查的字符位置,而codeLen表示字符串的总长度。循环将继续,直到current达到字符串的末尾。- 各种
if/else块用于检查字符类型并据此创建Token:- 根据
RegExpHelper.WHITESPACE正则表达式,忽略空格字符。 - 使用自定义的
Char类检查括号字符,并创建TokenType.PAREN类型的Token。 - 根据
RegExpHelper.NUMBERS正则表达式,识别数字序列并创建TokenType.NUMBER类型的Token。 - 检查双引号字符,以捕获字符串值。循环持续直到遇到另一个双引号,然后创建
TokenType.STRING类型的Token。 - 根据
RegExpHelper.LETTERS正则表达式,识别字母序列并创建TokenType.NAME类型的Token。
- 根据
throw new TypeError(...): 如果遇到一个字符,它不符合上述任何条件,Tokenizer类将抛出一个错误,指示它无法识别该字符。return tokens.value: 当tokenize方法完成时,它返回TokenList实例中存储的Token对象的数组。value获取器可能是TokenList类的一个方法,用于返回内部_tokens数组。
这个 Tokenizer 类提供了一个灵活的方式来将源代码字符串转换为一个有序的 Token 对象数组。通过解析字符和创建适当的 Token,它为后续的语法分析、代码生成或其他需要将源代码表示为结构化数据的任务做准备。
import { Token, TokenType } from '../types'
import { Char, TokenList } from "../utils"
import { BaseTokenizer } from '../utils/baseTokenizer'
const RegExpHelper = {
WHITESPACE: /\s/,
NUMBERS: /[0-9]/,
LETTERS: /[a-z]/i,
} as const
export class Tokenizer extends BaseTokenizer<Token> {
protected tokenize(rawSourceCode: string): Token[] {
let current: number = 0
const codeLen: number = rawSourceCode.length
const tokens = new TokenList<Token>()
while (current < codeLen) {
const char = new Char(rawSourceCode[current])
if (RegExpHelper.WHITESPACE.test(char.value)) {
current++
continue
}
if (char.is('(')) {
tokens.addToken({ type: TokenType.PAREN, value: '(' } as Token)
current++
continue
}
if (char.is(')')) {
tokens.addToken({ type: TokenType.PAREN, value: ')' } as Token)
current++
continue
}
if (RegExpHelper.NUMBERS.test(char.value)) {
let value = ''
while (RegExpHelper.NUMBERS.test(char.value)) {
value += char.value
char.value = rawSourceCode[++current]
}
tokens.addToken({ type: TokenType.NUMBER, value } as Token)
continue
}
if (char.is('"')) {
let value = ''
char.value = rawSourceCode[++current]
while (char.not('"')) {
value += char.value
char.value = rawSourceCode[++current]
}
char.value = rawSourceCode[++current]
tokens.addToken({ type: TokenType.STRING, value } as Token)
continue
}
if (RegExpHelper.LETTERS.test(char.value)) {
let value = ''
while (RegExpHelper.LETTERS.test(char.value) && current < codeLen) {
value += char.value
char.value = rawSourceCode[++current]
}
tokens.addToken({ type: TokenType.NAME, value } as Token)
continue
}
throw new TypeError(`I don't know what this character: ${char.value} is.`)
}
return tokens.value
}
}