fslex / fsyacc を使用してスクリプト言語を実装していますが、大規模なユーザー入力 (1,000 以上のスクリプト) に問題があります。特に、このエラーは、レクサーが非常に大きな文字列を分析するときに発生します。
カスタム レクサー関数で文字列をスキャンします (ユーザーがバックスラッシュで引用符などの文字をエスケープできるようにするため)。関数は次のとおりです。
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
この関数はさまざまな文字を解釈し、再帰的に自分自身を呼び出します。または、閉じ引用符を読み取ると、読み取った文字列を返します。入力が大きすぎる場合、 orルールで aStackOverflowException
がスローされます。_
(whitespace | ...
生成さ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# コンパイラは、末尾再帰関数と同様にこれら 2 つの関数を最適化し、再帰呼び出しの代わりに while ループを生成できますか? (関数型プログラミングは私にとってまだ新しいものです)
2)関数を末尾再帰にする/防止するにはどうすればよいStackOverflowException
ですか?FsLex のドキュメントはそれほど広範ではなく、公式の F# コンパイラが異なる点を理解できませんでした。明らかに、大きな文字列には問題はありませんが、その文字列ルールも再帰的に呼び出すため、ここで途方に暮れています: /