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