如何在C语言编写的FORTH-like语言解释器中实现循环
我正在用C编写一个简单的基于堆栈的语言,我想知道如何实现某种循环结构和/或前瞻符号。因为这个页面的代码有点长(超过200行),所以我把它放进去了 编辑:主程序位于文件如何在C语言编写的FORTH-like语言解释器中实现循环,c,loops,interpreter,lookahead,forth,C,Loops,Interpreter,Lookahead,Forth,我正在用C编写一个简单的基于堆栈的语言,我想知道如何实现某种循环结构和/或前瞻符号。因为这个页面的代码有点长(超过200行),所以我把它放进去了 编辑:主程序位于文件stack.c中 编辑:代码只需要输入单词,有点像FORTH。它使用scanf并从左到右工作。然后它使用一系列的ifs和strcmps来决定该做什么。确实如此。第四种方法是在数据堆栈旁边添加一个单独的循环堆栈。然后定义使用此循环堆栈的操作。例如: 5 0 DO I . LOOP 将打印 0 1 2 3 4 其工作方式是: DO
stack.c
中
编辑:代码只需要输入
单词
,有点像FORTH。它使用scanf
并从左到右工作。然后它使用一系列的if
s和strcmp
s来决定该做什么。确实如此。第四种方法是在数据堆栈旁边添加一个单独的循环堆栈。然后定义使用此循环堆栈的操作。例如:
5 0 DO I . LOOP
将打印
0 1 2 3 4
其工作方式是:
将索引(0)和控件(5)移到循环堆栈上DO
将循环堆栈的顶部复制到数据堆栈I
增加索引(循环堆栈顶部)。如果索引小于控件(循环堆栈顶部下方的一个),则它将从循环
返回到DO
。如果索引大于等于,则从循环堆栈中弹出索引和控件,控件恢复正常loop
- 您的语言根本不像Forth,所以不要复制Forth的循环结构(仅限于编译,这对您的语言来说是毫无意义的区别)
查看您的代码,添加
直到
作为条件重新启动评估词。也就是说,将它作为普通单词添加到范围
和跳转
旁边,使其弹出堆栈,如果堆栈顶部为true,则将stack.c的cmd
设置回开头
0
dup . 1 + dup 5 > until
.
,将在三次求值中生成输出
0 1 2 3 4 5 6
,并多次重新求值第二行。这假设您在各个求值之间保留状态,并且求值是面向行的。您可以挖掘更多类似的想法。向基于堆栈的语言添加控件结构的明显方法是添加“控件堆栈”。我将描述Postscript机制,因为这是我所知道的,但是Forth有一些类似的行为(当然有细微的差异)
最简单的控制回路是重复
回路。从技术上讲,无限循环
更简单,但使用它需要一个显式的退出
命令,并解释这将使事情复杂化
repeat的语法为:
int proc **repeat** -
因此,当repeat
命令开始时,它希望在操作数堆栈的顶部找到一个过程(这是要执行的循环体),并在该过程的正下方找到一个整数(执行该过程的次数)
由于还有一个执行堆栈(我认为Forth称之为“return”堆栈),因此命令可以通过将其他命令推到执行堆栈上来“执行”其他命令,从而安排在当前命令完成后立即执行其他命令,但在恢复当前命令的调用方之前执行。这是一个很长的句子,但关键的想法就在那里
作为一个具体的例子,假设解释器是从输入流执行的。堆栈看起来像这样:
operand: -empty-
execute: <stdin>
operand: -empty-
execute: <stdin> repeat {(Hello World!\n) print} 1 **{(Hello World!\n) print}**
struct object {
int type;
union {
int i;
void (*command)(context *);
} u;
};
struct dict {
int sz;
struct rec {
char *key;
object val;
} data[]; //think resizable!
};
带引号的(*)过程体被推送到堆栈上
operand: 2
execute: <stdin>
operand: 2 {(Hello World!\n) print}
execute: <stdin>
执行重复过程(一次)会使堆栈如下所示:
operand: -empty-
execute: <stdin>
operand: -empty-
execute: <stdin> repeat {(Hello World!\n) print} 1 **{(Hello World!\n) print}**
struct object {
int type;
union {
int i;
void (*command)(context *);
} u;
};
struct dict {
int sz;
struct rec {
char *key;
object val;
} data[]; //think resizable!
};
这样,每个命令都有自己的功能。好处是:功能小。你应该尽量使你的功能足够小,你可以看到整个东西在屏幕上的同时。一次扫描整个过程可以让你的右脑做一些检测问题区域的工作
然后需要另一种类型来保存命令序列。数组或列表应该可以工作。如果可以定义序列,则可以更轻松地重复序列
这里的好处是你离图灵完成只有一步之遥。序列、迭代、决策(或选择):这就是编写任何可计算函数所需的全部
*。正如评论员所发现的,后记实际上并没有我在描述中使用的引用概念。这里我借用了Lisp术语的概念。Postscript有一个文字/可执行标志,可以设置
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-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
对象或类似对象。对于单函数样式,这是循环操作符本身。这里有一篇博客文章,在我的小TransForth项目中实现了DO/LOOP、BEGIN/UNTIL、WHILE/REPEAT等:
然而,我后来改变了主意,完全依赖递归,没有这样的循环词
希望有帮助,玩得开心 一句忠告:如果代码太大而无法在这里发布,那么我们可能无法仔细研究以帮助您。将问题分解成更小的问题,并将其发布到这里。考虑到我现有的代码,我可以如何表达更小的问题吗?@paxdiablo:它实际上只是一个简单的线程解释器。代码很大,但并不复杂——问题本身实际上已经足够小了代码>。然后,
eval
@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