我在执行数组时遇到了困难。
PostScript语言参考手册第50页:
可执行数组或可执行打包数组(过程)对象 如果直接遇到操作数堆栈,则推送操作数堆栈 翻译。如果由于执行某些操作而间接调用它 其他对象(名称或运算符),而是调用它。该 解释器通过在执行堆栈上推送它来调用过程 然后依次执行数组元素。当翻译 到达程序的最后,它会弹出程序对象 执行堆栈。 (实际上,它存在时会弹出过程对象 剩下一个元素,然后推送该元素;这允许 无限深度的“尾递归”而不会溢出执行 栈。)
绿皮书第33页:
PostScript解释器在a处执行一个元素的数组对象 时间,将数组中未使用的部分留在执行堆栈上。该 解释器总是先检查过程体的其余部分 它执行第一个元素。如果程序体是空的,那么 口译员丢弃它。这允许无限深度的尾递归, 因为空过程体不会在执行时累积 堆。 (尾递归意味着递归过程调用是 程序体的最后一个元素(尾部)。)
当我读到两者时,我看到了一点点差异:
实际上,当有一个元素时,它会弹出过程对象 剩下然后推送那个元素;
VS
解释器总是检查过程体的其余部分 在它执行第一个元素之前。如果程序体是空的, 口译员丢弃它。
PLRM:在执行最后一个元素之前从执行堆栈中弹出数组主体并将最后一个元素推送到执行(?)堆栈。
绿皮书:PostScript解释器执行一个元素的数组对象 一次,将数组的未使用部分留在执行上 堆即可。解释器始终在执行第一个元素"之前检查过程主体的其余部分。
对于此代码:
%!PS-Abode-2.0
/F {
findfont exch scalefont setfont
} bind def
24 /Helvetica F
0 0 moveto
(Red Book) show
showpage
当扫描并解释F标记时,您将查找过程体并将其放在执行堆栈上:
{ findfont --exch-- --scalefont-- --setfont--}
-file-
示例1:它是否像这样工作(PLRM):
执行堆栈:
{ findfont --exch-- --scalefont-- --setfont--}
-file-
执行scalefont后,我们位于最后一个元素,所以弹出最后一个元素,弹出过程并推回最后一个元素:
--setfont--
-file-
示例2:或者是这样的(绿皮书):
执行堆栈:
{ findfont --exch-- --scalefont-- --setfont--}
-file-
执行堆栈:
{ --exch-- --scalefont-- --setfont--}
-file-
执行堆栈:
{ --scalefont-- --setfont--}
-file-
执行堆栈:
{ --setfont--}
-file-
执行堆栈:(检查余数,如果为空则弹出最后一个元素,弹出数组并推送最后一个元素:
--setfont--
-file-
我不清楚,因为两者都有不同的解释。绿皮书建议每次都修改(收缩)阵列体。
更新:一些伪代码
示例1:
array[1, 2, 3, 4]
for (int i = 0; i < array.Length; i++)
{
if (i == array.Length -1)
{
exectionstack.Pop() // pop the array
}
Execute(array[i]); // execute element
}
示例2:
//array[1, 2, 3, 4]
do {
var obj = executionstack.Pop();
if (obj is ArrayObject) {
var arr = executionstack.Pop();
var item = arr[0]; // first element
if (arr.Length > 1)
{
var newArray = new Array(arr.Length - 1); // create new array size -1
arr.CopyTo(newArray, 1, arr.Length - 1); // copy remaining elements
executionstack.Push(newArray); // push the remaining array body
}
executionstack.Push(item); // push the item
}
else
{
Execute(obj); // not an array, execute it
}
} while (executionstack.Count > 0)
答案 0 :(得分:1)
ISTM,你的伪代码没有抓住问题的关键。如果你原谅一点讲课,... Postscript像低级语言一样执行,就像汇编语言一样。让我使用你的示例proc并详细介绍。
首先,“何时扫描和解释F令牌”是什么意思?这意味着执行堆栈上有一个文件对象。
op-stack>
exec-stack> --file--
执行循环通过执行以下操作来处理可执行文件:
--file-- exec % push back on exec stack
--file-- token {
xcheck 1 index type /arraytype ne and {
exec
} if
}{
pop
} ifelse
token
返回可执行文件名F,因为它是可执行文件而不是数组,所以它会被推回到exec堆栈上。这留下了这样的堆栈:
op-stack>
exec-stack> --file-- F
执行循环通过执行以下操作来处理可执行文件名:
--name-- load
xcheck {
exec
} if
查找名称。如果值是可执行的,请执行它。这留下了这样的堆栈:
op-stack>
exec-stack> --file-- { findfont --exch-- --scalefont-- --setfont--}
执行循环通过执行以下操作来处理可执行数组:
--array--
dup length 1 gt {
dup 1 1 index 2 sub getinterval exec % push back remainder
} if
dup length 0 gt {
0 get
dup xcheck 1 index type /arraytype ne {
exec
} if
}{
% leave on stack
} ifelse
它使用等效的getinterval
将过程的其余部分的子数组推回到exec堆栈上。但是如果有1个或更少的元素,不要推动剩余部分但是浪费堆栈空间来保存长度为0的数组。这样就会产生这样的堆栈:
op-stack>
exec-stack> --file-- { --exch-- --scalefont-- --setfont--} findfont
这种情况由可执行文件名称处理。
每个处理程序只执行尽可能小的工作,并在exec堆栈上推送事情以便稍后完成。
为了提高效率,您可以非常便宜地制作子阵列非常重要。不要复制数组,而是设计数组类型,这样两个不同的指针可以有不同的长度和不同的起始偏移量。许多运算符(如search
)使用此功能返回参数字符串的子字符串。对于Xpost,我将所有对象打包成8字节的表示。数组对象有4个16位字段:
tag
length
VM-ref
offset
所有对象都有一个16位标记作为第一个字节。标签具有对象类型的位域。因此,忽略范围检查,getinterval
实现如下:
object
getinterval ( object array, uint16_t offset, uint16_t length ){
array.offset = offset;
array.length = length;
return array;
}
超级简单,超级快。没有分配,没有副本。
HTH
要真正回答这个问题,我认为绿皮书和PLRM正在以不同的重点描述同样的事情。 “堆叠机”设计的一个结果是:
所有各种循环操作符只需将exec堆栈上的东西推回并返回,每次处理一小部分作业。这甚至适用于复杂的循环运算符,如image
。就像上面的数组处理程序一样,forall
之类的东西在实现它的函数中没有“循环”。他们只使用一个循环,即解释器的执行循环。
例如
/forall { % arr proc
dup length 0 eq { pop pop }{
/forall cvx exec % comp proc
dup 0 get 3 1 roll % comp[0] proc comp
1 1 index length 1 sub getinterval % comp[0] proc comp[1..n-1]
1 index 2 array astore cvx exec % comp[0] proc
exec % comp[0]
} ifelse
} def
这是从debugger修改的,它模拟了postscript中的内部循环。并重新实现所有循环运算符以使用它。
注意,在这个版本的forall
中, 推送最终的0长尾以进行最后的迭代。这样,proc的最终调用仍然可以使用exit
来退出循环,并且exit
仍然必须在exec堆栈上有一些东西要搜索。
答案 1 :(得分:0)
除非您使用的是1级红皮书(PLRM),否则请记住,绿皮书正在将翻译的不同版本描述为2级或3级PLRM。
如果有疑问,请使用PLRM。