2012-10-29 49 views
0

我有一個奇怪的字符串語法,其中 分隔符的含義取決於上下文。在下面的示例 輸入:取決於上下文的令牌含義

((foo) (bar)) 

結果是兩個串["foo"; "bar"]的列表。 外部括號對進入列表模式。 然後,下一對括號分隔字符串。 在內部字符串中,平衡的括號對將被作爲字符串的一部分處理爲 。

現在詞法分析器根據全局變量inside決定返回什麼取決於 。

{ 
    open Sample_parser 
    exception Error of string 
    let inside = ref false (* <= to be eliminated *) 
} 

分隔符是括號。如果詞法分析器擊中 左括號,然後

  • 如果inside,就會發出 Enter令牌和inside設置爲
  • 如果inside是真,它切換到一個字符串的詞法 它把任何適當嵌套對括號 作爲字符串的一部分。如果嵌套級別返回到 零,則字符串緩衝區將傳遞給解析器。

如果一個右括號是一個字符串外遇到 一個Leave令牌被髮射和inside未設置。

我的問題是:如何重寫詞法分析器沒有 全局變量inside

Fwiw我使用menhir,但afaict同樣適用於 ocamlyacc。 (很抱歉,如果這聽起來很困惑,我真的 的YACC /法方法一新手。 我可以表達所有上述不假思索的PEG,但我 還沒有習慣在精神上保持詞法和語法分析器 分離。 隨意的代碼指出的其他問題)

簡單的例子:* sample_lexer.mll *

{ 
    open Sample_parser 
    exception Error of string 
    let inside = ref false (* <= to be eliminated *) 
} 

let lpar = "(" 
let rpar = ")" 
let ws = [' ' '\t' '\n' '\r'] 

rule tokenize = parse 
    | ws { tokenize lexbuf } 
    | lpar { if not !inside then begin 
       inside := true; 
       Enter 
      end else begin 
       let buf = Buffer.create 20 in 
       String (string_scanner 
         (Lexing.lexeme_start lexbuf) 
         0 
         buf 
         lexbuf) 
      end } 
    | rpar { inside := false; Leave } 
and string_scanner init depth buf = parse 
    | rpar { if depth = 0 then begin 
       Buffer.contents buf; 
      end else begin 
       Buffer.add_char buf ')'; 
       string_scanner init (depth - 1) buf lexbuf end } 
    | lpar { Buffer.add_char buf '('; 
      string_scanner init (depth + 1) buf lexbuf } 
    | eof { raise (Error (Printf.sprintf 
          "Unexpected end of file inside string, pos %d--%d]!\n" 
          init 
          (Lexing.lexeme_start lexbuf))) } 
    | _ as chr { Buffer.add_char buf chr; 
       string_scanner init depth buf lexbuf } 

* sample_scanner.mly *:

%token <string> String 
%token Enter 
%token Leave 

%start <string list> process 

%% 

process: 
    | Enter lst = string_list Leave { lst } 

string_list: 
    | elm = element lst = string_list { elm :: lst } 
    | elm = element     { [elm]  } 

element: 
    | str = String { str } 

main.ml

open Batteries 

let sample_input = "((foo (bar) baz) (xyzzy))" 
(*     EibssssssssssssseibssssseiL 
* where E := enter inner 
*  L := leave inner 
*  i := ignore (whitespace) 
*  b := begin string 
*  e := end string 
*  s := part of string 
* 
* desired result: [ "foo (bar) baz"; "xyzzy" ] (type string list) 
*) 

let main() = 
    let buf = Lexing.from_string sample_input in 
    try 
    List.print 
     String.print stdout 
     (Sample_parser.process Sample_lexer.tokenize buf); 
    print_string "\n"; 
    with 
    | Sample_lexer.Error msg -> Printf.eprintf "%s%!" msg 
    | Sample_parser.Error  -> Printf.eprintf 
            "Invalid syntax at pos %d.\n%!" 
            (Lexing.lexeme_start buf) 

let _ = main() 

回答

2

您可以通過國家作爲參數傳遞給tokenize。它仍然必須是可變的,但不是全球性的。

 
rule tokenize inside = parse 
    | ws { tokenize inside lexbuf } 
    | lpar { if not !inside then begin 
       inside := true; 
       Enter 
      end else begin 
       let buf = Buffer.create 20 in 
       String (string_scanner 
         (Lexing.lexeme_start lexbuf) 
         0 
         buf 
         lexbuf) 
      end } 
    | rpar { inside := false; Leave } 

你調用解析器如下:

 
Sample_parser.process (Sample_lexer.tokenize (ref false)) buf 
+0

這確實和設計改進,並回答了這個問題。但是有沒有辦法徹底擺脫指針? –

相關問題