我正在使用Ocamllex为Brainfuck编写词法分析器,为了实现其循环,我需要更改lexbuf的状态,以便它可以返回到流中的先前位置。
关于Brainfuck的背景信息(可跳过)
在Brainfuck中,循环由一对方括号完成 以下规则:
[
- >继续并评估下一个令牌]
- >如果当前单元格的值不为0,则返回匹配的[
因此,以下代码的计算结果为15:
+++ [ > +++++ < - ] > .
它的内容如下:
- 在第一个单元格中,指定3(增量3次)
- 输入循环,移至下一个单元格
- 分配5(增量5次)
- 移回第一个单元格,并从其值
中减去1- 点击结束方括号,现在当前单元格(第一个)等于2,因此跳回
[
并再次进入循环- 继续前进,直到第一个单元格等于0,然后退出循环
- 移至第二个单元格并使用
输出值.
第二个单元格中的值将增加到15 (增加5倍,持续3次)。
问题:
基本上,我编写了两个函数来处理推送和弹出[
文件标题部分中最后一个brainfuck.mll
的最后一个位置,即push_curr_p
和{{1}将lexbuf的当前位置推送并弹出到名为pop_last_p
的{{1}}:
int list ref
其他规则工作得很好,但似乎忽略了loopstack
和{ (* Header *)
let tape = Array.make 100 0
let tape_pos = ref 0
let loopstack = ref []
let push_curr_p (lexbuf: Lexing.lexbuf) =
let p = lexbuf.Lexing.lex_curr_p in
let curr_pos = p.Lexing.pos_cnum in
(* Saving / pushing the position of `[` to loopstack *)
( loopstack := curr_pos :: !loopstack
; lexbuf
)
let pop_last_p (lexbuf: Lx.lexbuf) =
match !loopstack with
| [] -> lexbuf
| hd :: tl ->
(* This is where I attempt to bring lexbuf back *)
( lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_cnum = hd }
; loopstack := tl
; lexbuf
)
}
{ (* Rules *)
rule brainfuck = parse
| '[' { brainfuck (push_curr_p lexbuf) }
| ']' { (* current cell's value must be 0 to exit the loop *)
if tape.(!tape_pos) = 0
then brainfuck lexbuf
(* this needs to bring lexbuf back to the previous `[`
* and proceed with the parsing
*)
else brainfuck (pop_last_p lexbuf)
}
(* ... other rules ... *)
}
。问题显然出现在[
以及如何获得并设置]
状态。会感激任何线索。
答案 0 :(得分:4)
lex_curr_p
用于跟踪当前位置,以便您可以在错误消息等中使用它。将其设置为新值不会使词法分析器实际回寻到文件中的较早位置。事实上,无论你做什么,我都99%肯定你不能像那样制作词法循环。
所以你不能像你想要的那样使用ocamllex
来实现整个解释器。你能做什么(以及ocamllex的设计目的)是将输入的字符流转换为一个标记流。
在其他语言中,这意味着将var xyz = /* comment */ 123
之类的字符流转换为VAR, ID("xyz"), EQ, INT(123)
之类的令牌流。所以lexing有三种方式帮助:它找到一个令牌结束而下一个令牌开始的位置,它将令牌分类为不同的类型(标识符与关键字等)并丢弃您不需要的令牌(空格和注释)。这可以简化进一步处理。
Lexing Brainfuck的帮助并不大,因为所有Brainfuck令牌只包含一个角色。因此,找出每个令牌结束和下一个开始的位置是无操作,找出令牌的类型只是意味着将字符与'[','+'等进行比较。所以Brainfuck词法分析器唯一有用的功能是丢弃空白和评论。
那么你的词法分析器会做的是将输入[,[+-. lala comment ]>]
转换成LOOP_START, IN, LOOP_START, INC, DEC, OUT, LOOP_END, MOVE_RIGHT, LOOP_END
,其中LOOP_START
等属于你(或你的解析器生成器)的区别联合一)定义并制作词法输出。
如果要使用解析器生成器,则需要在解析器的语法中定义标记类型,并使词法分析器生成这些类型的值。然后解析器可以解析令牌流。
如果你想手动解析,你可以手动调用lexer的token
函数来获取所有的标记。为了实现循环,你必须将已经消耗的标记存储在某个地方才能循环回来。最后,除了将输入读入字符串之外,最终还是要做更多的工作,但对于学习练习,我认为这无关紧要。
那就是说,我建议一直使用解析器生成器来创建AST。这样你就不必为循环创建一个令牌缓冲区,并且有一个AST实际上可以节省你一些工作(你不再需要一个堆栈来跟踪哪个[
属于哪个]
)