F# 如何使FsLex规则尾部递归/防止StackOverflowException
我正在用fslex/fsyacc实现一种脚本语言,但在大用户输入(即>1k个脚本)时遇到问题,特别是当lexer分析一个非常大的字符串时,会发生此错误 我在自定义lexer函数中扫描字符串(允许用户用反斜杠转义引号之类的字符)。下面是函数: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)
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
选项卡上)显式启用尾部调用。我确实在调试模式下运行,不知道生成尾部调用
选项。现在成功了,非常感谢!