我正在使用 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_myText
并myText
用一些 internal 来调用它_fslex_state
。
我认为这是问题所在:myText
不是尾递归的,因为它被转换为 2 个相互调用的函数,这在某些时候会导致StackOverflowException
.
所以我的问题真的是:
1)我的假设正确吗?或者 F# 编译器能否像使用尾递归函数一样优化这两个函数并生成一个 while 循环而不是递归调用?(函数式编程对我来说仍然是新事物)
2)我该怎么做才能使函数尾递归/防止StackOverflowException
?FsLex 文档不是那么广泛,我无法弄清楚官方 F# 编译器的不同之处 - 显然它对大字符串没有问题,但它的字符串规则也递归调用自身,所以我在这里不知所措: /