Возможно ли в ocamllex определить правило, которое смотрит вперед на следующий символ, не потребляя его?
Я использую ocamllex для написания лексера для языка сценариев, но столкнулся с конфликтом с моим правилом для комментариев.
Я хочу, чтобы аргументы моей команды не заключались в кавычки, если они содержат только буквенно-цифровые символы и косую черту "/". Например:
echo "quoted argument !@#%" /this/second/argument/is/unquoted
Кроме того, одним из моих предварительных требований являются комментарии в стиле C++ с "//"
//this is a comment
echo hello world
Проблема, которую это приносит, это такие вещи, как
echo foo//comment
Я хотел бы, чтобы мой лексер производил токен "foo", оставляя при этом "//" нетронутым для использования в следующий раз, когда я спрашиваю у лексера токен. Это возможно? Причиной этого является то, что возможно, что входной буфер еще не достиг конца комментария, и я бы скорее немедленно вернул токен "foo", чем без необходимости блокировал бы попытку с нетерпением потреблять комментарий.
1 ответ
Ниже приведен небольшой лексер, который соответствует только echo
, заключенные в кавычки и не заключенные в кавычки строки, комментарии и распечатки полученных токенов:
{
type token = NEWLINE | ECHO | QUOTED of string | UNQUOTED of string | COMMENT of string
exception Eof
type state = CODE | LINE_COMMENT
let state = ref CODE
}
let newline = '\n'
let alphanum = [ 'A'-'Z' 'a'-'z' '0'-'9' '_' ]
let comment_line = "//"([^ '\n' ]+)
let space = [ ' ' '\t' ]
let quoted = '"'([^ '"' ]+)'"'
let unquoted = ('/'?(alphanum+'/'?)+)
rule code = parse
space+ { code lexbuf }
| newline { code lexbuf }
| "echo" { ECHO }
| quoted { QUOTED (Lexing.lexeme lexbuf) }
| "//" { line_comment "" lexbuf }
| ('/'|alphanum+) { unquoted (Lexing.lexeme lexbuf) lexbuf }
| eof { raise Eof }
and unquoted buff = parse
newline { UNQUOTED buff }
| "//" { state := LINE_COMMENT; if buff = "" then line_comment "" lexbuf else UNQUOTED buff }
| ('/'|alphanum+) { unquoted (buff ^ Lexing.lexeme lexbuf) lexbuf }
| space+ { UNQUOTED buff }
| eof { raise Eof }
and line_comment buff = parse
newline { state := CODE; COMMENT buff }
| _ { line_comment (buff ^ Lexing.lexeme lexbuf) lexbuf }
{
let lexer lb =
match !state with
CODE -> code lb
| LINE_COMMENT -> line_comment "" lb
let _ =
try
let lexbuf = Lexing.from_channel stdin in
while true do
let () =
match lexer lexbuf with
ECHO -> Printf.printf "ECHO\n"
| QUOTED s -> Printf.printf "QUOTED(%s)\n" s
| UNQUOTED s -> Printf.printf "UNQUOTED(%s)\n" s
| COMMENT s -> Printf.printf "COMMENT(%s)\n" s
| NEWLINE -> Printf.printf "\n"
in flush stdout
done
with Eof -> exit 0
}
Это трюк, который я использовал в своем проекте, чтобы преодолеть то же ограничение в ocamllex (по сравнению с оригинальной программой C lex, которая позволяла сопоставлять шаблоны в "режиме просмотра вперед"). По сути, он разделяет неоднозначные правила по их отдельным радикалам и соответственно переключает лексер на другой парсер. Он также отслеживает текущий используемый анализатор и следующую точку входа.
В вашей ситуации единственные состояния, которые необходимо отслеживать, это состояние по умолчанию (CODE
) и режим комментариев (LINE_COMMENT
). Это может быть расширено для поддержки других государств, если это необходимо.