动手编写一个简单APL解释器(一)
速览
本系列教程旨在教读者构建一个简单的APL语言解释器。第一部分从基础概念入手,逐步讲解实现思路。适合对编程语言实现感兴趣的开发者。
AI 深度解读
背景
本文是系列博客“Let's build a simple interpreter for APL”的第一部分,作者受 Ruslan Spivak 的经典系列“Let's build a simple interpreter”(基于 Pascal 的解释器构建教程)启发。作者此前曾基于该系列创建了 Roj 编程语言,现在决定再次研读该系列,但目标转向构建一个 APL 解释器。APL 是一种以数组为中心、符号丰富的编程语言,其执行顺序从右向左,与 Pascal 等主流语言差异显著。作者通过撰写本系列,旨在深入学习 APL、锻炼 Python 编程能力、记录实现过程,并帮助读者自行构建 APL 解释器。
核心内容
本博客的目标是解析简单的 APL 语句,支持以下特性:
- 浮点数与整数(正数和负数,APL 中用
¯表示负号,例如¯3代表 -3)以及这些数值的向量; - 函数
+、-、×、÷的一元(monadic)和二元(dyadic)用法; - 交换/切换运算符
⍨(commute/switch operator); - 括号表达式。
词法分析(Tokenization)
第一步是将 APL 源代码拆分为 token(标记),去除不必要的空白字符,并识别每个字符的含义。例如,识别数字是整数还是浮点数,识别 APL 符号并关联其名称。
作者定义了 Token 类,包含以下 token 类型:INTEGER、FLOAT、PLUS、MINUS、TIMES、DIVIDE、NEGATE、COMMUTE、LPARENS、RPARENS、EOF(文件结束符)。同时提供了从字符到 token 类型的映射(WYSIWYG,所见即所得),例如 + 映射为 PLUS,- 映射为 MINUS,× 映射为 TIMES,÷ 映射为 DIVIDE,( 映射为 LPARENS,) 映射为 RPARENS,⍨ 映射为 COMMUTE。
Token 类实现了 __str__ 和 __repr__ 方法,以便友好地打印 token 实例。
Tokenizer 类
Tokenizer 类负责将源代码字符串转换为 token 列表。关键设计决策:
- 从右向左扫描:因为 APL 的执行顺序是从右向左,所以 tokenizer 从字符串末尾开始,逐步向左移动光标。
advance()方法将光标左移一位,并更新current_char,当光标到达字符串开头(即位置小于 0)时,将current_char设为None。skip_whitespace()跳过空格和制表符。get_integer()解析连续的数字字符,返回整数子串。get_number_token()解析数字 token:先获取整数部分,检查是否紧跟小数点(.),若有则继续获取小数部分;然后检查是否前有¯符号(表示负数),若有则添加负号。最终根据是否包含小数点返回FLOAT或INTEGERtoken。get_wysiwyg_token()根据当前字符从映射中获取对应 token(如+、-等)。get_next_token()调用skip_whitespace()后,根据当前字符类型(数字、WYSIWYG 字符、或空)返回相应 token,若无法识别则抛出异常。tokenize()方法不断调用get_next_token()直到遇到EOFtoken,将所有 token 收集到列表中,最后反转(因为是从右向左收集,反转后顺序变为从左到右,但注意EOFtoken 会出现在列表首位)。例如,表达式5 -⍨ ¯2.3会被 tokenize 为[Token(EOF, None), Token(INTEGER, 5), Token(MINUS, -), Token(COMMUTE, ⍨), Token(FLOAT, -2.3)]。
抽象语法树(AST)的规划
获得 token 列表后,需要构建抽象语法树(AST)以结构化表示程序。AST 的构建也是从右向左遍历 token 列表,识别标量、数组、运算符、一元/二元函数等。作者计划先定义 APL 子集的语法(grammar),然后基于语法生成 AST,从而简化后续的解释过程。本博客尚未给出完整的 AST 构建代码,但已为后续部分打下基础。
关键要点
- APL 的执行顺序是从右向左,因此 tokenizer 选择从右向左扫描,这与其他大多数语言的解释器不同。
- APL 负数使用
¯符号,而非减号-,处理时需注意符号的上下文(一元 vs 二元)。 - Token 类型设计:包括
INTEGER、FLOAT、PLUS、MINUS、TIMES、DIVIDE、NEGATE、COMMUTE、LPARENS、RPARENS、EOF,并定义了字符到类型的映射。 - 数字解析:支持整数、浮点数
