1

我正在使用 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要么在_or(whitespace | ...规则中抛出 a。

生成的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用一些 internal 来调用它_fslex_state

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

所以我的问题真的是:

1)我的假设正确吗?或者 F# 编译器能否像使用尾递归函数一样优化这两个函数并生成一个 while 循环而不是递归调用?(函数式编程对我来说仍然是新事物)

2)我该怎么做才能使函数尾递归/防止StackOverflowException?FsLex 文档不是那么广泛,我无法弄清楚官方 F# 编译器的不同之处 - 显然它对大字符串没有问题,但它的字符串规则也递归调用自身,所以我在这里不知所措: /

4

1 回答 1

2

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

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

于 2013-12-18T14:22:49.433 回答