Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some performance optimizations #107

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
69 changes: 38 additions & 31 deletions src/ExcelProvider.DesignTime/ExcelProvider.DesignTime.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,72 @@ module internal Helpers =

// Active patterns & operators for parsing strings
let (@?) (s: string) i =
if i >= s.Length then None else Some s.[i]
if i >= s.Length then ValueNone else ValueSome s.[i]

let inline satisfies predicate (charOption: option<char>) =
let inline satisfies predicate (charOption: voption<char>) =
match charOption with
| Some c when predicate c -> charOption
| _ -> None
| ValueSome c when predicate c -> charOption
| _ -> ValueNone

[<return: Struct>]
let (|EOF|_|) =
function
| Some _ -> None
| _ -> Some()
| ValueSome _ -> ValueNone
| _ -> ValueSome()

[<return: Struct>]
let (|LetterDigit|_|) = satisfies Char.IsLetterOrDigit
[<return: Struct>]
let (|Upper|_|) = satisfies Char.IsUpper
[<return: Struct>]
let (|Lower|_|) = satisfies Char.IsLower

let inline forall predicate (source : ReadOnlySpan<_>) =
let mutable state = true
let mutable e = source.GetEnumerator()
while state && e.MoveNext() do
state <- predicate e.Current
state

/// Turns a string into a nice PascalCase identifier
let niceName (set: System.Collections.Generic.HashSet<_>) (s: string) =
if s = s.ToUpper() then
s
else
// Starting to parse a new segment
let rec restart i =
seq {
match s @? i with
| EOF -> ()
| LetterDigit _ & Upper _ -> yield! upperStart i (i + 1)
| LetterDigit _ -> yield! consume i false (i + 1)
| _ -> yield! restart (i + 1)
}
match s @? i with
| EOF -> Seq.empty
| LetterDigit _ & Upper _ -> upperStart i (i + 1)
| LetterDigit _ -> consume i false (i + 1)
| _ -> restart (i + 1)

// Parsed first upper case letter, continue either all lower or all upper
and upperStart from i =
seq {
match s @? i with
| Upper _ -> yield! consume from true (i + 1)
| Lower _ -> yield! consume from false (i + 1)
| _ -> yield! restart (i + 1)
}
match s @? i with
| Upper _ -> consume from true (i + 1)
| Lower _ -> consume from false (i + 1)
| _ -> restart (i + 1)

// Consume are letters of the same kind (either all lower or all upper)
and consume from takeUpper i =
seq {
match s @? i with
| Lower _ when not takeUpper -> yield! consume from takeUpper (i + 1)
| Upper _ when takeUpper -> yield! consume from takeUpper (i + 1)
| Lower _ when not takeUpper -> consume from takeUpper (i + 1)
| Upper _ when takeUpper -> consume from takeUpper (i + 1)
| _ ->
yield from, i
yield! restart i
}
seq {
yield struct(from, i)
yield! restart i
}

// Split string into segments and turn them to PascalCase
let mutable name =
seq {
for i1, i2 in restart 0 do
let sub = s.Substring(i1, i2 - i1)
let sub = s.AsSpan(i1, i2 - i1)

if Seq.forall Char.IsLetterOrDigit sub then
yield sub.[0].ToString().ToUpper() + sub.ToLower().Substring(1)
if forall Char.IsLetterOrDigit sub then
yield Char.ToUpper(sub.[0]).ToString() + sub.Slice(1).ToString().ToLower()
}
|> String.concat ""

Expand Down Expand Up @@ -334,9 +341,9 @@ type public ExcelProvider(cfg: TypeProviderConfig) as this =

let key = (filename, sheetname, range, hasheaders, forcestring)

if dict.ContainsKey(key) then
dict.[key]
else
match dict.TryGetValue key with
| true, kv -> kv
| false, _ ->
let res = ProvidedTypeDefinitionExcelCall key
dict.[key] <- res
res
Expand Down
24 changes: 18 additions & 6 deletions src/ExcelProvider.Runtime/ExcelProvider.Runtime.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace FSharp.Interop.Excel

[<Struct>]
type ExcelFormat =
| Xlsx
| Csv
Expand All @@ -18,6 +19,7 @@ open FSharp.Interop.Excel
[<AutoOpen>]
module internal ExcelAddressing =

[<Struct>]
type Address =
{ Sheet: string; Row: int; Column: int }

Expand Down Expand Up @@ -139,7 +141,7 @@ module internal ExcelAddressing =
let workSheetName =
if worksheets.Contains sheetname then
sheetname
else if sheetname = null || sheetname = "" then
elif isNull sheetname || sheetname = "" then
worksheets.[0].TableName //accept TypeProvider without specific SheetName...
else
failwithf "ExcelProvider: Sheet [%s] does not exist." sheetname
Expand All @@ -157,7 +159,7 @@ module internal ExcelAddressing =
|> Seq.toList

let rangeViewsByColumn =
ranges |> Seq.map rangeViewOffsetRecord |> Seq.concat |> Seq.toList
ranges |> Seq.collect rangeViewOffsetRecord |> Seq.toList

if rangeViewsByColumn |> Seq.distinctBy fst |> Seq.length < rangeViewsByColumn.Length then
failwith "ExcelProvider: Ranges cannot overlap"
Expand Down Expand Up @@ -197,7 +199,10 @@ module internal ExcelAddressing =
#endif
let fail action (ex: exn) =
let exceptionTypeName = ex.GetType().Name
let message = sprintf "ExcelProvider: Could not %s. %s - %s" action exceptionTypeName (ex.Message)

let message =
sprintf "ExcelProvider: Could not %s. %s - %s" action exceptionTypeName (ex.Message)

failwith message

use stream =
Expand All @@ -220,7 +225,10 @@ module internal ExcelAddressing =
ExcelDataReader.ExcelReaderFactory.CreateBinaryReader(stream)

if reader.IsClosed then
fail action (Exception "ExcelProvider: The reader was closed on startup without raising a specific exception")
fail
action
(Exception
"ExcelProvider: The reader was closed on startup without raising a specific exception")

reader
with ex ->
Expand Down Expand Up @@ -252,7 +260,10 @@ module internal ExcelAddressing =
#endif
let fail action (ex: exn) =
let exceptionTypeName = ex.GetType().Name
let message = sprintf "ExcelProvider: Could not %s. %s - %s" action exceptionTypeName (ex.Message)

let message =
sprintf "ExcelProvider: Could not %s. %s - %s" action exceptionTypeName (ex.Message)

failwith message

let excelReader =
Expand Down Expand Up @@ -329,7 +340,8 @@ type Row(documentId, sheetname, rowIndex, getCellValue: int -> int -> obj, colum
|> Seq.map (fun kvp -> kvp.Key)
|> Seq.tryFind (fun header -> String.Equals(header, columnName, StringComparison.OrdinalIgnoreCase))
|> function
| Some header -> sprintf "ExcelProvider: Column \"%s\" was not found. Did you mean \"%s\"?" columnName header
| Some header ->
sprintf "ExcelProvider: Column \"%s\" was not found. Did you mean \"%s\"?" columnName header
| None -> sprintf "ExcelProvider: Column \"%s\" was not found." columnName
|> failwith

Expand Down
2 changes: 1 addition & 1 deletion tests/ExcelProvider.Tests/ExcelProvider.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ let ``Cannot create a type referring to a non-existant sheet at runtime``() =
//| Choice2Of2 v -> printfn "2 %A" v
//printfn "%A" diag
let dstr = diag[0].ToString()
dstr.Substring(dstr.IndexOf "typecheck") |> should equal "typecheck error The type provider 'FSharp.Interop.Excel.ExcelProvider.ProviderImplementation+ExcelProvider' reported an error: Sheet [C] does not exist."
dstr.Substring(dstr.IndexOf "typecheck").Replace("ExcelProvider: ", "") |> should equal "typecheck error The type provider 'FSharp.Interop.Excel.ExcelProvider.ProviderImplementation+ExcelProvider' reported an error: Sheet [C] does not exist."


[<Test>]
Expand Down