The F# lexer turns the text into tokens by always taking the longest match. This page follows the lexer of the compiler (lex.fsl and its keyword table) and the language specification, and ends with how the viewer colours each form. Where the specification and the lexer differ, the lexer is followed and the difference is said.
White space and line ends
White space is the space character; a line ends with \n or \r\n. A tab is an error in code (in strings and comments it is just a character). Indentation is significant: see the offside rule below.
Comments
| Form | Meaning |
|---|---|
// text | line comment, to the end of the line. The lexer reads // together with any operator characters that follow it, so //> and //- are comments too. |
/// text | XML documentation comment (exactly three slashes; four or more are an ordinary comment). The text before a declaration is its documentation, normally XML such as <summary> and <param name="x">; plain text is accepted as a summary. |
(* text *) | block comment; they nest, so (* a (* b *) c *) is one comment. |
(*) | not a comment: the multiplication operator as a function, (*). (**) is a comment. |
#! line | a shebang, read as a comment; only allowed as the first line of a file. |
Inside a block comment the lexer still reads strings, characters and verbatim and triple-quoted strings, so that a quoted *) does not end the comment: (* let s = "*)" *) is one comment. A character literal such as '"' is read whole, so its quote does not start a string, but a lone apostrophe (as in don't) is ignored. A triple quote inside a comment starts a triple-quoted string, so (* """ *) is an unclosed comment.
/// Adds two numbers.
/// <param name="a">the first</param>
let add a b = a + b // sum
(* a comment (* nested *) with a string "*)" inside *)
/// comment are coloured as tags.Identifiers
An identifier starts with a letter (Unicode categories Lu, Ll, Lt, Lm, Lo, Nl) or an underscore, and continues with letters, decimal digits, connecting punctuation (Pc), combining marks (Mn, Mc), format characters (Cf), apostrophes and underscores. So x', x'', _name and größe are identifiers; the apostrophe is allowed after the first character, which is why a'b' is one name and not a name and a character literal.
Any text between double backticks, without a line end, tab or `` inside, is an identifier too: ``my test`` or ``end`` (a keyword used as a name). A single backtick inside is allowed when no backtick follows it. When such a name is used for a type, union case, module or namespace, the characters . + $ & [ ] / \ * " ` are not allowed in it.
An identifier immediately followed by ! or # is a reserved form (an error), except the keywords let! use! do! yield! return! match! and! while!. __SOURCE_DIRECTORY__, __SOURCE_FILE__ and __LINE__ are replaced by string literals (the directory of the file, its name, and the current line number, adjusted by line directives).
Keywords
These words are keywords. The compiler's table lists each as always a keyword, or a keyword except under --mlcompatibility, where the F#-only ones can be identifiers.
| Group | Words |
|---|---|
| declarations | let use rec and in type module namespace open exception val extern member abstract override default static new inherit interface class struct delegate of with end |
| modifiers | mutable inline private public internal const fixed lazy global base |
| expressions | if then elif else match when function fun do done for to downto while try finally assert begin yield return upcast downcast void null true false as or mod sig |
The complete table, in order: abstract and as assert base begin class const default delegate do done downcast downto elif else end exception extern false finally fixed for fun function global if in inherit inline interface internal lazy let match member mod module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield. _ is also a token of its own (the wildcard). return and yield are two spellings of one token inside computation expressions, as are use and let (use is let with disposal).
Words with a bang
let! and use! bind the result of a computation expression step, and! binds a parallel step, do! runs one for its effect, return! and yield! return or yield a whole computation, match! matches on the result of one, and while! is a loop token that the lexer has (its translation is not described here). See computation expressions.
Reserved words
The lexer reserves break checked component constraint continue fori include mixin parallel params process protected pure sealed tailcall trait virtual. Using one as a name gives a warning and works. The documentation also lists event and external as reserved; the lexer's table does not contain them. The OCaml words asr land lor lsl lsr lxor are listed in the documentation as reserved keywords (usable as names under --mlcompatibility); the lexer's keyword table holds only mod and sig of the OCaml words. The viewer colours all of them as keywords.
Words reserved before F# 4.1 and now free: atomic constructor eager functor measure method object recursive volatile.
Contextual words
These are not keywords and may be names: get and set in a property (with get () = … and set v = …), select, where, sortBy and the other operations of query expressions, not (a function; also used in the constraint not struct), the constraint names unmanaged equality comparison, async seq task query (library values), and this and self (just names for the object).
Symbolic keywords
-> <- . : ( ) [ ] [< >] [| |] { } {| |} ' # :?> :? :> .. ... ..^ :: := ;; ; , = _ ? ?? (*) <@ @> <@@ @@> | || and the words with a bang above. ~ and a lone backtick are reserved.
Numeric literals
Digits may be separated by underscores (1_000_000), but a literal may not end in one. A literal starts with a digit; there is no leading sign (a - before it is the minus operator, which the parser merges with an adjacent number) and no form starting with a dot. The radix prefixes are 0x (hexadecimal), 0o (octal) and 0b (binary), in either case.
| Suffix | Type | Examples |
|---|---|---|
| none | int (32-bit), or float if it has a point or an exponent | 42 0x2A 0o52 0b101010 1_000 3.14 1e3 1.5e-3 1. |
y | sbyte | 86y 0b00000101y |
uy | byte | 86uy 0xFFuy |
s | int16 | 86s |
us | uint16 | 86us |
l | int32 | 86l |
u, ul | uint32 | 86u 86ul |
n | nativeint | 123n |
un | unativeint | 0x00002D3Fun |
L | int64 | 86L 0xFFL |
UL, uL | uint64 | 86UL |
f, F | float32 (single); after a decimal integer or a float | 1f 4.14F 2.3e+32f |
lf | float32 from a hexadecimal bit pattern | 0x00000000lf |
LF | float from a hexadecimal bit pattern | 0x0000000000000000LF |
m, M | decimal | 0.7833M 5m |
I | bigint | 9999999999999999999999999999I |
Q R Z G N | other big-number literals: the lexer accepts them after an integer and the program must define a module NumericLiteralQ (and so on) to give them a meaning | 12Q 12N |
A number followed by name characters that match no suffix (3foo, 1d, 0x) is a reserved form and an error. An integer followed directly by .. is the integer and the range symbol (1..5). Special float values are the library names infinity, -infinity, nan, infinityf and nanf. A value that does not fit its type is an error, except that the minimum value of a signed type may be written with its minus sign (-128y).
Characters and strings
| Form | Type | Notes |
|---|---|---|
'a' | char | one character, an escape, a trigraph '\065', '\x41', '\u0041' or '\U0001F600' (the last only when the code is one UTF-16 unit) |
'a'B | byte | an ASCII character; code above 127 is an error |
"text" | string | escapes; may run over several lines; a backslash at the end of a line removes the line end and the white space that starts the next |
"text"B | byte[] | an ASCII string |
@"text" | string | verbatim: no escapes, "" is one quote; @"text"B is a byte array |
"""text""" | string | triple-quoted: no escapes and quotes allowed inside; ends at the first """; """text"""B is a byte array |
$"text {expr}" | string or FormattableString | interpolated; see below |
The escapes are \n \t \b \r \a \f \v \\ \" \', \DDD (three decimal digits, 000 to 255: decimal, not octal), \xHH, \uHHHH and \UHHHHHHHH (a code point up to 10FFFF, written as a surrogate pair when needed). A backslash before any other character is just a backslash. A byte string cannot hold a character above 127.
A single quote is a character literal only when the whole literal fits ('a', '\n', '''); otherwise a quote followed by a name is a type variable ('a, 'T).
Interpolated strings
The forms are $"…", $@"…" and @$"…" (verbatim), $"""…""" (triple-quoted) and $$"""…""", $$$"""…""" (with n dollars: a hole is opened by n braces, so a single brace is text). A hole holds any expression and may hold strings, comments and further interpolated strings; {{ and }} are a literal brace (in the forms with one dollar). After the expression a hole may have an alignment ({x,10}, {x,-10}) and a .NET format after a colon ({x:N2}); a printf-style format goes before the hole (%d{x}, %s{name}, and %%d{{x}} in the extended form). A colon inside brackets belongs to the expression ({(x : int)}). A byte string cannot be interpolated.
let name = "Ada"
let n = 3
let a = $"Hello {name}, you have {n:N1} messages {{not a hole}}"
let b = $@"C:\users\{name}\files"
let c = $"""He said "{name}" and %d{n}"""
let d = $$"""{ "name": "{{name}}" }"""
let e = "tab\there \u0041 \065 \x41"
let f = @"a ""quoted"" word"
let g = "bytes"B, 'x'B
Operators
A symbolic operator is a run of operator characters: the first from ! % & * + - . / < = > @ ^ | ~ and then any of those and ? : (plus $, which the lexer allows to start an infix operator). Leading ., $ and ? characters are ignored when the operator class is chosen. The lexer reads the whole run (>>=, <*>, |||>), then the class of the first character gives its precedence, so a custom operator takes the precedence of the operator it begins like. Symbolic keywords (such as -> and :=) are excluded from this.
The class of an operator, and its precedence from highest to lowest, is below. OP stands for any operator that begins with the given characters and is not listed itself.
| Operator or construct | Associates |
|---|---|
f<types>, f(x) with no space (high-precedence application) | left |
. and ? | left |
prefix operators (-x, !x, ~~~x, %x, &x) | left |
function application f x, lazy x, assert x | left |
**OP | right |
*OP /OP %OP | left |
-OP +OP (infix) | left |
:? | none |
:: | right |
^OP @OP | right |
!=OP <OP >OP =OP |OP &OP $ (so |>, <|, =, <>, >=, <=, |||, &&&) | left |
:> :?> | right |
& && | left |
or || | left |
, | none |
:= | right |
<- | right |
if, function fun match try, let | none |
; | right |
| (a match rule) | left |
when, as | right |
The library operators are in the standard library. + - +. -. % %% & && and ?+ ?- can be prefix or infix; as prefix they are named with a leading tilde (~-, ~+) when defined, as in let (~-) x = … or static member (~-) (x: C) = …. A custom operator is defined by putting it in brackets: let (+++) a b = a + b; write ( *** ) with spaces so that the opening bracket does not start a comment. Operator definitions use the names op_Addition and so on when seen from other .NET languages. :: is the list cons case and cannot be redefined. or and & as boolean operators are kept from OCaml.
The offside rule
F# lets blocks be made by indentation. After the lexer, a filter reads the tokens with a stack of contexts and inserts hidden tokens ($begin, $end, $sep, $in, $done, and two for application: $app for f(x) and $tyapp for f<int>). An offside line is a column. It is set at the column of:
- the first token after
=in aletor member, afterthen,else,try,finally,do,->in a match,with, an infix operator, and after(,{,[,[|,begin,struct,sig,<@; - the first token of a file (a sequence block), and of the body of
interface,classandstructin a type; - the start of
let,ifandmodule.
A token that starts to the left of the offside line closes the context (a new declaration or an end of the block); a token at the same column starts a new item of the block (the filter inserts a separator, so a ; or in is not needed); a token to the right continues the expression. Closing brackets pop contexts up to the one their opening token made. There are permitted undentations (an | of a match may line up with match, the body of a function in a call may drop to the column of the line, if branches and module bodies may be undented). The contexts the filter keeps are Let, If, Try, Lazy, Fun, Function, WithLet, WithAugment, Match, For, While, Then, Else, Do, Type, Namespace, Module, Member, Paren, MatchClauses, SeqBlock and Vanilla. Comments are white space and do not count for indentation.
With light syntax the ;; at the end of a top-level expression and the in after a binding are optional; ;; is still how F# Interactive is told the input is complete when a line is typed.
let compute x =
let y = x * 2 // a new item of the block: same column
let z =
y + 1 // the block after = starts at this column
if z > 10 then
"big"
else
"small"
let result = compute 3 |> fun s ->
s.ToUpper() // a permitted undentation after a lambda
Other forms
| Form | Meaning |
|---|---|
[< … >] | an attribute list: [<Literal>], [<assembly: AssemblyVersion("1.0")>], [<A; B(1)>]. [< is one token, so a list holding a quotation must have a space: [ <@ 1 @> ]. |
(|A|B|), (|A|_|) | an active pattern name (“banana clips”) |
1.0<m/s> | a unit of measure written after a number or in a type, with no space before < |
<@ … @>, <@@ … @@> | typed and untyped quotations; %x and %%x splice a value in |
[| … |], {| … |} | array and anonymous record brackets |
'a, ^T, #T, _ | a generic type parameter, a statically resolved one, a flexible type, a wildcard |
f<int> | a type application; it needs no space before < and the text up to the matching > must look like a type |
a.[i], a[i], a.[i..j] | indexing and slicing; .. and ..^ (from the end) are symbols |
#if #else #elif #endif, #nowarn, #line, #r … | directives, at the start of a line: see directives |
How the viewer colours each form
The scanner (explorer/formats/shared/fsharp.js) reads the text once. Everything it does not recognise is left plain, and anything unterminated runs to the end of its line or of the file, as in the compiler.
| Colour | What gets it |
|---|---|
| comment | // and /// comments, nested (* *) comments, the #! line |
| tag | the XML tags (<summary>, </param>) in a /// comment |
| string | every string and character form (including the byte suffix), each piece of an interpolated string, the format after a colon in a hole |
| number | every numeric literal that is valid; a reserved form (3foo) is left plain |
| keyword | the keyword table (let and the others), the reserved and ML words, the words with a bang (with the bang), the arrows -> and <- and the type operators :> :?> :?, the attribute target (assembly:), get and set of a property, constraint words in a type |
| literal | true false null, None Some Ok Error, nan infinity and their float32 forms, stdin stdout stderr, the source identifiers, the symbols after #if |
| directive | #if and the other directives, [< and >], quotation marks, the brackets of an active pattern name, #: lines |
| type | names in a type (after :, :>, :?>, :?, of, inherit, interface, new, in generic arguments, in a type definition, in open, module and namespace paths), type variables ('a), attribute names, units, the built-in lower-case types (int … exn unit list), names with a capital letter in other places, and the library modules in List.map |
| function | the name bound by a let with parameters or a method member, a name followed directly by (, a name after a library module and a dot, the library functions (printfn failwith id ignore not …), the builders async seq task query, active pattern names |
| variable | names bound by let without parameters, parameters and pattern variables (in let, fun, for, match arms, members), a name followed by a single colon (a parameter, a record field, a union field), the self name of a member, property members |
| plain | other operators, brackets, separators, names the scanner cannot place |
The context rules in brief: a single colon starts a type, which runs until a token a type cannot hold (=, , or ; or a closing bracket outside the type, a keyword, a ( straight after a name) or the end of the line, unless the line ended with -> or * or the next line starts with one; the type of a definition after type X = also continues over a block of | cases or { } fields; let, fun, for and a | case start a pattern that ends at =, ->, in or when.
Not described
Known, not yet covered
- Reference: the exact offside rules (the pre-parse stack's balancing rules, every exception and permitted undentation) are summarised from the specification and
LexFilter.fswas not read in full; the grammar ofpars.fsy(which productions allow which tokens) is not described. - Reference: the exact rules of high-precedence application (
f(x)againstf (x)) and of type applications (f<int>against comparisons) are only named. - Viewer: the format specifiers inside a normal string (
%d,%s,%.2f) are part of the string colour and are not marked separately; the escapes in strings are not coloured separately; the active code of#ifis not known, so both sides are read. - Viewer: a function value passed on without a bracket (
List.map f xs, wherefis a function) is plain, since F# has no mark for an application; a capitalised value (let Pi = 3.14) is read as a type name where it is used; a type that continues on the next line other than with->or*ends at the line end.
Unknown
- Reference: whether
eventandexternalare reserved by any compiler version: the documentation lists them and the lexer table does not.
See also
- Types and Expressions and control flow – what the tokens build
- Directives and conditional compilation – the
#lines