Ocaml 更改Lexing.lexbuf的状态

Ocaml 更改Lexing.lexbuf的状态,ocaml,lex,ocamllex,Ocaml,Lex,Ocamllex,我正在用ocamlex编写一个lexer,为了实现它的循环,我需要更改lexbuf的状态,以便它可以返回到流中的前一个位置 脑力操的背景信息(可跳过) 在Brainfuck中,一个循环是由一对方括号和 以下规则: [->继续并评估下一个令牌 ]->如果当前单元格的值不是0,则返回到匹配的[ 因此,以下代码的计算结果为15: +++ [ > +++++ < - ] > . 其他规则工作正常,但似乎忽略了[和]。问题显然在于loopstack以及我如何获取和设置lex\u c

我正在用ocamlex编写一个lexer,为了实现它的循环,我需要更改lexbuf的状态,以便它可以返回到流中的前一个位置

脑力操的背景信息(可跳过)

在Brainfuck中,一个循环是由一对方括号和 以下规则:

  • [
    ->继续并评估下一个令牌
  • ]
    ->如果当前单元格的值不是0,则返回到匹配的
    [
因此,以下代码的计算结果为15:

+++ [ > +++++ < - ] > .

其他规则工作正常,但似乎忽略了
[
]
。问题显然在于
loopstack
以及我如何获取和设置
lex\u curr\u p
状态。如果有任何线索,我将不胜感激。

lex\u curr\u p
的目的是跟踪当前位置,以便您可以在错误消息等中使用它。将其设置为新值不会使lexer实际返回到当前位置文件中较早的位置。事实上,我99%确信,无论你做什么,你都不能使lexer循环像那样

因此,您不能像您正在尝试的那样使用
ocamlex
来实现整个解释器。您可以做的(以及ocamlex的设计目的)是将输入的字符流转换为令牌流

在其他语言中,这意味着将字符流(如
var xyz=/*comment*/123
转换为标记流(如
var,ID(“xyz”)、EQ,INT(123)
),因此词法分析有三种帮助:它找到一个标记的结束和下一个标记的开始,它将标记分类为不同的类型(标识符与关键字等)并丢弃您不需要的令牌(空白和注释)。这可以大大简化进一步的处理

对Brainfuck进行词法分析的帮助要小得多,因为所有Brainfuck标记都只包含一个字符。因此,找出每个标记的结束位置和下一个标记的开始位置是不可行的,找出标记的类型只意味着将字符与[',“+”等。所以脑力操词法分析器做的唯一有用的事情就是丢弃空格和注释

因此,你的词法分析器要做的是将输入
[,[+-.lala comment]>]
转换成类似
循环开始,进入,循环开始,INC,DEC,OUT,循环结束,向右移动,循环结束
,其中
循环开始
等属于你(或你的解析器生成器,如果你使用的话)的一个有区别的联合体定义并生成lexer输出

如果您想使用解析器生成器,您需要在解析器的语法中定义令牌类型,并使lexer生成这些类型的值,然后解析器就可以解析令牌流

如果你想手工解析,你可以在一个循环中手工调用lexer的
token
函数来获取所有的token。为了实现循环,你必须将已经消耗的token存储在某个地方,以便能够进行循环。最终,这将不仅仅是将输入读入字符串,而是为了学习因为我想这没关系


这就是说,我建议您一直使用解析器生成器来创建AST。这样,您就不必为循环创建令牌缓冲区,让AST实际为您节省一些工作(您不再需要堆栈来跟踪哪个
[
属于哪个
]
).

lex\u curr\u p
旨在跟踪当前位置,以便您可以在错误消息等中使用它。将其设置为新值不会使lexer实际查找回文件中的早期位置。事实上,我99%确定,无论您做什么,都无法使lexer像这样循环

因此,您不能像您正在尝试的那样使用
ocamlex
来实现整个解释器。您可以做的(以及ocamlex的设计目的)是将输入的字符流转换为令牌流

在其他语言中,这意味着将字符流(如
var xyz=/*comment*/123
转换为标记流(如
var,ID(“xyz”)、EQ,INT(123)
),因此词法分析有三种帮助:它找到一个标记的结束和下一个标记的开始,它将标记分类为不同的类型(标识符与关键字等)并丢弃您不需要的令牌(空白和注释)。这可以大大简化进一步的处理

对Brainfuck进行词法分析的帮助要小得多,因为所有Brainfuck标记都只包含一个字符。因此,找出每个标记的结束位置和下一个标记的开始位置是不可行的,找出标记的类型只意味着将字符与[',“+”等。所以脑力操词法分析器做的唯一有用的事情就是丢弃空格和注释

因此,你的词法分析器要做的是将输入
[,[+-.lala comment]>]
转换成类似
循环开始,进入,循环开始,INC,DEC,OUT,循环结束,向右移动,循环结束
,其中
循环开始
等属于你(或你的解析器生成器,如果你使用的话)的一个有区别的联合体定义并生成lexer输出

如果您想使用解析器生成器,您需要在解析器的语法中定义令牌类型,并使lexer生成这些类型的值,然后解析器就可以解析令牌流

如果你想手工解析,你可以在一个循环中手工调用lexer的
token
函数来获取所有的token。为了实现循环,你必须将已经消耗的token存储在某个地方,以便能够进行循环。最终,这将不仅仅是将输入读入字符串,而是为了学习因为我想这没关系

也就是说,我建议您一直使用解析器生成器来创建AST。这样您就不必为循环创建令牌缓冲区,使用AST实际上可以节省一些工作(您不需要)
{ (* 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 ... *)
}