Длинные строки Lua в fslex

В свободное время я работал над лексером Lua fslex, используя руководство ocamllex в качестве справочного материала.

Я столкнулся с несколькими проблемами, пытаясь правильно токенизировать длинные строки. «Длинные строки» разделены токенами '[' ('=')* '[' и ']' ('=')* ']'; количество знаков = должно быть одинаковым.

В первой реализации лексер, казалось, не распознавал шаблоны [[, создавая две лексемы LBRACKET, несмотря на правило наибольшего совпадения, тогда как [=[ и варианты распознавались правильно. Кроме того, регулярное выражение не могло гарантировать, что используется правильный токен закрытия, останавливаясь при первом захвате ']' ('=')* ']', независимо от фактического «уровня» длинной строки. Кроме того, fslex, похоже, не поддерживает конструкции as в регулярных выражениях.


let lualongstring =    '[' ('=')* '[' ( escapeseq | [^ '\\' '[' ] )* ']' ('=')* ']'

(* ... *)
    | lualongstring    { (* ... *) }
    | '['              { LBRACKET }
    | ']'              { RBRACKET }
(* ... *)


Я пытался решить проблему с другим правилом в лексере:


rule tokenize = parse
    (* ... *)
    | '[' ('=')* '['   { longstring (getLongStringLevel(lexeme lexbuf)) lexbuf }
    (* ... *)

and longstring level = parse 
    | ']' ('=')* ']'   { (* check level, do something *) }
    | _                { (* aggregate other chars *) }

    (* or *)

    | _    {
               let c = lexbuf.LexerChar(0);
               (* ... *)           
           }

Но я застрял по двум причинам: во-первых, я не думаю, что смогу, так сказать, "подтолкнуть" токен к следующему правилу, как только закончу чтение длинной строки; во-вторых, мне не нравится идея чтения char за char, пока не будет найден правильный закрывающий токен, что делает текущий дизайн бесполезным.

Как я могу токенизировать длинные строки Lua в fslex? Спасибо за чтение.


person Raine    schedule 04.12.2010    source источник
comment
Навскидку, просто хотел упомянуть: вы всегда должны разбирать его, а не лексировать.   -  person Brian    schedule 04.12.2010
comment
@ Брайан, можешь уточнить? :) Я немного теряюсь, пытаясь понять, как анализировать последовательность несвязанных токенов, чтобы создать исходную длинную строку, при условии, что лексер может создавать токены для всего содержимого строки. Спасибо за ваш комментарий.   -  person Raine    schedule 04.12.2010
comment
Да, вероятно, это не очень хорошая стратегия, я просто выкинул ее туда.   -  person Brian    schedule 04.12.2010
comment
@Brian, все равно спасибо, я все еще в ладах с F # и fslex, каждая мелочь помогает.   -  person Raine    schedule 04.12.2010
comment
@Raine В любом случае держите нас в курсе; Меня также интересуют как F#, так и Lua.   -  person TechNeilogy    schedule 04.12.2010


Ответы (1)


Извините, если я отвечаю на свой вопрос, но я хотел бы внести свой вклад в решение проблемы для дальнейшего использования.

Я сохраняю состояние вызовов функций лексера с помощью свойства LexBuffer‹_>.BufferLocalStore, которое является просто доступным для записи экземпляром IDictionary.

Примечание: длинные скобки используются как в длинных строках, так и в многострочных комментариях. Эту часть грамматики Lua часто упускают из виду.



let beginlongbracket =    '[' ('=')* '['
let endlongbracket =      ']' ('=')* ']'

rule tokenize = parse
    | beginlongbracket 
    { longstring (longBracketLevel(lexeme lexbuf)) lexbuf }

(* ... *)

and longstring level = parse
    | endlongbracket 
    { if longBracketLevel(lexeme lexbuf) = level then 
          LUASTRING(endLongString(lexbuf)) 
      else 
          longstring level lexbuf 
    }

    | _ 
    { toLongString lexbuf (lexeme lexbuf); longstring level lexbuf }

    | eof 
    { failwith "Unexpected end of file in string." }


Вот функции, которые я использую для упрощения хранения данных в BufferLocalStore:

let longBracketLevel (str : string) =
    str.Count(fun c -> c = '=')

let createLongStringStorage (lexbuf : LexBuffer<_>) =
    let sb = new StringBuilder(1000)
    lexbuf.BufferLocalStore.["longstring"] <- box sb
    sb

let toLongString (lexbuf : LexBuffer<_>) (c : string) =
    let hasString, sb = lexbuf.BufferLocalStore.TryGetValue("longstring")
    let storage = if hasString then (sb :?> StringBuilder) else (createLongStringStorage lexbuf)
    storage.Append(c.[0]) |> ignore

let endLongString (lexbuf : LexBuffer<_>) : string = 
    let hasString, sb = lexbuf.BufferLocalStore.TryGetValue("longstring")
    let ret = if not hasString then "" else (sb :?> StringBuilder).ToString()
    lexbuf.BufferLocalStore.Remove("longstring") |> ignore
    ret

Возможно, он не очень функционален, но, кажется, выполняет свою работу.

  • используйте правило токенизации, пока не будет найдено начало длинной скобки
  • переключитесь на правило длинной строки и выполните цикл, пока не будет найдена закрывающая длинная скобка того же уровня
  • сохранять каждую лексему, которая не соответствует закрывающей длинной скобке того же уровня, в StringBuilder, который, в свою очередь, сохраняется в LexBuffer BufferLocalStore.
  • как только длинная строка закончится, очистите BufferLocalStore.

Изменить: проект можно найти по адресу http://ironlua.codeplex.com. Лексирование и синтаксический анализ должны быть в порядке. Я планирую использовать DLR. Комментарии и конструктивная критика приветствуются.

person Raine    schedule 05.12.2010