F# 如何使FsLex规则尾部递归/防止StackOverflowException

F# 如何使FsLex规则尾部递归/防止StackOverflowException,f#,tail-recursion,fslex,F#,Tail Recursion,Fslex,我正在用fslex/fsyacc实现一种脚本语言,但在大用户输入(即>1k个脚本)时遇到问题,特别是当lexer分析一个非常大的字符串时,会发生此错误 我在自定义lexer函数中扫描字符串(允许用户用反斜杠转义引号之类的字符)。下面是函数: and myText pos builder = parse | '\\' escapable { let char = lexbuf.LexemeChar(1)

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

我在自定义lexer函数中扫描字符串(允许用户用反斜杠转义引号之类的字符)。下面是函数:

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
将在
(空白|…
规则中抛出

生成的
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 
// [...]
(*规则myText*)
和myText pos builder(lexbuf:Microsoft.FSharp.Text.Lexing.LexBuffer)=\u fslex\u myText pos builder 21 lexbuf
// [...] 
和fslex_MyTextPOS builder fslex_Statelexbuf=
匹配表。用
| 0 -> ( 
#105“Lexer.fsl”
让char=lexbuf.LexemeChar(1)在
builder.Append(escapeSpecialTextChar)|>忽略
myText pos builder lexbuf
// [...]
因此,实际规则变为
\u fslex\u myText
,并且
myText
使用一些内部
\u fslex\u状态调用它

我假设这就是问题所在:
myText
不是尾部递归的,因为它被转换成两个相互调用的函数,这在某个点上会导致
StackOverflowException

所以我的问题是:

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


2) 如何使函数尾部递归/防止
StackOverflowException
?FsLex文档并没有那么广泛,我也不知道它是什么,官方的F#编译器做了什么不同的事情——显然它对大字符串没有问题,但是它的字符串规则也递归调用它自己,所以我在这里不知所措:/

,如前所述,您的
myText
函数似乎没有问题——F#编译器应该能够使尾部递归


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

我确实在调试模式下运行,不知道
生成尾部调用
选项。现在成功了,非常感谢!