Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#### 11.4.0 - Unreleased
#### 11.4.1 - 28 August, 2026
* Fix fslex generating an invalid signature file when the header defines a module #240

#### 11.4.0 - 6 July, 2026
* Add Fable support to FsLexYacc.Runtime.
* Make the AssocTable lookup cache initial capacity configurable to avoid pre-allocating 2000 entries per parse #54
* Add `--assoc-cache-capacity` option for fsyacc to set the generated parser's AssocTable cache capacity from the command line #54
Expand Down
21 changes: 13 additions & 8 deletions src/FsLex.Core/fslexdriver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,22 @@ let writeOpens opens (writer: Writer) =
writer.WriteLine ""
writer.WriteLineInterface ""

/// Picks the module declaration and open statements out of the header code, so they can be
/// repeated in the generated signature file. A nested module definition (a `module ... =` line)
/// is not a declaration: its body does not end up in the signature file, so copying it would
/// leave an empty module behind.
let getHeaderDeclarations (code: string) =
code.Split([| '\n'; '\r' |])
|> Array.filter (fun s ->
(s.StartsWith("module ", StringComparison.Ordinal)
&& not (s.TrimEnd().EndsWith("=", StringComparison.Ordinal)))
|| s.StartsWith("open ", StringComparison.Ordinal))

let writeTopCode code (writer: Writer) =
writer.WriteCode code

let moduleAndOpens =
(fst code).Split([| '\n'; '\r' |])
|> Array.filter (fun s ->
s.StartsWith("module ", StringComparison.Ordinal)
|| s.StartsWith("open ", StringComparison.Ordinal))
|> String.concat Environment.NewLine

writer.WriteInterface "%s" moduleAndOpens
for line in getHeaderDeclarations (fst code) do
writer.WriteLineInterface "%s" line

let writeUnicodeTranslationArray dfaNodes domain (writer: Writer) =
let parseContext =
Expand Down
66 changes: 66 additions & 0 deletions tests/FsLex.Core.Tests/DriverTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module FsLex.Core.Tests.DriverTests

open System.IO
open FSharp.Text.Lexing
open FsLexYacc.FsLex.Driver
open Expecto

let private writeTopCodeToInterface (header: string) =
let output = Path.GetTempFileName()
let outputi = String.concat "" [ output; "i" ]

try
using (new Writer(output, outputi)) (writeTopCode (header, Position.Empty))
File.ReadAllText outputi
finally
File.Delete output
File.Delete outputi

[<Tests>]
let tests =
testList "Driver" [
testList "getHeaderDeclarations" [
test "keeps the module declaration and the opens" {
let actual =
getHeaderDeclarations "module Test.Lexer\n\nopen System\nopen System.Text\n\nlet x = 1"

Expect.sequenceEqual
actual
[| "module Test.Lexer"; "open System"; "open System.Text" |]
"Module declaration and opens should be kept"
}

test "skips a nested module definition" {
let actual =
getHeaderDeclarations "open System\nmodule Ranges =\n let isInt8BadMax x = 1 <<< 7 = x"

Expect.sequenceEqual actual [| "open System" |] "A nested module definition should be skipped"
}

test "keeps a module abbreviation" {
let actual = getHeaderDeclarations "module Range = FSharp.Compiler.Text.Range"

Expect.sequenceEqual
actual
[| "module Range = FSharp.Compiler.Text.Range" |]
"A module abbreviation should be kept"
}
]

testList "writeTopCode" [
test "the header in the signature file is newline terminated" {
let actual = writeTopCodeToInterface "module Test.Lexer\nopen System\nlet x = 1"

Expect.equal
actual
(sprintf "module Test.Lexer%sopen System%s" System.Environment.NewLine System.Environment.NewLine)
"Every header line should be written on its own line"
}

test "a header without declarations writes nothing" {
let actual = writeTopCodeToInterface "let x = 1"

Expect.equal actual "" "Nothing should be written when there is no declaration to repeat"
}
]
]
1 change: 1 addition & 0 deletions tests/FsLex.Core.Tests/FsLex.Core.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<Compile Include="UnicodeTests.fs" />
<Compile Include="DriverTests.fs" />
<Compile Include="Main.fs" />
</ItemGroup>

Expand Down
3 changes: 2 additions & 1 deletion tests/JsonLexAndYaccExample/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Lexer.fs
Lexer.fsi
Parser.fs
Parser.fsi
Parser.fsi
3 changes: 2 additions & 1 deletion tests/LexAndYaccMiniProject/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Lexer.fs
Lexer.fsi
Parser.fs
Parser.fsi
test.txt
test.txt