This page demonstrates how to use FSharp.Formatting.CodeFormat
to tokenize
F# source code, obtain information about the source code (mainly tooltips
from the type-checker) and how to turn the code into a nicely formatted HTML.
First, we need to load the assembly and open necessary namespaces:
open FSharp.Formatting.CodeFormat
open System.Reflection
If you want to process multiple snippets, it is a good idea to keep the formatting agent around if possible. The agent needs to load the F# compiler (which needs to load various files itself) and so this takes a long time.
The formatting agent provides a CodeFormatAgent.ParseAndCheckSource method (together with an asynchronous
version for use from F# and also a version that returns a .NET Task
for C#).
To call the method, we define a simple F# code as a string:
let source =
"""
let hello () =
printfn "Hello world"
"""
let snippets, diagnostics =
CodeFormatter.ParseAndCheckSource("C:\\snippet.fsx", source, None, None, ignore)
When calling the method, you need to specify a file name and the actual content
of the script file. The file does not have to physically exist. It is used by the
F# compiler to resolve relative references (e.g. #r
) and to automatically name
the module including all code in the file.
You can also specify additional parameters, such as *.dll
references, by passing
a third argument with compiler options (e.g. "-r:Foo.dll -r:Bar.dll"
).
This operation might take some time, so it is a good idea to use an asynchronous variant of the method. It returns two arrays - the first contains F# snippets in the source code and the second contains any errors reported by the compiler. A single source file can include multiple snippets using the same formatting tags as those used on fssnip.net as documented in the about page.
Each returned snippet is essentially just a collection of lines and each line consists of a sequence of tokens. The following snippet prints basic information about the tokens of our sample snippet:
// Get the first snippet and obtain list of lines
let (Snippet (title, lines)) = snippets |> Seq.head
// Iterate over all lines and all tokens on each line
for (Line (_, tokens)) in lines do
for token in tokens do
match token with
| TokenSpan.Token (kind, code, tip) ->
printf "%s" code
tip
|> Option.iter (fun spans -> printfn "%A" spans)
| TokenSpan.Omitted _
| TokenSpan.Output _
| TokenSpan.Error _ -> ()
printfn ""
The TokenSpan.Token
is the most important kind of token. It consists of a kind
(identifier, keyword, etc.), the original F# code and tool tip information.
The tool tip is further formatted using a simple document format, but we simply
print the value using the F# pretty printing, so the result looks as follows:
let hello[Literal "val hello : unit -> unit"; ...] () =
printfn[Literal "val printfn : TextWriterFormat<'T> -> 'T"; ...] "Hello world"
The Omitted
token is generated if you use the special (*[omit:...]*)
command.
The Output
token is generated if you use the // [fsi:...]
command to format
output returned by F# interactive. The Error
command wraps code that should be
underlined with a red squiggle if the code contains an error.
Finally, the CodeFormat
type also includes a method CodeFormat.FormatHtml that can be used
to generate nice HTML output from an F# snippet. This is used, for example, on
F# Snippets. The following example shows how to call it:
let prefix = "fst"
let html = CodeFormat.FormatHtml(snippets, prefix)
// Print all snippets, in case there is more of them
for snip in html.Snippets do
printfn "%s" snip.Content
// Print HTML code that is generated for ToolTips
printfn "%s" html.ToolTip
If the input contains multiple snippets separated using the //[snippet:...]
comment, e.g.:
1: 2: 3: 4: 5: 6: 7: |
// [snippet: First sample] printf "The answer is: %A" 42 // [/snippet] // [snippet: Second sample] printf "Hello world!" // [/snippet] |
then the formatter returns multiple HTML blocks. However, the generated tool tips are shared by all snippets (to save space) and so they are returned separately.