LogoCompiler的设计和原理
一、前言
本⽂档并不是项⽬⽂档的⼀部分,我的两位(或者⼀位)队友负责⽂档的撰写,这部分⼯作依然由他们 负责,但我觉得有⼀些内容值得⼀提,⽂档中却没有讲清楚,因此本⽂可以作为补充材料。
虽然LogoComplier是⼀⻔讲述⾯向过程编程的课程的⼤作业,但是在本项⽬中,⾯向对象编程 (OOP)的技巧和思想⼀以贯之。事实证明,OOP的引⼊使LogoComplier变得更为强⼤、灵活、明 了。⽽且OOP在本项⽬中并不是对功能函数的简单包装,⽽是使⽤了继承、多态,例如Op类是所有操 作的基类,它派⽣出MoveOp、TurnOp、DefOp等,⼦类重写虚函数exec(),以表现不同的⾏为。
LogoComplier应该被视为⼀种logo语⾔的编译器,因此它的使⽤⽅法与⼤多数编译器类似: ./logoCompiler testcase1.logo
正常情况下,会在输⼊⽂件相同⽂件夹内⽣成⼀个testcase1.bmp⽂件。
二、设计思路
LogoComplier的主要构成包括三部分:词法分析器(Lexer),解释器(Interpreter),执⾏器 (Executor)。
2.1 Lexer
Lexer的功能是词法分析,是使⽤我们平常构建编译器时⽤的lex和yacc中的lex实现的。我发现 LogoComplier的语法实在太过简单,以⾄于不需要⽤yacc。Lexer把输⼊⽂件中的内容分为⼀个⼀个符 号(Symbol),压⼊队列lexQueue,并将其传给Interpreter,开始第⼆阶段的分析。
2.2 Interpreter
Interpreter会做⼀些简单的语法检查,然后通过分析lexQueue,调⽤Executor的接⼝,将Symbol序列 转变为可执⾏的Op序列。之后调⽤Executor的run()⽅法,开始第三阶段的执⾏。
2.3 Executor
Executor负责实际的运算和绘画⼯作。通过Interpreter在第⼆阶段的处理,输⼊的符号序列已经变为了 ⼀连串的Op序列,Executor调⽤每个Op的exec()⽅法。⼀些的Op的集合组成⼀个Function,Executor 内维护⼀个Function的列表和⼀个调⽤堆栈callStack。callStack由若⼲个栈帧StackFrame组成,⼀个 StackFrame中存有当前执⾏的Function指针、返回地址、局部变量。
Function就相当于我们平时运⾏程序时的代码段,callStack就是运⾏堆栈。
三、⼀些feature和实现⽅式
3.1 函数调用
LogoCompiler运⾏时的最外层函数是“0global”,之所以起这个名字,是因为它不是⽤户可使⽤的合法 标识符名,“0global”就相当于C语⾔中的main函数。 由于StackFrame和callStack的存在,实际上函数可以⽀持递归,但是由于语法中没有if⽀持,⽆法设置 递归基,因此递归不可⽤。
3.2 变量作⽤域
作⽤域是以Function区分的。Function内的变量会遮蔽外部的变量。
StackFrame中维护了⼀个Variable的数组,是在当前作⽤域中定义的局部变量。当要引⽤⼀个变量的时 候,会优先在当前作⽤域中寻找,若找不到,会在外层作⽤域中继续寻找,若都找不到,则会报错。
3.3 VariableWrapper类
例如MOVE操作可以接受⼀个常数也可以接受⼀个变量,如MOVE 100和MOVE X。为了统⼀两者,我引 ⼊了VariableWrapper类,它可以将Variable和INTCONST字⾯量统⼀起来,并提供getValue()⽅法,在 执⾏时再计算实际的值。这也算是⼀种lazy policy,它还可以配合变量作⽤域,优先查找当前作⽤域中 的同名变量。
3.4 Op类
⼏乎每个Op类都是Executor的友元,可以直接修改其内部变量。
- 每个Op⼦类都重写了exec()函数。
3.5 错误提示
我尽量使LogoCompiler⽀持可读的错误提示。⼀些例⼦如下:
Cannot open the file
Error at line 1: \
Error: The file is empty
Error at line 6: Unexpected symbol: hackhacks
Runtime Error at line 10: END LOOP not found
Error: End of file in function definition, did you miss "END FUNC" for
line()?
Runtime Error at line 11: arguments do not match
Runtime warning: Color value out of range, value larger than 255 will be set
to 255, value smaller than 0 will be set 0
Error at line 19: Unexpected symbol: END
3.6 逆向⽣成logo⽂件
在 testcases/extended/logoGen ⽂件夹中,有⼀个辅助⼯具 logoGenerator.py ,它可以把任意⼀ 张图⽚,转变为合法的logo⽂件。把该logo⽂件作为输⼊,LogoCompiler可以⽣成完全相同的图⽚。 logo⽂件可能很⼤,但是LogoCompiler可以⾼效地执⾏它。
具体⽤法:
python3 logoGenerator.py Lenna.png > Lenna.logo
./logoCompiler Lenna.logo
四、输出展示