改变Lexing.lexbuf的状态

时间:2017-10-12 19:43:48

标签: ocaml lex ocamllex

我正在使用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 ... *) } 。问题显然出现在[以及如何获得并设置]状态。会感激任何线索。

1 个答案:

答案 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实际上可以节省你一些工作(你不再需要一个堆栈来跟踪哪个[属于哪个]