如何使FsLex规则尾递归/防止StackOverflowExceptions

时间:2013-12-18 11:52:17

标签: f# tail-recursion fslex

我正在使用fslex / fsyacc实现脚本语言并且遇到大量用户输入的问题(即> 1k脚本),特别是当词法分析器分析一个非常大的字符串时会发生此错误。

我在自定义词法分析器函数中扫描字符串(允许用户使用反斜杠转义字符,如引号)。这是功能:

and myText pos builder = parse 
| '\\' escapable                 { let char = lexbuf.LexemeChar(1)
                                   builder.Append (escapeSpecialTextChar char) |> ignore
                                   myText pos builder lexbuf 
| newline                        { newline lexbuf; 
                                   builder.Append Environment.NewLine |> ignore 
                                   myText pos builder lexbuf 
                                 }
| (whitespace | letter | digit)+ { builder.Append (lexeme lexbuf) |> ignore
                                   myText pos builder lexbuf     
                                 } // scan as many regular characters as possible
| '"'                            { TEXT (builder.ToString())   } // finished reading myText
| eof                            { raise (LexerError (sprintf "The text started at line %d, column %d has not been closed with \"." pos.pos_lnum (pos.pos_cnum - pos.pos_bol))) }
| _                              { builder.Append (lexbuf.LexemeChar 0) |> ignore
                                   myText pos builder lexbuf 
                                 } // read all other characters individually

该函数只解释各种字符,然后递归调用自身 - 或者在读取结束引号时返回读取字符串。当输入过大时,StackOverflowException_规则中会抛出(whitespace | ...

生成的Lexer.fs包含:

(* Rule myText *)
and myText pos builder (lexbuf : Microsoft.FSharp.Text.Lexing.LexBuffer<_>) = _fslex_myText pos builder 21 lexbuf
// [...] 

and _fslex_myText pos builder _fslex_state lexbuf =
  match _fslex_tables.Interpret(_fslex_state,lexbuf) with
  | 0 -> ( 
# 105 "Lexer.fsl"
                                                   let char = lexbuf.LexemeChar(1) in
                                                   builder.Append (escapeSpecialTextChar char) |> ignore
                                                   myText pos builder lexbuf 
// [...]

因此实际规则变为_fslex_myTextmyText使用内部_fslex_state调用它。

我认为这是问题所在:myText不是尾递归的,因为它被转换为2个相互调用的函数,在某些时候它会产生StackOverflowException

所以我的问题确实是:

1)我的假设是正确的吗?或者F#编译器可以优化这两个函数,就像它使用尾递归函数一样,并生成一个while循环而不是递归调用? (功能编程对我来说还是新的)

2)如何使函数尾递归/阻止StackOverflowException? FsLex文档并不是那么广泛,我无法弄清楚官方F#编译器的不同之处 - 显然它对大字符串没有问题,但它的字符串规则也会递归调用自己,所以我在在这里亏损:/

1 个答案:

答案 0 :(得分:2)

如上所述,您的myText函数似乎没问题 - F#编译器应该能够进行尾递归。

要检查一件事 - 您是在Debug还是Release配置中编译/运行此功能?默认情况下,F#编译器仅在Release配置中进行尾调用优化。如果您在调试时需要尾调用,则需要在项目的属性中明确启用它(在Build选项卡上)。