我正在用C编写一个简单的基于堆栈的语言,并且想知道我应该如何实现某种循环结构和/或超前符号。由于此页面的代码有点长(超过200行),我已将其放入a GitHub repository。
编辑:主程序位于文件stack.c
。
编辑:代码只接受words
中的输入,有点像FORTH。它使用scanf
并从左到右工作。然后,它使用一系列if
和strcmp
来决定要做什么。那就是它。
答案 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
作为条件重启评估字。也就是说,将它作为普通单词添加到range
和jump
旁边,让它弹出堆栈,如果堆栈顶部为真,则将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
我已经改变了主意,完全依赖递归而没有这样的循环词。
希望有所帮助,玩得开心!