鹦鹉虚拟机/Squaak教程/总结和结论
第1集: 简介
第2集: 深入编译器内部
第3集: Squaak细节和第一步
第4集: PAST节点和更多语句
第5集: 变量声明和作用域
第6集: 作用域和子程序
第7集: 运算符和优先级
第8集: 哈希表和数组
第9集: 总结和结论
欢迎来到鹦鹉编译器工具教程的最后一集!让我们回顾一下之前的章节,并总结一下这个教程。
在第1集中,我们介绍了鹦鹉编译器工具(PCT),概述了Squaak的高级功能,Squaak是我们在这个教程中实现的案例研究语言,我们还生成了一个语言shell,作为实现Squaak的基础。
第2集讨论了基于PCT的编译器的一般结构。在此之后,我们描述了四个默认编译阶段:解析阶段、解析树到PAST、PAST到POST和POST到PIR。我们还在交互式语言shell中添加了一个命令行横幅和命令行提示符。
在第3集中,我们介绍了Squaak语言的完整语法。之后,我们开始实现第一部分,然后我们能够为(简单的)赋值生成代码。
在第4集中,我们更详细地讨论了鹦鹉抽象语法树节点的构建,之后我们实现了if语句和throw语句。
第5集重点介绍了变量声明和变量作用域。我们实现了必要的基础设施来正确处理全局和局部变量。在第6集中,我们继续讨论作用域,但现在是在子程序的上下文中。之后我们实现了子程序调用。
第7集扩展了我们的语法以处理复杂的表达式,使我们能够使用算术运算符和其他运算符。我们讨论了如何使用PCT的内置支持来处理运算符优先级。
在上一集第8集中,我们讨论了处理Squaak的聚合数据类型的语法和操作方法:数组和哈希。我们还提到了按引用和按值传递参数的主题。
如果你完成了教程并做了练习,你的实现应该已经完成。虽然讨论了很多实现,但有些部分留作了习题。这是为了鼓励你亲自动手,自己弄清楚事情,而文本中包含足够的提示(在我看来)来解决给定的问题。当然,这种方法要求你花更多的时间,自己思考,但我认为你阅读所有这些内容是为了学习一些东西。在我看来,花更多的时间是值得的。
现在是看看我们能用这种语言做什么的时候了。Squaak不仅仅是初学者在解析器讨论中经常提供的平均计算器示例;它是一种完整的编程语言。
这是鹦鹉编译器工具教程的最后一集。我们展示了如何在短短几百行源代码中为鹦鹉虚拟机实现一种完整的语言。当然,这必须证明PCT确实是一个实现语言的有效工具包。在撰写本文时,PCT仍然缺乏对某些语言结构的有效支持。因此,我们专注于那些使用PCT很容易构建的部分。一旦PCT功能完备,肯定会发布另一个关于高级功能的教程。想想面向对象编程、闭包、协程以及高级控制流,如return语句。大多数功能已经可以实现,但对于这个教程的水平来说太复杂了。
你可能已经注意到Squaak有点像Lua,虽然它在某些方面有所不同。这并非完全偶然。在Lua源代码的发布中,有一个名为“life.lua”的示例,它实现了康威的“生命游戏”。这是一个很好的演示程序,很容易移植到Squaak。它的实现如下所示。运行它,享受吧!
## John Conway's Game of Life ## Implementation based on life.lua, found in Lua's distribution. ## var width = 40 # width of "board" var height = 20 # height of "board" var generation = 1 # generation couner{{typo help inline|reason=similar to cooner|date=September 2022}} var numgenerations = 50 # how often should we evolve? ## initialize board to all zeroes sub initboard(board) for var y = 0, height do for var x = 0, width do board[y][x] = 0 end end end ## spawn new life in board, at position (left, top), ## the life data is stored in shapedata, and shape width and ## height are specified. sub spawn(board, left, top, shapew, shapeh, shapedata) for var y = 0, shapeh - 1 do for var x = 0, shapew - 1 do board[top + y][left + x] = shapedata[y * shapew + x] end end end ## calculate the next generation. sub evolve(thisgen, nextgen) var ym1 = height - 1 var y = height var yp1 = 1 var yi = height while yi > 0 do var xm1 = width-1 var x = width var xp1 = 1 var xi = width while xi > 0 do var sum = thisgen[ym1][xm1] + thisgen[ym1][x] + thisgen[ym1][xp1] + thisgen[y][xm1] + thisgen[y][xp1] + thisgen[yp1][xm1] + thisgen[yp1][x] + thisgen[yp1][xp1] nextgen[y][x] = sum==2 and thisgen[y][x] or sum==3 xm1 = x x = xp1 xp1 = xp1 + 1 xi = xi - 1 end ym1 = y y = yp1 yp1 = yp1 + 1 yi = yi - 1 end end ## display thisgen to stdout. sub display(thisgen) var line = "" for var y = 0, height do for var x = 0, width do if thisgen[y][x] == 0 then line = line .. "-" else line = line .. "O" end end line = line .. "\n" end print(line, "\nLife - generation: ", generation) end ## main program sub main() var heart = [1,0,1,1,0,1,1,1,1] var glider = [0,0,1,1,0,1,0,1,1] var explode = [0,1,0,1,1,1,1,0,1,0,1,0] var thisgen = [] initboard(thisgen) var nextgen = [] initboard(nextgen) spawn(thisgen,3,5,3,3,heart) spawn(thisgen,5,4,3,3,glider) spawn(thisgen,25,10,3,4,explode) while generation <= numgenerations do evolve(thisgen, nextgen) display(thisgen) generation = generation + 1 ## prevent switching nextgen and thisgen around, ## just call evolve with arguments switched. evolve(nextgen, thisgen) display(nextgen) generation = generation + 1 end end ## start here. main()
注意使用子程序“print”。查看文件src/builtins/say.pir,并将子程序“say”(由语言shell创建脚本生成)重命名为“print”。
如果你不想做练习,或者只是想看看它是什么样子,而不想做任何麻烦,这里就是它是什么样子(这是生命第9代)。
----------------------------------------- ----------------------------------------- ----------------------------------------- -----O----------------------------------- ----OO----------------------------------- --OO--O---------------------------------- -----OO---------------------------------- --OOOOO------------------OOO------------- ------------------------O---O------------ -----------------------O-----O----------- ----------------------O---O---O---------- ----------------------O--O-O--O---------- ----------------------O---O---O---------- -----------------------O-----O----------- ------------------------O---O------------ -------------------------OOO------------- ----------------------------------------- ----------------------------------------- ----------------------------------------- ----------------------------------------- ----------------------------------------- Life - generation: 9
但实际上,它与在鹦鹉上运行这个程序相比是无法比拟的。:-)
Squaak被设计成一种简单的语言,提供足够的功能来完成一些工作,但同时保持简单。当然,在阅读完本教程之后,你也是专家了;-) 如果你想添加更多功能,这里有一些建议。
- 实现前缀和后缀自增/自减运算符,允许你写“generation++”而不是“generation = generation + 1”。
- 实现自增赋值运算符,例如“+=”及其同类。
- 扩展语法以允许在一个语句中声明多个变量,允许你写“var x = 1, y, z =3”。当然,初始化部分仍然是可选的。你如何确保标识符和初始化表达式保持在一起?
- 实现一种机制(如“import”语句)来包含或加载另一个Squaak文件,这样Squaak程序就可以拆分成多个文件。PCT不支持此功能,因此你需要编写一些PIR来实现它。
- 改进for语句,以允许负步长。请注意,这样做会使循环条件变得更加复杂。
请注意,这些只是建议,我没有亲自实现它们,因此在最后我不会提供解决方案。
到目前为止,你应该对PCT有了很好的印象,并且应该能够处理针对鹦鹉的其他语言。目前,已经针对ECMAScript、Python、Ruby以及当然还有Perl 6完成了工作。它们中的大多数尚未完成(提示,提示)。
我希望你喜欢阅读本教程,并且学到了足够的知识,让你对处理其他(现有)针对鹦鹉的语言充满信心。Perl 6的实现仍然需要更多贡献者!
非常感谢所有阅读本教程并为我提供提示、技巧和反馈的人!感谢你阅读本教程!