FSharp.Formatting


F# Formatting: Code formatting

This page demonstrates how to use FSharp.CodeFormat.dll 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:

1: 
2: 
3: 
#r "../../bin/FSharp.CodeFormat.dll"
open FSharp.CodeFormat
open System.Reflection

Starting a background agent

The FSharp.CodeFormat namespace contains CodeFormat type which is the entry point. The static method CreateAgent starts a background worker that can be called to format snippets repeatedly:

1: 
let formattingAgent = CodeFormat.CreateAgent()

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. As the above example shows, you can specify which version of FSharp.Compiler.dll to use.

Processing F# source

The formatting agent provides a ParseSource 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:

1: 
2: 
3: 
4: 
5: 
let source = """
    let hello () = 
      printfn "Hello world"
  """
let snippets, errors = formattingAgent.ParseSource("C:\\snippet.fsx", source)

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.

Working with returned tokens

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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
// 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:

1: 
2: 
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.

Generating HTML output

Finally, the CodeFormat type also includes a method 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:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
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.

Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.CodeFormat
namespace System
namespace System.Reflection
val formattingAgent : CodeFormatAgent

Full name: Codeformat.formattingAgent
type CodeFormat =
  static member CreateAgent : unit -> CodeFormatAgent
  static member FormatHtml : snippets:Snippet [] * prefix:string -> FormattedContent
  static member FormatHtml : snippets:Snippet [] * prefix:string * addLines:bool * addErrors:bool -> FormattedContent
  static member FormatHtml : snippets:Snippet [] * prefix:string * openTag:string * closeTag:string * addLines:bool * addErrors:bool -> FormattedContent
  static member FormatHtml : snippets:Snippet [] * prefix:string * openTag:string * closeTag:string * openLinesTag:string * closeLinesTag:string * addLines:bool * addErrors:bool -> FormattedContent
  static member FormatLatex : snippets:Snippet [] -> FormattedContent
  static member FormatLatex : snippets:Snippet [] * addLines:bool -> FormattedContent
  static member FormatLatex : snippets:Snippet [] * openTag:string * closeTag:string * addLines:bool -> FormattedContent

Full name: FSharp.CodeFormat.CodeFormat
static member CodeFormat.CreateAgent : unit -> CodeFormatAgent
val source : string

Full name: Codeformat.source
val snippets : Snippet []

Full name: Codeformat.snippets
val errors : SourceError []

Full name: Codeformat.errors
member CodeFormatAgent.ParseSource : file:string * source:string * ?options:string * ?defines:string -> Snippet [] * SourceError []
Multiple items
union case Snippet.Snippet: string * Line list -> Snippet

--------------------
type Snippet = | Snippet of string * Line list

Full name: FSharp.CodeFormat.Snippet
val title : string

Full name: Codeformat.title
val lines : Line list

Full name: Codeformat.lines
module Seq

from Microsoft.FSharp.Collections
val head : source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.head
Multiple items
union case Line.Line: TokenSpans -> Line

--------------------
type Line = | Line of TokenSpans

Full name: FSharp.CodeFormat.Line
val tokens : TokenSpans
val token : TokenSpan
type TokenSpan =
  | Token of TokenKind * string * ToolTipSpans option
  | Error of ErrorKind * string * TokenSpans
  | Omitted of string * string
  | Output of string

Full name: FSharp.CodeFormat.TokenSpan
union case TokenSpan.Token: TokenKind * string * ToolTipSpans option -> TokenSpan
val kind : TokenKind
val code : string
val tip : ToolTipSpans option
val printf : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
module Option

from Microsoft.FSharp.Core
val iter : action:('T -> unit) -> option:'T option -> unit

Full name: Microsoft.FSharp.Core.Option.iter
val spans : ToolTipSpans
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
union case TokenSpan.Omitted: string * string -> TokenSpan
union case TokenSpan.Output: string -> TokenSpan
union case TokenSpan.Error: ErrorKind * string * TokenSpans -> TokenSpan
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val prefix : string

Full name: Codeformat.prefix
val html : FormattedContent

Full name: Codeformat.html
static member CodeFormat.FormatHtml : snippets:Snippet [] * prefix:string -> FormattedContent
static member CodeFormat.FormatHtml : snippets:Snippet [] * prefix:string * addLines:bool * addErrors:bool -> FormattedContent
static member CodeFormat.FormatHtml : snippets:Snippet [] * prefix:string * openTag:string * closeTag:string * addLines:bool * addErrors:bool -> FormattedContent
static member CodeFormat.FormatHtml : snippets:Snippet [] * prefix:string * openTag:string * closeTag:string * openLinesTag:string * closeLinesTag:string * addLines:bool * addErrors:bool -> FormattedContent
val snip : FormattedSnippet
property FormattedContent.Snippets: FormattedSnippet []
property FormattedSnippet.Content: string
property FormattedContent.ToolTip: string
Fork me on GitHub