特定类型(数组)的 PostScript 执行

PostScript execution of specific types (Array)

我很难执行数组。

PostScript 语言参考手册第 50 页:

An executable array or executable packed array (procedure) object is pushed on the operand stack if it is encountered directly by the interpreter. If it is invoked indirectly as a result of executing some other object (a name or an operator), it is called instead. The interpreter calls a procedure by pushing it on the execution stack and then executing the array elements in turn. When the interpreter reaches the end of the procedure, it pops the procedure object off the execution stack. (Actually, it pops the procedure object when there is one element remaining and then pushes that element; this permits unlimited depth of “tail recursion” without overflowing the execution stack.)

绿皮书第 33 页:

The PostScript interpreter executes array objects one element at a time, leaving the unused part of the array on the execution stack. The interpreter always checks the remainder of the procedure body before it executes the first element. If the procedure body is empty, the interpreter discards it. This permits infinitely deep tail recursion, since the empty procedure bodies will not accumulate on the execution stack. (Tail recursion means that the recursive procedure call is the very last element (the tail) of the procedure body.)

当我阅读两者时,我看到了细微的差别:

Actually, it pops the procedure object when there is one element remaining and then pushes that element;

对比

The interpreter always checks the remainder of the procedure body before it executes the first element. If the procedure body is empty, the interpreter discards it.

PLRM:在执行最后一个元素之前 你从执行堆栈中弹出数组主体并将最后一个元素推入执行(?)堆栈。

绿皮书:PostScript解释器执行数组对象一个元素 一次,将数组中未使用的部分留在执行中 堆栈。解释器 总是 在执行 first 元素之前检查过程主体的其余部分。

对于此代码:

%!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)

除非您使用的是 1 级红皮书 (PLRM),否则请记住,绿皮书描述的是与 2 级或 3 级 PLRM 不同的解释器化身。

如有疑问,请选择 PLRM。

ISTM,您的伪代码没有抓住问题的关键。恕我直言,... Postscript 的执行就像一种低级语言,就像一种汇编语言。让我使用您的示例过程并详细介绍。

首先,"when the F token is scanned and interpreted"是什么意思?这意味着执行堆栈上有一个文件对象。

  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 returns 可执行文件名 F,因为它是可执行文件而不是数组,所以它被推回可执行堆栈。这使得堆栈像这样:

  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 个或更少的元素,则不要压入余数,但它会浪费堆栈 space 来保存长度为 0 的数组。这使得堆栈像这样:

  op-stack>
exec-stack>  --file--  { --exch-- --scalefont-- --setfont--}  findfont

这种情况由可执行文件名大小写处理。

每个处理程序只做尽可能少的工作,并将事情推送到 exec 堆栈上以供稍后完成。

为了提高效率,可以非常便宜地制造子阵列是非常重要的。不要复制数组,而是设计数组类型,使两个不同的指针可以具有不同的长度和不同的起始偏移量。许多运算符都使用此功能,例如 search 其中 returns 参数字符串的子字符串。对于 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 描述的是相同的东西,但侧重点不同。 "stack-machine" 设计的一个结果是:

  • 只有一个循环

所有各种循环运算符只是将内容压入 exec 堆栈,return,每次只处理一小部分工作。这甚至适用于像 image 这样的复杂循环运算符。所以就像上面的数组处理程序一样, forall 之类的东西在实现它的函数中没有 "loop" 。他们只使用一个循环,解释器的执行循环。

例如

/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 堆栈上搜索一些东西。