1

我有一个奇怪的字符串语法,其中分隔符的含义取决于上下文。在以下示例输入中:

( (foo) (bar) )

结果是两个字符串的列表["foo"; "bar"]。外圆括号进入列表模式。然后,下一对括号分隔字符串。在字符串内部,平衡的括号对将被视为字符串的一部分。

现在,词法分析器根据全局变量决定返回什么inside

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

分隔符是括号。如果词法分析器遇到左括号,则

  • 如果insidefalse,它会发出一个 Enter令牌并inside设置为true
  • 如果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 }

主要.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. 它仍然必须是可变的,但不是全局的。

rule tokenize inside = 解析
  | ws { 在 lexbuf 中标记化 }
  | lpar { 如果不是 !inside 则开始
              里面:=真;
              进入
            结束否则开始
              让 buf = Buffer.create 20 in
              字符串(string_scanner
                        (Lexing.lexeme_start lexbuf)
                        0
                        缓冲区
                        lexbuf)
            结尾 }
  | rpar { 里面 := false; 离开 }

你调用解析器如下:

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