Значение токена зависит от контекста
У меня есть странный строковый синтаксис, где значение разделителя зависит от контекста. В следующем примере ввода:
( (foo) (bar) )
В результате получается список из двух строк ["foo"; "bar"]
, Внешняя пара скобок входит в режим списка. Затем следующая пара скобок отделяет строку. Внутри строк сбалансированные пары скобок должны рассматриваться как часть строки.
Прямо сейчас лексер решает, что возвращать в зависимости от глобальной переменной inside
,
{
open Sample_parser
exception Error of string
let inside = ref false (* <= to be eliminated *)
}
Разделителями являются круглые скобки. Если лексер попадает в открывающую скобку, то
- если
inside
ложно, он испускаетEnter
токен иinside
установлен в true. - Если
inside
true, он переключается на лексер строки, который обрабатывает любую правильно вложенную пару скобок как часть строки. Если уровень вложенности возвращается к нулю, строковый буфер передается анализатору.
Если закрывающая скобка встречается вне строки, Leave
токен испускается и inside
не установлен
Мой вопрос: как мне переписать лексер без глобальной переменнойinside
?
Я использую менгир, но то же самое можно сказать и о 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 ()
1 ответ
Вы можете передать состояние в качестве аргумента tokenize
, Это все еще должно быть изменчивым, но не глобальным.
правило токенизировать внутри = разбирать | ws { tokenize внутри lexbuf } | lpar {если нет! внутри, тогда начинай внутри:= правда; Войти конец еще начало let buf = Buffer.create 20 in String (string_scanner (Lexing.lexeme_start lexbuf) 0 ЬиЕ lexbuf) конец } | rpar { inside:= false; Покидать }
И вы вызываете парсер следующим образом:
Sample_parser.process (Sample_lexer.tokenize (ref false)) buf