2017-10-12 77 views
0

我正在用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_ppop_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狀態。將不勝感激任何線索。

+0

把解釋器放在詞法分析器裏面有什麼好處? – sepp2k

+0

@ sepp2k只是爲了學習ocamllex。對於Brainfuck,可以用簡單的Ocaml編寫遞歸解析器(我已經完成)。 – PieOhPah

+3

我的意思並不是要聽起來(或者是)相反,但是如果你想學習ocamllex,將它用於其預期目的不會更有意義(也就是說,用它來編寫詞法分析器,而不是整個解釋器)?我甚至不確定你想要做什麼(即在詞法分析器中循環)甚至是可能的,如果可能的話,你會從中學到什麼,在實際項目中使用ocamllex會有用嗎? – sepp2k

回答

3

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實際上可以節省一些工作(不再需要堆棧來跟蹤哪個[屬於哪個])。