我在理解解释器时遇到了很多相同的问题和困惑。我认为 "deferred execution" 这个术语并不是非常有用。此外,我认为 "immediately evaluated" 这个术语也不是非常有用。你不需要一个 DeferredFlag。
这里涉及到两个独立但相关的部分:解释器循环和 token 操作符。
token 处理 "延迟执行" 的一部分,它将可执行数组的所有令牌收集到一个单独的对象中。因此,如果一个文件或字符串以一个过程体开头,则对其调用 token 将产生整个过程体。
{ execution is deferred until the closing }
这看起来像是一条注释,但它实际上是一行后置代码。即使没有定义deferred、closing、the等单词,它也可以无错误地运行。但是如果您在其上调用exec
或将其定义为名称并调用该名称,则它将执行并且内容必须被定义。
解释器循环始终从执行堆栈中获取顶部对象,并且在语义上可执行的数组、文件和字符串都是相同的。解释器将其视为源并获取第一个元素。名称情况有所不同,因为它本质上不是一个源。(我正在引入这个概念,希望它能起到帮助/作用。)以C风格的伪代码为例:
main_loop(){
while( ! quit ){
eval();
}
}
eval(){
object = pop( exec_stack );
if( !executable_flag( object ) ) push( op_stack, object );
else switch( type_of( object ) ){
case array: array_handler( object ); break;
case string: string_handler( object ); break;
case file: file_handler( object ); break;
case name: name_handler( object ); break;
default: push( op_stack, object );
}
}
在名字的情况下,查找名称并在可执行时执行。
name_handler( object ) {
object = load( object );
push( executable_flag( object ) ? exec_stack : op_stack, object );
}
在另外三个地方,你还需要检查它是否为数组。
array_handler( object ){
switch( length( object ){
default:
push( exec_stack, getinterval( object, 1, length( object ) - 1 ) );
case 1:
object = get( object, 0 );
push( executable_flag( object ) && type_of( object ) != array ?
exec_stack : op_stack, object );
case 0:
}
}
只有在executable_flag( object ) && type_of( object ) != array
的情况下,才将其推入exec堆栈。
对于另一个问题,即立即评估名称,我更喜欢称它们为“立即加载”名称。token
运算符在返回之前调用load
。如果在正确的位置处理,很容易处理。它与“延迟执行”部分没有真正的交互。
编辑:
我通过我的调试器对你的示例进行了跟踪。这显示了每个令牌执行后op_stack的运行图像。左侧的元素是token
返回的对象。请注意,token
已经使用了所有的//
。
$ cat test.ps
(db5.ps) run currentfile cvx traceon debug
/foo { 2 3 add } def
foo
% result: 5
/foo { 2 3 add } def
//foo
% result: { 2 3 add }
/foo { 2 3 add } def
/bar { foo } def
bar
% result: 5
/foo { 2 3 add } def
/bar { //foo } def
bar
% result: { 2 3 add }
$ gsnd -DNOSAFER test.ps
GPL Ghostscript 9.19 (2016-03-23)
Copyright (C) 2016 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
%|-
/foo %|- /foo
{2 3 add} %|- /foo {2 3 add}
def %|-
foo %|- 5
/foo %|- 5 /foo
{2 3 add} %|- 5 /foo {2 3 add}
def %|- 5
{2 3 add} %|- 5 {2 3 add}
/foo %|- 5 {2 3 add} /foo
{2 3 add} %|- 5 {2 3 add} /foo {2 3 add}
def %|- 5 {2 3 add}
/bar %|- 5 {2 3 add} /bar
{foo} %|- 5 {2 3 add} /bar {foo}
def %|- 5 {2 3 add}
bar %|- 5 {2 3 add} 5
/foo %|- 5 {2 3 add} 5 /foo
{2 3 add} %|- 5 {2 3 add} 5 /foo {2 3 add}
def %|- 5 {2 3 add} 5
/bar %|- 5 {2 3 add} 5 /bar
{{2 3 add}} %|- 5 {2 3 add} 5 /bar {{2 3 add}}
def %|- 5 {2 3 add} 5
bar GS<4>
GS<4>pstack
{2 3 add}
5
{2 3 add}
5
GS<4>
def
总是采用单个对象作为值,并且立即评估的名称始终从先前的def
生成值,因此立即评估的名称只能返回单个对象。另一方面,所有程序结构都通过token
运算符进入解释器,并且token
始终消耗到}并返回单个对象。如果没有右括号,则token
会发出/syntaxerror信号。因此,在后置脚本中不存在“token {”这样的东西。它永远不能单独存在。 - luser droog