系列文章目录
文章目录
- 系列文章目录
- 前言
- 一、PROCGRAM{ 是什么?
- 二、详细分析PROCGRAM{
- 1. 0 =: main
- 2. @proclist null!
- variable ( – )
- hole ( – p)
- box (x – p)
- 示例
- 3. @proccnt 0!
- 4. @gvarcnt 0!
- 5. { bl word @newproc } : NEWPROC
- @declproc
前言
这次我们将详细讲解PROGRAM{,在上一期我们介绍了 PROC 这次将介绍一下对于fift程序的识别接口。PROCGRAM{
一、PROCGRAM{ 是什么?
在fift源代码编译中,PROGRAM{ 是fift函数的开头文件,他的源代码位于 asm.fif
{ 0 =: main @proclist null! @proccnt 0! @gvarcnt 0!{ bl word @newproc } : NEWPROC{ bl word dup (def?) ' drop ' @newproc cond } : DECLPROC{ bl word dup find{ nip execute <> abort"method redefined with different id" }{ swap 17 @declproc }cond } : DECLMETHOD{ bl word @newglobvar } : DECLGLOBVAR"main" 0 @proclistadddictnew dup @procdict !@procinfo ! 16 0 @procinfo!
} : PROGRAM{
接下来笔者花了一上午的时间整理了所有相关的定义:
{ 0 =: main @proclist null! @proccnt 0! @gvarcnt 0!{ bl word @newproc } : NEWPROC{ bl word dup (def?) ' drop ' @newproc cond } : DECLPROC{ bl word dup find{ nip execute <> abort"method redefined with different id" }{ swap 17 @declproc }cond } : DECLMETHOD{ bl word @newglobvar } : DECLGLOBVAR"main" 0 @proclistadddictnew dup @procdict !@procinfo ! 16 0 @procinfo!
} : PROGRAM{
variable @proccnt
variable @proclist
variable @procinfo
variable @gvarcnt
variable @procdict
19 constant @procdictkeylen{ @proccnt @ 1+ dup @proccnt ! 1 @declproc } : @newproc{ over @procdictkeylen fits not abort"procedure index out of range"over swap dup @procinfo~! 2dup @proclistadd1 'nop does swap 0 (create)
} : @declproc• fits (x y – ?), checks whether Integer x is a signed y-bit integer (i.e.,
whether −2y−1 ≤ x < 2y−1 for 0 ≤ y ≤ 1023), and returns −1 or 0
accordingly{ not 2 pick @ and xor swap ! } : ~!{ pair @proclist @ cons @proclist ! } : @proclistadd{ bl word dup (def?) ' drop ' @newproc cond } : DECLPROCcons (h t – l), constructs a list from its head (first element) h and
its tail (the list consisting of all remaining elements) t. Equivalent to
pair.(create) (e S x – ), creates a new word with the name equal to String S
and definition equal to WordDef e, using flags passed in Integer 0 ≤
x ≤ 3, cf. 4.5. If bit +1 is set in x, creates an active word; if bit +2 is
set in x, creates a prefix word.• (compile) (l x1 . . . xn n e – l′), extends WordList l so that it would
push 0 ≤ n ≤ 255 values x1, . . . , xn into the stack and execute the
execution token e when invoked, where 0 ≤ n ≤ 255 is an Integer. If e
is equal to the special value ’nop, the last step is omitted.• does (x1 . . . xn n e – e′), creates a new execution token e′ that would
push n values x1, . . . , xn into the stack and then execute e. It is roughly
equivalent to a combination of ({), (compile), and (}).{ find 0<> dup ' nip if } : (def?)nip (x y – y), removes the second stack entry from the top, cf. 2.5.
Equivalent to swap dropfind (S – e −1 or e 1 or 0), looks up String S in the dictionary
and returns its definition as a WordDef e if found, followed by −1 for
ordinary words or 1 for active words. Otherwise pushes 0.{ @gvarcnt @ 1+ dup @gvarcnt ! @declglobvar } : @newglobvar{ 1 'nop does swap 0 (create) } : @declglobvar• dictnew ( – D), pushes a Null value that represents a new empty
dictionary.
二、详细分析PROCGRAM{
让我们逐步分析这段代码的执行过程,并解释每一行代码的含义:
1. 0 =: main
这行代码定义了一个标签main
并将其值设置为0
。在某些编程语言中,这可能是用来标记程序入口点的,但在这段代码的上下文中,它可能仅仅是为main
这个词(word)赋了一个初始值。
注意在这个之后,我们可以find到具体的main,因为在fif中,变量是全局的,字典是全局的,如下面所示如果是0 =: main则输出为空,若没有0 =: main 则输出为main 1
variable @proccnt
@proccnt 0!
0 =: main
{ @proccnt @ 1+ dup @proccnt ! } : Dp
{ bl word dup find 0<> dup ' nip if ' drop ' Dp cond } : DD
DD main .s
具体原因就是 cond有没有执行 drop ,if 有没有 nip。
当然上面的程序还可以改写为
variable @proccnt
@proccnt 0!
0 =: main
{ @proccnt @ 1+ dup @proccnt ! } : Dp
{ bl word dup find 0<> ' 2drop ' Dp cond } : DD
DD main .s
使用’ 2drop来代替 dup ’ nip if ’ drop 节省了一个条件推断的操作节省了一些储存的gas,这对于func又是进一步的优化。这也体现了,func 并没有完全优化gas。
2. @proclist null!
首先复习一下盒子box
在堆栈式编程语言中,variable
, hole
, 和 box
是用来创建和管理数据存储位置的操作符。让我们详细分析这些操作符:
variable ( – )
- 用途:定义一个新的变量。
- 操作:读取输入中的空白分隔的单词名
S
,分配一个空的盒子(box),并定义一个新的普通单词S
作为一个常量。当调用这个单词时,它会将这个新盒子的地址推入堆栈。 - 等效操作:等同于
hole constant
。hole
创建一个新的盒子p
,这个盒子不包含任何值。constant
定义一个新的单词,使其成为一个常量,当调用时,它会将一个常数值推入堆栈。- 因此,
hole constant
首先创建一个空盒子,然后将这个盒子的地址定义为一个常量。
hole ( – p)
- 用途:创建一个新的盒子。
- 操作:创建一个新的盒子
p
,这个盒子不包含任何值。 - 等效操作:等同于
null box
。null
是一个表示空值的特殊值。box
创建一个新的盒子,包含指定的值。- 因此,
null box
首先创建一个空盒子,然后将null
值存储在这个盒子里。
box (x – p)
- 用途:创建一个包含指定值的盒子。
- 操作:创建一个新的盒子
p
,并将指定的值x
存储在这个盒子里。 - 等效操作:等同于
hole tuck !
。hole
创建一个新的盒子。tuck
将堆栈顶部的值复制并插入到堆栈的第二位置(即下面一个位置)。!
是赋值操作,将堆栈顶部的值存储到堆栈第二位置的地址指向的位置。- 因此,
hole tuck !
首先创建一个空盒子,然后将堆栈顶部的值复制并插入到堆栈的第二位置,最后将这个值存储到新创建的盒子里。
示例
假设我们要定义一个变量 counter
并初始化为 0
:
-
使用
variable
:variable counter
这会创建一个名为
counter
的新变量,它指向一个空盒子。 -
使用
hole
和constant
:hole constant counter
这会创建一个空盒子,并将这个盒子的地址定义为
counter
。 -
使用
box
:0 box counter !
这会创建一个包含值
0
的盒子,然后将这个盒子的地址赋值给counter
。 -
使用
hole
,tuck
, 和!
:hole tuck 0 ! constant counter
这会创建一个空盒子,将
0
复制并插入到堆栈的第二位置,然后将0
存储到新创建的盒子里,最后将这个盒子的地址定义为counter
。操作符提供了灵活的方式来创建和管理变量和盒子,使得数据存储和访问更加高效和直观。
回到代码中,将@proclist
变量设置为null
,即空值。这意味着初始化了一个空的过程列表。
在这段代码中,null!
是一个操作符,它的作用是将null
值存储到一个盒子(box)中。在Forth语言和一些类似的堆栈式编程语言中,盒子(box)是一种数据结构,用来存储单一的值。这个值可以是任何类型的数据,比如数字、字符串、甚至其他盒子。
让我们分解null!
操作符的组成部分:
-
null
:这是一个特殊的值,通常用来表示“没有值”或者“空值”。在很多编程语言中,null
是一个预定义的关键字,用来表示空指针或者未初始化的变量。 -
!
:这个符号通常表示赋值操作。在堆栈式编程语言中,赋值操作通常涉及到从堆栈中弹出值并将其存储在指定的位置。! (x p – ), stores new value x into Box p. -
(p – )
:这是操作符的堆栈效应(stack effect),它描述了操作符如何影响堆栈。在这个例子中,(p – )
意味着操作符接受一个盒子(表示为p
)作为输入,并在执行后不返回任何值(表示为–
)。换句话说,这个操作符会消耗堆栈顶部的一个盒子,并在其中存储null
值。
现在,让我们看看null!
操作符的等效操作null swap !
:
null
:这是我们要存储的值。swap
:这是一个堆栈操作,它会交换堆栈顶部的两个值。在这个上下文中,如果我们先swap
,那么盒子(p
)就会移动到堆栈的顶部,而null
值就会在下面。!
:这是一个赋值操作,它会将堆栈顶部的值(在swap
操作之后,这就是盒子p
)赋值为堆栈中下一个顶部的值(在swap
操作之后,这就是null
)。
所以,null!
操作符的执行步骤如下:
- 将
null
值压入堆栈。 - 将盒子
p
压入堆栈。 - 执行
swap
操作,这样盒子p
就在堆栈的顶部,而null
值就在下面。 - 执行
!
操作,将盒子p
存储的值更新为null
。
这个操作符的目的是将一个盒子的内容设置为null
,这在初始化数据结构或者清除不再需要的值时非常有用。在堆栈式编程语言中,这种操作符的设计使得值的存储和检索非常直观和高效,因为它们直接与堆栈操作相对应。
3. @proccnt 0!
这行代码将@proccnt
变量设置为0
,即过程计数器初始化为0
。
遇上面的类似,这里继续复习一下 ,如果使用 swap
来实现 @proccnt 0!
的操作,需要确保变量和值的顺序正确,以便执行赋值操作。
0
:这是我们要赋值的值。@proccnt
:这是我们要赋值的目标变量。swap
:这个操作会交换堆栈顶部的两个元素。
在没有 swap
的情况下,赋值操作通常是这样的:
0 @proccnt !
这将 0
压入堆栈,然后 @proccnt
操作将 proccnt
的地址压入堆栈,最后 !
操作弹出这两个值并将 0
赋值给 proccnt
。
使用 swap
,我们需要先压入变量,然后压入值,再使用 swap
来交换它们的位置,最后使用 !
来完成赋值。代码如下:
@proccnt 0 swap !
执行步骤如下:
a. @proccnt
被压入堆栈,此时堆栈顶部是 proccnt
的地址。
b. 0
被压入堆栈,现在堆栈顶部是 0
,下面是一个 proccnt
的地址。
c. swap
被执行,它交换了堆栈顶部的 0
和 proccnt
的地址,现在 proccnt
的地址在顶部,0
在下面。
d. !
被执行,它弹出堆栈顶部的 proccnt
的地址和下面的 0
,然后将 0
赋值给由 proccnt
的地址指向的内存位置。
4. @gvarcnt 0!
这行代码将@gvarcnt
变量设置为0
,即全局变量计数器初始化为0
。
5. { bl word @newproc } : NEWPROC
这行代码定义了一个新的词(word)NEWPROC
,它是一个函数,当调用时会创建一个新的过程(procedure)。bl word
是一个函数或操作,用于接受外面的新的词(word)bl 是32 空格的代码,意味着词语将会从空格分割,而如果是0 word 则不会分割。
而@newproc
是实际创建过程的函数。这里定义了一个名为 @newproc
的新词。让我们逐步分析这个词的定义:
{ @proccnt @ 1+ dup @proccnt ! 1 @declproc } : @newproc
-
@proccnt @
:@
是一个用于变量访问的操作符,它将变量的值推入堆栈。@proccnt
是一个变量,它存储当前定义的过程的数量。- 执行
@proccnt @
会将@proccnt
变量的当前值推入堆栈。
-
1+
:- 这是一个简单的加法操作,它将堆栈顶部的值加一。
- 由于
@proccnt @
已经将@proccnt
的值推入堆栈,1+
将这个值加一,得到新的过程计数。
-
dup
:dup
是复制操作,它会复制堆栈顶部的值并再次推入堆栈。- 执行
dup
后,堆栈顶部将有两个相同的值,都是@proccnt
的新值。
-
@proccnt !
:!
是赋值操作,它将堆栈顶部的值弹出并存储到堆栈第二位置的变量中。@proccnt !
将堆栈顶部的值(@proccnt
的新值)赋值回@proccnt
变量,更新过程计数。
-
1 @declproc
:- 这里
1
是传递给@declproc
的参数,可能表示某种标志或特定的值。 @declproc
是一个词(word),它用于声明一个新的过程。1 @declproc
调用@declproc
并传递1
作为参数,这可能指示@declproc
执行特定的操作,如创建一个新的过程或更新过程列表。
- 这里
-
: @newproc
::
是定义新词的操作符,它将跟随的代码块定义为一个新的词,并将其与词名@newproc
关联。- 执行
@newproc
将执行上述步骤,即增加过程计数,更新@proccnt
变量,并调用@declproc
来声明新的过程。
@newproc
这个词的作用是创建一个新的过程。它首先获取当前的过程计数,将其加一,然后将新值存储回@proccnt
变量。最后,它调用@declproc
来实际声明新的过程,可能还传递了一些额外的参数或标志。这个过程确保了每次调用@newproc
时,都会正确地更新过程计数并声明新的过程。
@declproc
先看前半部分,熟悉 abort"message"
的概念和代码段 over @procdictkeylen fits not abort"procedure index out of range"
,分析错误检查和处理的过程。这也是fift很通用的过程,笔者称之为取反求解。因为abort只有在true时候才被触发。
-
over
:- 这个操作符复制堆栈顶部的值,使得这个值可以在接下来的操作中使用两次。
-
@procdictkeylen
:- 这个变量存储了过程字典键的最大允许长度19。
-
fits
:- 这个操作符检查堆栈顶部的值(复制的过程索引)是否适合在
@procdictkeylen
位内表示的有符号整数范围内。如果适合,返回0
(false);如果不适合,返回-1
(true)。
- 这个操作符检查堆栈顶部的值(复制的过程索引)是否适合在
-
not
:- 如果
fits
返回-1
(true),表示过程索引不适合指定的范围,not
操作将其取反得到0
(false)。 - 如果
fits
返回0
(false),表示过程索引适合指定的范围,not
操作将其取反得到-1
(true)。
- 如果
-
abort"procedure index out of range"
:- 如果
not
操作的结果是-1
(true),即过程索引不适合指定的范围,那么执行abort"procedure index out of range"
。 - 这个操作抛出一个异常,异常信息为 “procedure index out of range”。
- 如果
异常处理
-
抛出异常:
- 当
abort"procedure index out of range"
被执行时,它会抛出一个fift::IntError
异常,异常的值等于指定的错误消息字符串 “procedure index out of range”。
- 当
-
异常表示:
- 在 C++ 中,这个异常由
fift::IntError
类表示,其构造时传入的错误消息就是 “procedure index out of range”。
- 在 C++ 中,这个异常由
-
异常处理:
- 这个异常通常由 Fift 解释器内部处理。
- 解释器会中止所有执行,直到到达顶层环境,并打印出错误消息。
-
错误信息:
- 错误消息包括:
- 源文件名:解释器正在执行的源代码文件的名称。
- 行号:发生错误的代码行号。
- 当前解释的词:发生错误时解释器正在解释的词(word)的名称。
- 指定的错误消息:“procedure index out of range”。
剩下的部分关于@procinfo~!我们会单独做一期。在 Fift 语言中, pair @proclist @ cons @proclist ! 这行代码涉及到几个操作,包括 pair 、 @proclist 、 cons 和 ! 。让我们详细解释每个部分:
- 错误消息包括:
-
@proclist :
这是一个变量,它存储了当前的过程列表。在 Fift 中, @ 符号通常用于访问变量的值。 -
@proclist @ :
这部分代码读取 @proclist 变量的值并将其推入堆栈。这样,堆栈顶部就有了过程列表的值。 -
pair :
pair 是一个操作符,它创建了一个新的单元(cell),其中包含两个值。在 Fift 中,单元(cell)是一种数据结构,可以存储多个值。 -
cons :
cons 是一个操作符,它用于构造一个新的列表元素,通常包含一个头部(head)和一个尾部(tail)。在这里,它可能与 pair 操作符结合使用,以创建一个新的单元,其中包含过程的名称和过程列表。 -
cons @proclist ! :
cons 将新的过程(作为头部)与现有的过程列表(作为尾部)结合起来,形成一个新的列表。
@proclist ! 将这个新构造的列表赋值回 @proclist 变量,更新过程列表。 -
{ bl word dup find { nip execute <> abort"method redefined with different id" } { swap 17 @declproc } cond } : DECLMETHOD
这行代码定义了一个新的词(word)
DECLMETHOD
。它是一个函数,用于声明一个新的方法。它首先复制传入的词,然后在字典中查找这个词。如果找到了,并且执行nip execute <>
的结果不是预期的(即方法已经被定义但ID不同),它会抛出一个错误。如果没有找到,它会使用@declproc
函数和17
这个数字来声明一个新的过程。 -
{ bl word @newglobvar } : DECLGLOBVAR
这行代码定义了一个新的词(word)
DECLGLOBVAR
。它是一个函数,用于声明一个新的全局变量。它使用@newglobvar
函数来创建一个新的全局变量。 -
"main" 0 @proclistadd
这行代码将字符串
"main"
和数字0
作为参数传递给@proclistadd
函数,这个函数将它们添加到过程列表中。这可能是将main
过程添加到过程列表中。 -
dictnew dup @procdict !
这行代码创建了一个新的空字典,并将其复制,然后将复制的字典赋值给
@procdict
变量。这可能是为了初始化一个空的过程字典。 -
@procinfo !
这行代码将
@procinfo
变量设置为null
或空值,可能是为了初始化过程信息。 -
16 0 @procinfo!
这行代码将
@procinfo
变量设置为16 0
,可能是为了初始化过程信息的某个特定部分。 -
} : PROGRAM{
这行代码结束了
PROGRAM{
的定义。PROGRAM{
是一个复合词(word),它包含了初始化编程环境所需的所有步骤,包括设置变量、定义过程和方法的创建规则、声明全局变量等。