1

区切り文字の意味がコンテキストに依存する奇妙な文字列構文があります。次のサンプル入力では:

( (foo) (bar) )

結果は 2 つの文字列のリストです["foo"; "bar"]。括弧の外側のペアはリスト モードに入ります。次に、次の括弧のペアが文字列を区切ります。文字列内では、バランスの取れた括弧のペアが文字列の一部として扱われます。

現在、レクサーはグローバル変数に応じて何を返すかを決定しますinside

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

区切り文字は括弧です。レクサーが左括弧にヒットした場合、

  • insidefalseの場合、 Enterトークンを発行し、 trueinsideに設定されます。
  • insidetrue の場合、適切にネストされた括弧のペアを文字列の一部として扱う文字列レクサーに切り替えます。ネスト レベルがゼロに戻ると、文字列バッファーがパーサーに渡されます。

閉じ括弧が文字列の外側にある場合、Leaveトークンが発行され、inside設定が解除されます。

私の質問は次のとおりです。グローバル変数なしでレクサーを書き直すにはどうすればよいですinsideか?

Fwiw 私は menhir を使用していますが、ocamlyacc でも同じことが言えます。(これが混乱しているように聞こえる場合は、申し訳ありません。私は yacc/lex アプローチの初心者です。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 ()
4

1 に答える 1

3

状態を引数としてに渡すことができますtokenize。それでも可変である必要がありますが、グローバルではありません。

ルールtokenizeinside= parse
  | ws{lexbuf内でトークン化}
  | lpar {!insideでない場合は、開始します
              内部:= true;
              入る
            終了その他開始
              buf = Buffer.create 20in
              文字列(string_scanner
                        (Lexing.lexeme_start lexbuf)
                        0
                        buf
                        lexbuf)
            終わり }
  | rpar {inside:= false; 離れる }

そして、次のようにパーサーを呼び出します。

Sample_parser.process(Sample_lexer.tokenize(ref false))buf
于 2012-10-29T18:20:39.707 に答える