最近,在comp.lang.forth上,我发现了一些代码,由Coos Haak编写,我很难理解。
它应该对括号之间的数字求和或相乘。例如
( 1 2 3 +) ok
. 6 ok
为方便起见,我将在这里重现它:
: (
depth 1+ r> 2>r
;
: cond
depth j >
;
: done
2r> rdrop 2>r
;
: +)
begin cond
while +
repeat
done
;
: *)
begin cond
while *
repeat
done
;
我看到了r> 2>r
和2r> rdrop 2>r
的短语.但是,我对他们在做什么感到困惑。我猜左括号处的堆栈深度以某种方式隐藏在返回堆栈上。但是,我不明白。
这些对返回堆栈有什么作用?
在 Gforth 文档中,我看到:
r> R:w – w core “r-from”
2>r d – R:d core-ext “two-to-r”
2r> R:d – d core-ext “two-r-from”
rdrop R:w – gforth “rdrop”
w Cell, can contain an integer or an address
d double sized signed integer
这与w和d之间的转换有关吗?
2>r
(和 Forth 200x 单词 n>r
)保留了推送到返回堆栈的元素的顺序。 因此,如果您在数据堆栈上有( 1 0 )
,其中 0 作为堆栈的顶部,那么2>r
之后,返回堆栈的顶部将有 0,在它下面有 1。 因此,2>r
是可定义的,而不是
: 2>r ]] >r >r [[ ; immediate
但作为:
: 2>r ]] swap >r >r [[ ; immediate
这些定义是等效的:
: a ]] 0 >r 1 >r [[ ; immediate
: b ]] 0 1 2>r [[ ; immediate
然后,Coos Haak 在该代码中所做的是将一个值滑到返回堆栈顶部下方。 如果他的(
只是将深度推到返回堆栈的顶部,那么在退出这个词时,gforth 会尝试跳到深度作为地址。 如果您尝试以这种方式使用他的话,则会看到相同的错误条件:
: numbers ( 1 2 ;
: sum +) ;
numbers sum
output: :16: error: Invalid memory address
>>>numbers<<< sum
但是,如果(
和+)
与返回堆栈上的第三个元素而不是第二个元素协调,则该代码将起作用(并且正常使用将失败)。
此代码存在一些陷阱:
可以这么说,返回堆栈的普通居民不能保证只占用返回堆栈的一个单元格。
j
的使用依赖于有关j
提取的返回堆栈的精确深度的知识 - 即,它依赖于有关如何实现DO ... LOOP
和相关单词的知识。
这些单词可以作为即时单词移植实现,它们将深度保持在返回堆栈的顶部,但是你不能在定义之外使用它们。 这很简单,可以让它们在任何给定的 Forth 上正常工作。
过早优化的典型示例。2>R 将两个项目移动到返回堆栈,但标准规定了两个项目到达那里的顺序。Coos Haak知道这一点,并利用了它。
将代码替换为等效代码
: (
R> remember return address
depth >R
>R restore return address.
;
现在你看看发生了什么。您希望记住堆栈深度,但如果它在堆栈上,则会干扰计算。因此,您将它放在(代码的返回地址下,稍后将以类似的方式检索。
或者,您可以将其设置为机器代码定义,然后无需担心返回地址。
CODE (
<DEPTH> <to-r>
ENDCODE
其中实际的机器代码作为练习留下。
然而,另一种选择是使用宏,这也不必担心返回堆栈。
: ( POSTPONE DEPTH POSTPONE >R ;
我忽略了 1+ .是一种技术性,因为深度本身将深度改变 1。因此,每当实际使用深度时,您始终必须明智地添加 1-或 1+。