1

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_myTextmyTextinternal でそれを呼び出します_fslex_state

これが問題だと思います。myText相互に呼び出す2つの関数に変換されるため、末尾再帰ではなく、ある時点でStackOverflowException.

だから私の質問は本当にです:

1)私の仮定は正しいですか?または、F# コンパイラは、末尾再帰関数と同様にこれら 2 つの関数を最適化し、再帰呼び出しの代わりに while ループを生成できますか? (関数型プログラミングは私にとってまだ新しいものです)

2)関数を末尾再帰にする/防止するにはどうすればよいStackOverflowExceptionですか?FsLex のドキュメントはそれほど広範ではなく、公式の F# コンパイラが異なる点を理解できませんでした。明らかに、大きな文字列には問題はありませんが、その文字列ルールも再帰的に呼び出すため、ここで途方に暮れています: /

4

1 に答える 1

2

書かれているように、myText関数は問題ないようです。F# コンパイラは末尾再帰を実行できるはずです。

Debug確認すべきことの1つは、これをまたはRelease構成でコンパイル/実行していますか? 既定では、F# コンパイラはRelease構成で末尾呼び出しの最適化のみを行います。デバッグ中に末尾呼び出しが必要な場合は、プロジェクトのプロパティ (Buildタブ) で明示的に有効にする必要があります。

于 2013-12-18T14:22:49.433 に答える