使用TypeScript完成一个monkey语言解释器
原版书籍为用Go语言自制解释器
根据b站up主Snow-原青阳的js版学习更改而成
前言
实现了词法分析器和REPL
词法分析器
1 定义词法单元
只是一些常量和类型定义
见 token.ts
1 | export enum TokenType { |
2 单元测试函数
初步写一个测试函数,测试词法分析器是否能正确的将源代码转换为词法单元
结构很简单,定义输入的字符串和答案数组,然后遍历验证是不是正确
见 test/lexer.test.ts
1 | import { TokenType } from '../src/token'; |
3 词法分析1 最基础的特殊符号
先简单实现 Lexer 的框架,就是读字符串,然后逐个转换成 token 里定义的 enum。
主要是实现基础功能,比如一些状态,下一个的函数之类的,只添加了对基础的特殊符号识别
见 lexer.ts
1 | import { Token, TokenType } from './token'; |
然后npm run test运行测试,打印所有测试用例均测试完成!
,并且前面没有任何错误打印,说明简单的Lexer完成
4 词法分析2 简单关键字和数字、空格
实现更复杂的token转换。比如多字符关键字,空格,回车等
这里先实现let fn两个关键字和数字,以及用户的自定义变量
先把测试函数里的输入和答案数组改了
见 test/lexer.test.ts
1 | const input = ` |
这时候肯定是通过不了测试。需要继续完善lexer
先完善token.ts,添加关键字的映射
见token.ts
1 | const keywordIdentMap = new Map<string, TokenType>([ |
然后扩展switch语句,用以识别关键字和标识符,数字
并且需要跳过空白字符
见lexer.ts
1 | nextToken = (): Token => { |
然后npm run test运行测试
这时发现我们的简单测试用例
1 | let five = 5; |
能通过了,成功识别了用户的变量以及数字、let fn关键字
4 词法分析3 剩下的全部关键字
接下来是剩下的关键字。双字符的==复杂一点,剩下的关键字true false return之类的,以及+-*/之类的运算符和前面一样
测试用例加上这几个
见 test/lexer.test.ts
1 | /** 前略 */ |
在token.ts里添加这些关键字的映射
见token.ts
1 | MINUS = '-', |
然后在lexer.ts的switch里简单添加上这些的识别就行了,和前面基本一样。
不一样的就是这个双字符的关键字==和!=,单独处理一些即可,用了个peekChar函数预读下一个字符
见lexer.ts
1 | case '=': |
改完之后运行npm run test,测试没有报错均通过,完成这部分的编写
REPL
1 什么是REPL
REPL是Read-Eval-Print Loop的缩写,顾名思义就是读入一行代码,执行它,然后打印结果。
对用户来说直观来看就是一个控制台,方便用户交互用的。用户输入它就执行,输出结果
实现上来看,就是一个死循环里用node的readline模块,监听用户输入的文本,按到回车就执行,然后输出结果,开始下一轮
2 REPL实现
虽然说现在只是实现了源码转token的词法分析器,但是我们可以先实现一个简单的REPL,先把token打印出来,看看我们词法分析器是否正确
下面实现一下。
见repl.ts
1 | import { createInterface } from 'readline'; |
然后为了方便,在package.json里加上一个命令
1 | "scripts": { |
运行npm run repl,打开repl。
试着输入一些monkey的代码,
比如let a = 1; fn abc(a, b) { return a + b; };
看看能不能正确的打印出token
结果是正确打印如下
1 | npm run repl |
repl至此完成
结语
这次完成了词法分析器和REPL的实现,虽然只是简单的实现了一些功能,但是已经能看出monkey语言的雏形了
接下来会继续实现语法分析器