如何使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
函数只是解释各种字符,然后递归地调用它自己 - 或者在读取结束引号时返回读取字符串。 当输入过大时,将在_
或(whitespace | ...
规则中引发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
// [...]
所以实际的规则变成_fslex_myText
, myText
用一些内部的_fslex_state
调用它。
我认为这是问题所在: myText
不是尾递归的,因为它被转换为两个互相调用的函数,这些函数在某些时候会导致StackOverflowException
。
所以我的问题确实是:
1)我的假设是否正确? 或者,F#编译器是否可以像使用尾递归一样优化这两个函数,并生成一个while循环而不是递归调用? (函数式编程对我来说仍然是新的)
2)我能做些什么来实现函数尾递归/防止StackOverflowException
? FsLex文档并不是那么广泛,我不知道官方的F#编译器做了什么不同的事情 - 显然它对大字符串没有问题,但是它的字符串规则也会递归地调用它自己,所以我在这里感到茫然: /
正如所写的,你的myText
函数似乎没问题--F#编译器应该能够进行尾递归。
有一件事要检查 - 你是在Debug
还是Release
配置中编译/运行它? 默认情况下,F#编译器仅在Release
配置中执行tail-call优化。 如果你在调试的时候需要tail-calls,你需要在项目的属性中(在Build
选项卡上)明确地启用它。