Как вернуть несколько токенов для одного шаблона правил fslex?

Используя fslex, я хотел бы вернуть несколько токенов для одного шаблона, но я не вижу способа, как это сделать. Даже использование другой функции правила, которая возвращает несколько токенов, подойдет мне.

Я пытаюсь использовать что-то вроде этого:

let identifier = [ 'a'-'z' 'A'-'Z' ]+

// ...

rule tokenize = parse
// ...
| '.' identifier '(' { let value = lexeme lexbuf
                       match operations.TryFind(value) with
                      // TODO: here is the problem:
                      // I would like to return like [DOT; op; LPAREN]
                      | Some op -> op
                      | None    -> ID(value) }

| identifier         { ID (lexeme lexbuf) }
// ...

Проблема, которую я пытаюсь решить, состоит в том, чтобы сопоставить предопределенные токены (см.: operations карта), только если identifier находится между . а также (, В противном случае матч должен быть возвращен как ID,

Я довольно новичок в fslex, поэтому я рад любым указателям в правильном направлении.

3 ответа

Решение

(Это отдельный ответ)

В этом конкретном случае это может решить вашу проблему лучше:

...

rule tokenize = parse
...
| '.' { DOT }
| '(' { LPAREN }
| identifier { ID (lexeme lexbuf) }

...

И использование:

let parse'' text =
    let lexbuf = LexBuffer<char>.FromString text
    let rec tokenize =
        let stack = ref []
        fun lexbuf ->
        if List.isEmpty !stack then
            stack := [Lexer.tokenize lexbuf]
        let (token :: stack') = !stack // can never get match failure,
                                        // else the while wouldn't have exited
        stack := stack'
        // this match fixes the ID to an OP, if necessary
        // multiple matches (and not a unified large one),
              // else EOF may cause issues - this is quite important
        match token with
        | DOT ->
          match tokenize lexbuf with
          | ID id ->
            match tokenize lexbuf with
            | LPAREN ->
              let op = findOp id
              stack := op :: LPAREN :: !stack
            | t -> stack := ID id :: t :: !stack
          | t -> stack := t :: !stack
        | _ -> ()
        token
    Parser.start tokenize lexbuf

Это установит идентификаторы для операций, если они окружены точками DOT и LPAREN, и только тогда.

PS: у меня есть 3 отдельных матча, потому что унифицированный матч потребует либо использования Lazy<_> значения (что сделает его еще менее читаемым), или не будет на последовательности [DOT; EOF]потому что это ожидало бы дополнительный третий токен.

Хорошо, вот оно.

Каждое правило лексера (т.е. rule <name> = parse .. cases ..) определил функцию <name> : LexBuffer<char> -> 'a, где 'a может быть любого типа. Обычно вы возвращаете токены (возможно, определенные для вас с помощью FsYacc), поэтому вы можете анализировать текст следующим образом:

let parse text =
    let lexbuf = LexBuffer<char>.FromString text
    Parser.start Lexer.tokenize lexbuf

куда Parser.start это функция синтаксического анализа (из вашего файла FsYacc), типа (LexBuffer<char> -> Token) -> LexBuffer<char> -> AST (Token а также AST Ваши типы, ничего особенного в них нет).

В вашем случае вы хотите <name> : LexBuffer<char> -> 'a listИтак, все, что вам нужно сделать, это:

let parse' text =
    let lexbuf = LexBuffer<char>.FromString text
    let tokenize =
        let stack = ref []
        fun lexbuf ->
        while List.isEmpty !stack do
            stack := Lexer.tokenize lexbuf
        let (token :: stack') = !stack // can never get match failure,
                                        // else the while wouldn't have exited
        stack := stack'
        token
    Parser.start tokenize lexbuf

Это просто сохраняет токены, поставляемые вашим лексером, и передает их анализатору один за другим (и генерирует больше токенов по мере необходимости).

Попытайтесь сохранить семантический анализ как "... только если идентификатор находится между. И (" вне вашего лексера (fslex), и вместо этого сохраните его для вашего синтаксического анализатора (fsyacc). Т.е. одним из вариантов будет сохранение вашего лексера в неведении operations:

let identifier = [ 'a'-'z' 'A'-'Z' ]+    
// ...
rule tokenize = parse
// ...
| '.' { DOT }
| '(' { LPAREN }
| identifier { ID (lexeme lexbuf) }
// ...

а затем в fsyacc решите проблему с помощью правила вроде:

| DOT ID LPAREN { match operations.TryFind($2) with
                  | Some op -> Ast.Op(op)
                  | None    -> Ast.Id($2) }

ОБНОВЛЕНИЕ в ответ на комментарий:

Возможно следующее тогда в вашем лексере:

let identifier = [ 'a'-'z' 'A'-'Z' ]+   
let operations =
  [
    "op1", OP1
    "op2", OP2
    //...
  ] |> Map.ofList 

// ...
rule tokenize = parse
// ...
| '.' { DOT }
| '(' { LPAREN }
| identifier 
  { 
    let input = lexeme lexbuf
    match keywords |> Map.tryFind input with
    | Some(token) -> token
    | None -> ID(input) 
  }
// ...

и в вашем парсере:

| DOT ID LPAREN { ... }
| DOT OP1 LPAREN { ... }
| DOT OP2 LPAREN { ... }

Таким образом, вы применили правило, которое IDс и operationдолжно быть между DOT и LPAREN в вашем синтаксическом анализаторе, сохраняя при этом лексер простым, как и должно быть (для предоставления потока токенов, с минимальными усилиями по обеспечению достоверности токенов по отношению друг к другу).

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