4

使用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 还很陌生,所以我很高兴看到正确方向的任何指示。

4

3 回答 3

4

好的,就是这样。

每个词法分析器规则(即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> -> ASTToken并且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

这只是保存您的词法分析器提供的标记,并将它们一一提供给解析器(并根据需要生成更多标记)。

于 2012-12-19T14:22:13.287 回答
3

尝试保留语义分析,例如“...仅当标识符介于 . 和 (” 之间时,您的词法分析器 (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 { ... }

因此,您在解析器中强制执行了IDs 和operations 必须位于 aDOT和 a之间的规则,LPAREN同时保持您的词法分析器应有的简单(提供一个标记,几乎没有强制执行相关标记的有效性的方式对彼此)。

于 2012-12-19T14:21:14.627 回答
2

(这是一个单独的答案)

对于这种特定情况,这可能会更好地解决您的问题:

...

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

如果 ID 被 DOT 和 LPAREN 包围,这将修复 ID 为操作,并且只有这样。

P.S.: I have 3 separate matches, because a unified match would require either using Lazy<_> values (which will make it even less readable), or will fail on a sequence of [DOT; EOF], because it'd expect an additional third token.

于 2012-12-19T15:58:48.637 回答