如何在用C编写的类似FORTH的语言解释器中实现LOOP

时间:2011-08-04 22:34:08

标签: c loops interpreter lookahead forth

我正在用C编写一个简单的基于堆栈的语言,并且想知道我应该如何实现某种循环结构和/或超前符号。由于此页面的代码有点长(超过200行),我已将其放入a GitHub repository

编辑:主程序位于文件stack.c

编辑:代码只接受words中的输入,有点像FORTH。它使用scanf并从左到右工作。然后,它使用一系列ifstrcmp来决定要做什么。那就是它。

4 个答案:

答案 0 :(得分:10)

Forth方法是在数据堆栈旁边添加一个单独的循环堆栈。然后定义使用此循环堆栈的操作。例如:

5 0 DO I . LOOP

将打印

0 1 2 3 4

这种方式的工作原理是:

  • DO将索引(0)和控件(5)移动到循环堆栈。
  • I将循环堆栈的顶部复制到数据堆栈。
  • LOOP递增索引(循环堆栈顶部)。如果索引小于控件(低于循环堆栈顶部的索引),则它会将DO的命令重新运行回LOOP。如果索引是> =,那么它会从循环堆栈中弹出索引和控件,并且控制将恢复正常。

答案 1 :(得分:6)

将控制结构添加到基于堆栈的语言的显而易见的方法是添加“控制堆栈”。我将描述Postscript机制,因为这就是我所知道的,但Forth有一些类似的行为(确切地说有微妙的差异)。

最简单的受控循环是repeat循环。从技术上讲,无限loop更简单,但要使用它需要一个明确的exit命令,并解释这会使事情复杂化。

repeat的语法是:

int proc  **repeat**  -

因此,当repeat命令开始时,它期望在操作数堆栈的顶部找到一个过程(这是要执行的循环体),并且在过程的正下方有一个整数(的次数到执行程序)。

由于还有一个执行堆栈(我认为Forth称之为“返回”堆栈),命令可以通过在执行堆栈上按下它们来“执行”其他命令,从而安排其他命令在当前执行后立即执行命令已完成,但在恢复当前命令的调用者之前。这是一个很长的句子,但关键的想法就在那里。

作为一个具体示例,假设解释器正在从输入流执行。堆栈看起来像这样:

operand: -empty-
execute: <stdin>

用户输入2 {(Hello World!\n) print} repeat

整数2被推入堆栈。

operand: 2
execute: <stdin>

引用的(*)过程体被推入堆栈。

operand: 2 {(Hello World!\n) print}
execute: <stdin>

执行repeat命令。

operand: 2 {(Hello World!\n) print}
execute: <stdin> repeat

Repeat: expects operands: int proc
    if int<0 Error
    if int==0 return //do nothing
    push `repeat` itself on exec stack
    push quoted proc on exec stack
    push int-1 on exec stack
    push "executable" proc on exec stack

执行重复过程(一次)会像这样离开堆栈:

operand: -empty-
execute: <stdin> repeat {(Hello World!\n) print} 1 **{(Hello World!\n) print}**

解释器在exec堆栈的顶部执行过程,打印“Hello World!”,然后找到一个整数,它将其推送到op堆栈。

operand: 1
execute: <stdin> repeat {(Hello World!\n) print}

解释器通过按下op栈来执行引用的过程。

operand: 1 {(Hello World!\n) print}
execute: <stdin> repeat

我们回到了开始!为下一次迭代做好准备(如果整数已降为零,则终止)。

希望这有帮助。

修改;

在实际查看你的代码之后,我有一个不同的建议,也许是我上面描述的类似的跳板。我认为你应该暂时忘记代码并专注于数据结构。你有一个很好的变量表,但是所有命令都是通过代码分散的嵌入式文字!

如果使表保持变量记录类型,则可以对两者使用相同的查找机制(甚至可以移动到散列或三元搜索树(我当前最喜欢的))。我想到这样的事情:

struct object {
    int type;
    union {
        int i;
        void (*command)(context *);
    } u;
};

struct dict {
    int sz;
    struct rec {
        char *key;
        object val;
    }  data[]; //think resizable!
};

这样每个命令都有自己的功能。奖金是:小功能。您应该尝试使您的功能足够小,以便您可以同时在屏幕上看到整个事物。一次扫描整个事物可以让你的右脑做一些检测问题区域的工作。

然后你需要另一种类型来保存命令序列。数组或列表应该有效。当您可以定义序列时,您可以更轻松地重复序列。

这里的奖励是你只有一个远离图灵完成的结构。序列,迭代,决策(或选择):这就是编写任何可计算函数所需的全部内容!


*。正如评论员发现的那样,Postscript实际上并没有我在描述中使用的引用的相同概念。在这里,我借用了Lisp术语中的概念。 Postscript有一个 literal / executable 标志,可以设置cvx cvlit或测试xcheck。将执行执行堆栈上的可执行数组。因此,对于“引用”过程体,它实际上是文字过程体(即数组)。因此,repeat还必须在下一次迭代之前执行对cvx的调用。因此,repeat的postscript实现的更正确的伪代码是:

Repeat: expects operands: int proc
    if int<0 Error
    if int==0 return //do nothing
    push `repeat` itself on exec stack
    push 'cvx' on the exec stack
    push cvlit(proc) on exec stack
    push int-1 on exec stack
    push "executable" proc on exec stack

这将执行必要的标记 - 在执行堆栈上将过程作为数据传递。

我看到实现这种控制结构的另一种方式是使用两个函数repeat相同的入口点,以及一个理论上不需要名称的内部延续运算符。我认为ghostscript称之为@ repeat-continue @。使用单独的continue函数,您不必非常小心堆栈中的东西顺序,并且您不需要旋转 literal 标志。相反,您可以在exec堆栈上的递归调用中存储一些持久数据 ;但你也要清理它。

因此,替代repeat将是:

int proc  **repeat**  -
    if int<0 Error
    if int==0 return //do nothing
    push null on exec stack   <--- this marks our "frame"
    push int-1 on exec stack
    push proc on exec stack
    push '@repeat-continue' on exec stack
    push executable proc on exec stack

延续也有一个更简单的工作。

@repeat-continue
    peek proc from exec stack
    peek int from exec stack
    if int==0 clear-to-null and return
    push '@repeat-continue' on exec stack
    push executable proc on exec stack

最后,exit运算符与循环密切相关,它将exec堆栈清除到循环运算符的“框架”。对于双功能样式,这是null对象或类似对象。对于单功能样式,这是循环运算符本身。

答案 2 :(得分:2)

你的语言根本不像Forth,所以不要复制Forth(仅限编译 - 对你的语言来说毫无意义的区别)循环结构。

查看代码,添加until作为条件重启评估字。也就是说,将它作为普通单词添加到rangejump旁边,让它弹出堆栈,如果堆栈顶部为真,则将stack.c的cmd设置回一开始。

0
dup . 1 + dup 5 > until
.

,在您的语言中,将在三次评估中生成输出0 1 2 3 4 5 6,并多次重新评估第二行。这假设您在整个评估期间保留状态,并且该评估是面向行的。你可以挖掘LSE64以获得更多这样的想法。

答案 3 :(得分:1)

这是一篇博文,其中DO / LOOP,BEGIN / UNTIL,WHILE / REPEAT等在我的小型TransForth项目中得到了肯定:http://blogs.msdn.com/b/ashleyf/archive/2011/02/06/loopty-do-i-loop.aspx

我已经改变了主意,完全依赖递归而没有这样的循环词。

希望有所帮助,玩得开心!