Возможно ли в 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). Это может быть расширено для поддержки других государств, если это необходимо.

Другие вопросы по тегам