更改Lexing.lexbuf的状态
我正在用Ocamllex为Brainfuck编写一个词法分析器,为了实现其循环,我需要更改lexbuf的状态,以便它可以返回到流中的前一个位置。上Brainfuck(可跳过)更改Lexing.lexbuf的状态
背景信息
在Brainfuck,环路是通过一对方括号与 完成了以下的规则:
[
- >继续进行,并评估下一个标记]
- >如果当前单元格的值不是0,则返回匹配的[
因此,下面的代码的计算结果为15:
+++ [ > +++++ < - ] > .
记载:
- 在第一小区,分配3(增量3次)
- 输入回路,移动到下一个单元
- 指定5(增量5次)
- 回到第一个ce LL,并从它的值中减去1
- 击中右方括号,现在当前小区(第一)是等于2,从而跳回到
[
并继续进入循环再次- 保持下去,直到所述第一小区是等于0,则退出循环
- 移动到所述第二小区和输出的值与
.
在第二单元中的值将被递增到15 (由5 3次递增)。
问题:
基本上,我写了两个函数来照顾压入和弹出的brainfuck.mll
文件的开头部分的最后[
的最后一个位置即push_curr_p
和pop_last_p
其push和pop该lexbuf的当前位置到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 ... *)
}
其它的规则,工作得很好,b似乎忽略[
和]
。问题显然在loopstack
以及我如何获得并设置lex_curr_p
状态。将不胜感激任何线索。
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
等都属于你(或你的解析器生成,如果你使用一个)中定义并提出了词法分析器输出可识别联合。
如果您想使用解析器生成器,您需要在解析器的语法中定义令牌类型,并让词法分析器生成这些类型的值。然后解析器可以解析令牌流。
如果你想手工解析,你可以在循环中手动调用词法分析器的token
函数来获取所有的令牌。为了实现循环,您必须将已消耗的令牌存储在某处以便能够循环回去。最后,它最终会比将输入读入字符串更多的工作,但对于学习练习,我认为这并不重要。
这就是说,我会建议一路走下去,并使用解析器生成器来创建一个AST。这样你就不必为循环创建一个令牌缓冲区,并且使用AST实际上可以节省一些工作(不再需要堆栈来跟踪哪个[
属于哪个]
)。
把解释器放在词法分析器里面有什么好处? – sepp2k
@ sepp2k只是为了学习ocamllex。对于Brainfuck,可以用简单的Ocaml编写递归解析器(我已经完成)。 – PieOhPah
我的意思并不是要听起来(或者是)相反,但是如果你想学习ocamllex,将它用于其预期目的不会更有意义(也就是说,用它来编写词法分析器,而不是整个解释器)?我甚至不确定你想要做什么(即在词法分析器中循环)甚至是可能的,如果可能的话,你会从中学到什么,在实际项目中使用ocamllex会有用吗? – sepp2k