PostScript 是一种传统上用于描述打印文档的语言,它运行在基于栈的执行模型上,程序被处理为一系列以空格分隔的标记(tokens)。通过将这些机制映射到 JavaScript 类型,开发者可以构建一个功能性的“玩具”解释器,用于处理基础算术、数组操作和过程定义。

基于栈的执行模型

PostScript 逻辑的核心依赖于两个主要结构:操作数栈和字典栈。在这种模型中,操作符从操作数栈中弹出值,执行计算,然后将结果推回栈中。诸如 `/double` 之类的名称作为字面量推入栈中,而其他名称则在字典中查找以找到相关的过程。

例如,命令 `3 double` 首先推入数字 3。解释器随后在字典栈中查找“double”,找到其关联的过程(例如 `{2 mul}`)并执行它。该过程弹出 3,将其乘以 2,并将结果 6 推回栈中。

“PostScript 主要设计用于描述打印文档:它拥有丰富的特性词汇,用于创建和操作光栅图形和矢量图形。”

— Let's Write a PostScript Interpreter

将 PostScript 映射到 JavaScript

为了构建简化的解释器,必须将 PostScript 数据类型转换为 JavaScript 等效类型。数字和布尔值直接映射到 JavaScript 的 `Number` 和 `Boolean` 类型,而 PostScript 数组和字典则表示为 JavaScript 的 `Array` 和 `Map` 对象。PostScript 的一个独特概念 `mark` 被实现为一个单例对象,用于跟踪数组构建时的栈位置。

解析过程使用正则表达式将程序分解为标记,并过滤掉行注释(以 `%` 开头)。解释器随后遍历这些标记。如果一个标记是没有 `/` 前缀的名称,系统将从字典中检索其过程;如果它是一个过程,解释器将执行包含在花括号内的标记序列。

显示 PostScript 解释器核心逻辑 JavaScript 实现的代码列表。
JavaScript 实现展示了如何将 PostScript 的基于栈的机制映射到现代数据结构。beyondloom.com

此代码列表说明了将 PostScript 类型转换为 JavaScript 对象以及执行过程的过程。

实现与局限性

解释器的机制主要包含在基础操作符定义中。这些包括标准算术(add, sub, mul, div)、栈操作(dup, exch, roll)和控制流(ifelse, for, forall)。通过在初始化期间将这些基础操作符安装到字典栈中,解释器可以执行复杂的嵌套表达式,如 `[11 22 33] {10 mul} forall`。

解释器源代码的截图,显示执行循环和标记处理。
解释器的执行循环识别并处理标记,区分字面量、名称和过程。beyondloom.com

此视觉效果提供了解释器机制以及它如何处理标记序列的具体示例。

此实现是一个“人类规模”的练习,旨在理解语言机制,而非生产就绪的工具。为了简化,它故意省略了复杂的特性,如字符串处理和错误处理。全功能的 PostScript 引擎(如 Ghostscript)涉及高保真图形渲染和字体管理方面的显著额外复杂性,这超出了基础解释器的范围。

Hacker News 读者的反应

  • 亲身经验: 一位评论者建议,构建一个小型的解释器是理解编程语言内部如何运作的有价值的教育练习。 评论 ↗
  • 质疑: 另一位读者认为核心语言相对简单,而 PostScript 实现的真正复杂性在于图形和字体渲染。 评论 ↗

以上为 Hacker News 讨论中观点的摘要,并非经过核实的事实。 阅读完整讨论 ↗