Skip to content

Commit a2de8fc

Browse files
committed
[F#] Fix recursion stack overflow when parsing big files
Before this fix, the stack could overflow with ~12000 lines on my machine. Unit test is using 100000 lines without a crash Fixes https://dev.azure.com/devdiv/DevDiv/_workitems/edit/988934
1 parent 5fb62e5 commit a2de8fc

2 files changed

Lines changed: 19 additions & 14 deletions

File tree

main/external/fsharpbinding/MonoDevelop.FSharp.Shared/Lexer.fs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -157,20 +157,16 @@ module Lexer =
157157
parseLine tokenizer (tok::tokens) state
158158
| None, state -> tokens |> List.rev, state
159159

160-
let rec parseLines (sourceTok:FSharpSourceTokenizer) tokens state lines filename defines =
161-
[ match lines with
162-
| line::lines ->
163-
// Create tokenizer & tokenize single line
164-
let tokenizer = sourceTok.CreateLineTokenizer(line)
165-
let tokens, state = parseLine tokenizer [] state
166-
yield tokens, line
167-
// Tokenize the rest of the lines using the new state
168-
yield! parseLines sourceTok tokens state lines filename defines
169-
| [] -> () ]
170-
171-
let getTokensWithInitialState state lines filename defines =
160+
let getTokensWithInitialState initialState lines filename defines =
172161
let sourceTok = FSharpSourceTokenizer(defines, filename)
173-
parseLines sourceTok [] state lines filename defines
162+
let res, _newState =
163+
lines
164+
|> List.mapFold(fun state line ->
165+
let tokenizer = sourceTok.CreateLineTokenizer(line)
166+
let tokens, newState = parseLine tokenizer [] state
167+
(tokens, line), newState
168+
) initialState
169+
res
174170

175171
let findTokenAt col (tokens:FSharpTokenInfo list) =
176172
let isTokenAtOffset col (t:FSharpTokenInfo) = col-1 >= t.LeftColumn && col-1 <= t.RightColumn

main/external/fsharpbinding/MonoDevelop.FSharp.Tests/LexerTests.fs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,13 @@ module LexerTests =
1313
let sourceTok = FSharpSourceTokenizer([], None)
1414
let tokenizer = sourceTok.CreateLineTokenizer line
1515
let tokens, state = Lexer.parseLine tokenizer [] FSharpTokenizerLexState.Initial
16-
Assert.AreNotEqual(state, FSharpTokenizerLexState.Initial)
16+
Assert.AreNotEqual(state, FSharpTokenizerLexState.Initial)
17+
18+
19+
[<Test>]
20+
let ``can parse long file``() =
21+
let lines = [ for i in 1..100000 do
22+
yield sprintf "let x = %i" i ]
23+
let sourceTok = FSharpSourceTokenizer([], None)
24+
let res = Lexer.getTokensWithInitialState FSharpTokenizerLexState.Initial lines (Some "test.fsx") []
25+
()

0 commit comments

Comments
 (0)