【问题标题】:How to return multiple tokens for one fslex rule pattern?如何为一个 fslex 规则模式返回多个令牌?
【发布时间】:2012-12-19 11:50:10
【问题描述】:

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

【问题讨论】:

  • 该死,我似乎总是把这两个混淆 ;-) 我希望这个问题还是有意义的。
  • 这可以解决(虽然不应该,您可能应该重新设计您的词法分析器) - 如果没有其他人这样做,我会在使用舒适的键盘后发布解决方案。

标签: f# fslex


【解决方案1】:

好的,就是这样。

每个词法分析器规则(即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&lt;char&gt; -&gt; Token) -&gt; LexBuffer&lt;char&gt; -&gt; ASTTokenAST 是您的类型,它们没什么特别之处)。

在你的情况下,你想要&lt;name&gt; : LexBuffer&lt;char&gt; -&gt; '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

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

【讨论】:

  • 感谢您的洞察力!我想这基本上就是我所要求的,尽管我确实有感觉(就像你上面提到的那样)我必须修改我的词法分析器逻辑。但我害怕在这一点上卡住。
  • 我曾经写过一两个这样的词法分析器(F# 编译器也会截取标记并对其进行一些修改),但这通常意味着您在一般设计中遗漏了一些东西。顺便说一句,如果你只想解决这个特定的场景,我会发布一个替代方案。
【解决方案2】:

尝试保留语义分析,如“...仅当标识符介于 . 和 (” 之间时,您的词法分析器 (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 必须介于 DOTLPAREN 之间的规则,同时保持词法分析器应保持简单(提供 令牌流,几乎没有强制执行令牌相互之间的有效性)。

【讨论】:

  • 这也是我的想法,但我也想在解析器中使用解析后的 ID 之后的标记。比如DOT OP1 LPAREN ...等等
  • 感谢您的输入 - 您的更新版本看起来与我目前的版本几乎相同。这种方法的问题是,如果解析的 操作令牌 不在 DOTLPAREN 之间,我不能将它们用作 IDs
  • “几乎相同”实际上,是的,但更符合使用 lex 和 yacc 工具时规定的设计模式。稍后我将用将operations 视为IDs 的解决方案来更新我的答案。
  • 实际上,我收回了这一点,对于您所追求的最新规则,我没有一个好的解决方案可以提供。但是现在我再次认为最好的方法是使用我在回答中提供的第一种方法:lex 和 fslex,使用 fsyacc 解析,并在自定义语义分析函数中执行语义分析(即 DOT OP1 LPAREN ... 之类的规则)用于处理解析器生成的 AST。
  • ...尽量让你的语法保持简单,它会很快爆炸并变得难以调试。
【解决方案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

这会将 ID 修复为操作,如果它们被 DOT 和 LPAREN 包围,那么只有这样。

P.S.:我有 3 个单独的匹配项,因为统一匹配需要使用 Lazy&lt;_&gt; 值(这将使其更不可读),或者会在 [DOT; EOF] 序列上失败,因为它会期望额外的第三个令牌。

【讨论】:

  • 再次感谢您的努力。我现在必须回想你在这里所做的事情:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-13
  • 1970-01-01
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 2012-07-12
  • 1970-01-01
相关资源
最近更新 更多