Commit 00bfc969e50359c1fcf41d06468159d7ac421670

Michael Schmidt 2018-12-01T21:27:42

Improve F# (#1573) Implement a few F# features: 1. [Attributes](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/attributes). 2. [Computation expressions](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions) (resolves #1459). 3. Class names for type annotations, [casts ](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/casting-and-conversions)(not including `as`), definitions and instancing (resolves #1460). 4. Proper support for ([nullable](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators)) [operators](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/).

diff --git a/components/prism-fsharp.js b/components/prism-fsharp.js
index 375ab8c..86bd106 100644
--- a/components/prism-fsharp.js
+++ b/components/prism-fsharp.js
@@ -9,17 +9,26 @@ Prism.languages.fsharp = Prism.languages.extend('clike', {
 			lookbehind: true
 		}
 	],
-	'keyword': /\b(?:let|return|use|yield)(?:!\B|\b)|\b(abstract|and|as|assert|base|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|global|if|in|inherit|inline|interface|internal|lazy|match|member|module|mutable|namespace|new|not|null|of|open|or|override|private|public|rec|select|static|struct|then|to|true|try|type|upcast|val|void|when|while|with|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|include|method|mixin|object|parallel|process|protected|pure|sealed|tailcall|trait|virtual|volatile)\b/,
 	'string': {
 		pattern: /(?:"""[\s\S]*?"""|@"(?:""|[^"])*"|"(?:\\[\s\S]|[^\\"])*")B?|'(?:[^\\']|\\.)'B?/,
 		greedy: true
 	},
+	'class-name': {
+		pattern: /(\b(?:exception|inherit|interface|new|of|type)\s+|\w\s*:\s*|\s:\??>\s*)[.\w]+\b(?:\s*(?:->|\*)\s*[.\w]+\b)*(?!\s*[:.])/,
+		lookbehind: true,
+		inside: {
+			'operator': /->|\*/,
+			'punctuation': /\./
+		}
+	},
+	'keyword': /\b(?:let|return|use|yield)(?:!\B|\b)|\b(abstract|and|as|assert|base|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|global|if|in|inherit|inline|interface|internal|lazy|match|member|module|mutable|namespace|new|not|null|of|open|or|override|private|public|rec|select|static|struct|then|to|true|try|type|upcast|val|void|when|while|with|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|include|method|mixin|object|parallel|process|protected|pure|sealed|tailcall|trait|virtual|volatile)\b/,
 	'number': [
 		/\b0x[\da-fA-F]+(?:un|lf|LF)?\b/,
 		/\b0b[01]+(?:y|uy)?\b/,
 		/(?:\b\d+\.?\d*|\B\.\d+)(?:[fm]|e[+-]?\d+)?\b/i,
 		/\b\d+(?:[IlLsy]|u[lsy]?|UL)?\b/
-	]
+	],
+	'operator': /([<>~&^])\1\1|([*.:<>&])\2|<-|->|[!=:]=|<?\|{1,3}>?|\??(?:<=|>=|<>|[-+*/%=<>])\??|[!?^&]|~[+~-]|:>|:\?>?/
 });
 Prism.languages.insertBefore('fsharp', 'keyword', {
 	'preprocessor': {
@@ -34,3 +43,25 @@ Prism.languages.insertBefore('fsharp', 'keyword', {
 		}
 	}
 });
+Prism.languages.insertBefore('fsharp', 'punctuation', {
+	'computation-expression': {
+		pattern: /[_a-z]\w*(?=\s*\{)/i,
+		alias: 'keyword'
+	}
+});
+Prism.languages.insertBefore('fsharp', 'string', {
+	'annotation': {
+		pattern: /\[<.+?>\]/,
+		inside: {
+			'punctuation': /^\[<|>\]$/,
+			'class-name': {
+				pattern: /^\w+$|(^|;\s*)[A-Z]\w*(?=\()/,
+				lookbehind: true
+			},
+			'annotation-content': {
+				pattern: /[\s\S]*/,
+				inside: Prism.languages.fsharp
+			}
+		}
+	}
+});
diff --git a/components/prism-fsharp.min.js b/components/prism-fsharp.min.js
index 3579777..ffe5bc2 100644
--- a/components/prism-fsharp.min.js
+++ b/components/prism-fsharp.min.js
@@ -1 +1 @@
-Prism.languages.fsharp=Prism.languages.extend("clike",{comment:[{pattern:/(^|[^\\])\(\*[\s\S]*?\*\)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],keyword:/\b(?:let|return|use|yield)(?:!\B|\b)|\b(abstract|and|as|assert|base|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|global|if|in|inherit|inline|interface|internal|lazy|match|member|module|mutable|namespace|new|not|null|of|open|or|override|private|public|rec|select|static|struct|then|to|true|try|type|upcast|val|void|when|while|with|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|include|method|mixin|object|parallel|process|protected|pure|sealed|tailcall|trait|virtual|volatile)\b/,string:{pattern:/(?:"""[\s\S]*?"""|@"(?:""|[^"])*"|"(?:\\[\s\S]|[^\\"])*")B?|'(?:[^\\']|\\.)'B?/,greedy:!0},number:[/\b0x[\da-fA-F]+(?:un|lf|LF)?\b/,/\b0b[01]+(?:y|uy)?\b/,/(?:\b\d+\.?\d*|\B\.\d+)(?:[fm]|e[+-]?\d+)?\b/i,/\b\d+(?:[IlLsy]|u[lsy]?|UL)?\b/]}),Prism.languages.insertBefore("fsharp","keyword",{preprocessor:{pattern:/^[^\r\n\S]*#.*/m,alias:"property",inside:{directive:{pattern:/(\s*#)\b(?:else|endif|if|light|line|nowarn)\b/,lookbehind:!0,alias:"keyword"}}}});
\ No newline at end of file
+Prism.languages.fsharp=Prism.languages.extend("clike",{comment:[{pattern:/(^|[^\\])\(\*[\s\S]*?\*\)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(?:"""[\s\S]*?"""|@"(?:""|[^"])*"|"(?:\\[\s\S]|[^\\"])*")B?|'(?:[^\\']|\\.)'B?/,greedy:!0},"class-name":{pattern:/(\b(?:exception|inherit|interface|new|of|type)\s+|\w\s*:\s*|\s:\??>\s*)[.\w]+\b(?:\s*(?:->|\*)\s*[.\w]+\b)*(?!\s*[:.])/,lookbehind:!0,inside:{operator:/->|\*/,punctuation:/\./}},keyword:/\b(?:let|return|use|yield)(?:!\B|\b)|\b(abstract|and|as|assert|base|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|global|if|in|inherit|inline|interface|internal|lazy|match|member|module|mutable|namespace|new|not|null|of|open|or|override|private|public|rec|select|static|struct|then|to|true|try|type|upcast|val|void|when|while|with|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|include|method|mixin|object|parallel|process|protected|pure|sealed|tailcall|trait|virtual|volatile)\b/,number:[/\b0x[\da-fA-F]+(?:un|lf|LF)?\b/,/\b0b[01]+(?:y|uy)?\b/,/(?:\b\d+\.?\d*|\B\.\d+)(?:[fm]|e[+-]?\d+)?\b/i,/\b\d+(?:[IlLsy]|u[lsy]?|UL)?\b/],operator:/([<>~&^])\1\1|([*.:<>&])\2|<-|->|[!=:]=|<?\|{1,3}>?|\??(?:<=|>=|<>|[-+*\/%=<>])\??|[!?^&]|~[+~-]|:>|:\?>?/}),Prism.languages.insertBefore("fsharp","keyword",{preprocessor:{pattern:/^[^\r\n\S]*#.*/m,alias:"property",inside:{directive:{pattern:/(\s*#)\b(?:else|endif|if|light|line|nowarn)\b/,lookbehind:!0,alias:"keyword"}}}}),Prism.languages.insertBefore("fsharp","punctuation",{"computation-expression":{pattern:/[_a-z]\w*(?=\s*\{)/i,alias:"keyword"}}),Prism.languages.insertBefore("fsharp","string",{annotation:{pattern:/\[<.+?>\]/,inside:{punctuation:/^\[<|>\]$/,"class-name":{pattern:/^\w+$|(^|;\s*)[A-Z]\w*(?=\()/,lookbehind:!0},"annotation-content":{pattern:/[\s\S]*/,inside:Prism.languages.fsharp}}}});
\ No newline at end of file
diff --git a/tests/languages/fsharp/annotation_feature.test b/tests/languages/fsharp/annotation_feature.test
new file mode 100644
index 0000000..3445236
--- /dev/null
+++ b/tests/languages/fsharp/annotation_feature.test
@@ -0,0 +1,35 @@
+[<Foo>]
+[<Bar("bar"); Foo(1, 2)>]
+
+----------------------------------------------------
+
+[
+	["annotation", [
+		["punctuation", "[<"],
+		["class-name", "Foo"],
+		["punctuation", ">]"]
+	]],
+	["annotation", [
+		["punctuation", "[<"],
+		["class-name", "Bar"],
+		["annotation-content", [
+			["punctuation", "("],
+			["string", "\"bar\""],
+			["punctuation", ")"],
+			["punctuation", ";"]
+		]],
+		["class-name", "Foo"],
+		["annotation-content", [
+			["punctuation", "("],
+			["number", "1"],
+			["punctuation", ","],
+			["number", "2"],
+			["punctuation", ")"]
+		]],
+		["punctuation", ">]"]
+	]]
+]
+
+----------------------------------------------------
+
+Checks for annotations.
diff --git a/tests/languages/fsharp/class-name_feature.test b/tests/languages/fsharp/class-name_feature.test
new file mode 100644
index 0000000..7939964
--- /dev/null
+++ b/tests/languages/fsharp/class-name_feature.test
@@ -0,0 +1,86 @@
+let func : HttpFunc = handler (Some >> Task.FromResult)
+
+type Base1() =
+    abstract member F : unit -> unit
+    default u.F() =
+        printfn "F Base1"
+
+type Derived1() =
+    inherit Base1()
+    override u.F() =
+        printfn "F Derived1"
+
+let d1 : Derived1 = Derived1()
+
+let base1 = d1 :> Base1
+let derived1 = base1 :?> Derived1
+
+type PersonName =
+    | FirstOnly of string
+    | LastOnly of string
+    | FirstLast of string * string
+
+type Shape =
+    | Rectangle of height : float * width : float
+    | Circle of radius : float
+
+type MyInterface =
+   abstract member Add: int -> int -> int
+   abstract member Pi : float
+
+exception Error1 of string
+exception Error2 of string * int
+
+----------------------------------------------------
+
+[
+	["keyword", "let"], " func ",
+	["punctuation", ":"], ["class-name", ["HttpFunc"]],
+	["operator", "="], " handler ", ["punctuation", "("],
+	"Some ", ["operator", ">>"], " Task", ["punctuation", "."], "FromResult",
+	["punctuation", ")"],
+
+	["keyword", "type"], ["class-name", ["Base1"]], ["punctuation", "("], ["punctuation", ")"], ["operator", "="],
+	["keyword", "abstract"], ["keyword", "member"], " F ", ["punctuation", ":"],
+	["class-name", [
+		"unit ", ["operator", "->"], " unit"]
+	],
+	["keyword", "default"], " u", ["punctuation", "."], ["function", "F"], ["punctuation", "("], ["punctuation", ")"],
+	["operator", "="], "\n        printfn ", ["string", "\"F Base1\""],
+
+	["keyword", "type"], ["class-name", ["Derived1"]], ["punctuation", "("], ["punctuation", ")"], ["operator", "="],
+	["keyword", "inherit"], ["class-name", ["Base1"]], ["punctuation", "("], ["punctuation", ")"],
+	["keyword", "override"], " u", ["punctuation", "."], ["function", "F"], ["punctuation", "("], ["punctuation", ")"], ["operator", "="],
+	"\n        printfn ", ["string", "\"F Derived1\""],
+
+	["keyword", "let"], " d1 ", ["punctuation", ":"], ["class-name", ["Derived1"]], ["operator", "="],
+	["function", "Derived1"], ["punctuation", "("], ["punctuation", ")"],
+
+	["keyword", "let"], " base1 ", ["operator", "="], " d1 ", ["operator", ":>"], ["class-name", ["Base1"]],
+
+	["keyword", "let"], " derived1 ", ["operator", "="], " base1 ", ["operator", ":?>"], ["class-name", ["Derived1"]],
+
+	["keyword", "type"], ["class-name", ["PersonName"]], ["operator", "="],
+	["operator", "|"], " FirstOnly ", ["keyword", "of"], ["class-name", ["string"]],
+	["operator", "|"], " LastOnly ", ["keyword", "of"], ["class-name", ["string"]],
+	["operator", "|"], " FirstLast ", ["keyword", "of"], ["class-name", ["string ", ["operator", "*"], " string"]],
+
+	["keyword", "type"], ["class-name", ["Shape"]], ["operator", "="],
+	["operator", "|"], " Rectangle ", ["keyword", "of"],
+	" height ", ["punctuation", ":"], ["class-name", ["float"]], ["operator", "*"],
+	" width ", ["punctuation", ":"], ["class-name", ["float"]],
+	["operator", "|"], " Circle ", ["keyword", "of"], " radius ", ["punctuation", ":"], ["class-name", ["float"]],
+
+	["keyword", "type"], ["class-name", ["MyInterface"]], ["operator", "="],
+	["keyword", "abstract"], ["keyword", "member"], " Add", ["punctuation", ":"],
+	["class-name", ["int ", ["operator", "->"], " int ", ["operator", "->"], " int"]],
+	["keyword", "abstract"], ["keyword", "member"], " Pi ", ["punctuation", ":"], ["class-name", ["float"]],
+
+	["keyword", "exception"], ["class-name", ["Error1"]], ["keyword", "of"], ["class-name", ["string"]],
+
+	["keyword", "exception"], ["class-name", ["Error2"]], ["keyword", "of"], ["class-name", ["string ", ["operator", "*"], " int"]]
+]
+
+----------------------------------------------------
+
+Checks for class-names.
diff --git a/tests/languages/fsharp/computation-expression_feature.test b/tests/languages/fsharp/computation-expression_feature.test
new file mode 100644
index 0000000..a49c6e3
--- /dev/null
+++ b/tests/languages/fsharp/computation-expression_feature.test
@@ -0,0 +1,17 @@
+async {}
+task {}
+seq {}
+foo {}
+
+----------------------------------------------------
+
+[
+	["computation-expression", "async"], ["punctuation", "{"], ["punctuation", "}"],
+	["computation-expression", "task"], ["punctuation", "{"], ["punctuation", "}"],
+	["computation-expression", "seq"], ["punctuation", "{"], ["punctuation", "}"],
+	["computation-expression", "foo"], ["punctuation", "{"], ["punctuation", "}"]
+]
+
+----------------------------------------------------
+
+Checks for computation expressions.
diff --git a/tests/languages/fsharp/issue1480.test b/tests/languages/fsharp/issue1480.test
index f46fce2..2c692a3 100644
--- a/tests/languages/fsharp/issue1480.test
+++ b/tests/languages/fsharp/issue1480.test
@@ -16,8 +16,7 @@ let map (f: 'a -> 'b) (xs: 'a list): 'b list = failWith "not implemented"
 	"f",
 	["punctuation", ":"],
 	" 'a ",
-	["operator", "-"],
-	["operator", ">"],
+	["operator", "->"],
 	" 'b",
 	["punctuation", ")"],
 	["punctuation", "("],
@@ -34,4 +33,4 @@ let map (f: 'a -> 'b) (xs: 'a list): 'b list = failWith "not implemented"
 
 ----------------------------------------------------
 
-Checks for apostrophes in names. See #1480.
\ No newline at end of file
+Checks for apostrophes in names. See #1480.
diff --git a/tests/languages/fsharp/keyword_feature.test b/tests/languages/fsharp/keyword_feature.test
index d2ef996..17f058a 100644
--- a/tests/languages/fsharp/keyword_feature.test
+++ b/tests/languages/fsharp/keyword_feature.test
@@ -4,19 +4,20 @@ class;
 default
 delegate do done downcast
 downto elif else end
-exception extern false finally
+exception;
+extern false finally
 for fun function global
-if in inherit inline
+if in inherit; inline
 interface;
 internal lazy let
 let! match member module
 mutable namespace
 new;
 not
-null of open or override
+null of; open or override
 private public rec return
 return! select static struct
-then to true try type
+then to true try type;
 upcast use use! val void
 when while with yield
 yield! asr land lor lsl
@@ -39,19 +40,20 @@ virtual volatile
 	["keyword", "default"],
 	["keyword", "delegate"], ["keyword", "do"], ["keyword", "done"], ["keyword", "downcast"],
 	["keyword", "downto"], ["keyword", "elif"], ["keyword", "else"], ["keyword", "end"],
-	["keyword", "exception"], ["keyword", "extern"], ["keyword", "false"], ["keyword", "finally"],
+	["keyword", "exception"], ["punctuation", ";"],
+	["keyword", "extern"], ["keyword", "false"], ["keyword", "finally"],
 	["keyword", "for"], ["keyword", "fun"], ["keyword", "function"], ["keyword", "global"],
-	["keyword", "if"], ["keyword", "in"], ["keyword", "inherit"], ["keyword", "inline"],
+	["keyword", "if"], ["keyword", "in"], ["keyword", "inherit"], ["punctuation", ";"], ["keyword", "inline"],
 	["keyword", "interface"], ["punctuation", ";"],
 	["keyword", "internal"], ["keyword", "lazy"], ["keyword", "let"],
 	["keyword", "let!"], ["keyword", "match"], ["keyword", "member"], ["keyword", "module"],
 	["keyword", "mutable"], ["keyword", "namespace"],
 	["keyword", "new"], ["punctuation", ";"],
 	["keyword", "not"],
-	["keyword", "null"], ["keyword", "of"], ["keyword", "open"], ["keyword", "or"], ["keyword", "override"],
+	["keyword", "null"], ["keyword", "of"], ["punctuation", ";"], ["keyword", "open"], ["keyword", "or"], ["keyword", "override"],
 	["keyword", "private"], ["keyword", "public"], ["keyword", "rec"], ["keyword", "return"],
 	["keyword", "return!"], ["keyword", "select"], ["keyword", "static"], ["keyword", "struct"],
-	["keyword", "then"], ["keyword", "to"], ["keyword", "true"], ["keyword", "try"], ["keyword", "type"],
+	["keyword", "then"], ["keyword", "to"], ["keyword", "true"], ["keyword", "try"], ["keyword", "type"], ["punctuation", ";"],
 	["keyword", "upcast"], ["keyword", "use"], ["keyword", "use!"], ["keyword", "val"], ["keyword", "void"],
 	["keyword", "when"], ["keyword", "while"], ["keyword", "with"], ["keyword", "yield"],
 	["keyword", "yield!"], ["keyword", "asr"], ["keyword", "land"], ["keyword", "lor"], ["keyword", "lsl"],
@@ -68,4 +70,4 @@ virtual volatile
 
 ----------------------------------------------------
 
-Checks for all keywords.
\ No newline at end of file
+Checks for all keywords.
diff --git a/tests/languages/fsharp/operator_feature.test b/tests/languages/fsharp/operator_feature.test
new file mode 100644
index 0000000..7e15afc
--- /dev/null
+++ b/tests/languages/fsharp/operator_feature.test
@@ -0,0 +1,53 @@
+ >=   <=   <>   >   <   =   +   -   *   /   %
+ >=?  <=?  <>?  >?  <?  =?  +?  -?  *?  /?  %?
+?>=? ?<=? ?<>? ?>? ?<? ?=? ?+? ?-? ?*? ?/? ?%?
+?>=  ?<=  ?<>  ?>  ?<  ?=  ?+  ?-  ?*  ?/  ?%
+
+**
+
+<- ->
+..
+::
+:=
+:> :? :?>
+<< >>
+<<< >>> ~~~ ^^^ &&& |||
+| ||
+<| <|| <|||
+|> ||> |||>
+~~ ~- ~+
+
+? ^ !
+!= ==
+& &&
+
+----------------------------------------------------
+
+[
+	["operator", ">="], ["operator", "<="], ["operator", "<>"], ["operator", ">"], ["operator", "<"], ["operator", "="], ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], ["operator", "%"],
+	["operator", ">=?"], ["operator", "<=?"], ["operator", "<>?"], ["operator", ">?"], ["operator", "<?"], ["operator", "=?"], ["operator", "+?"], ["operator", "-?"], ["operator", "*?"], ["operator", "/?"], ["operator", "%?"],
+	["operator", "?>=?"], ["operator", "?<=?"], ["operator", "?<>?"], ["operator", "?>?"], ["operator", "?<?"], ["operator", "?=?"], ["operator", "?+?"], ["operator", "?-?"], ["operator", "?*?"], ["operator", "?/?"], ["operator", "?%?"],
+	["operator", "?>="], ["operator", "?<="], ["operator", "?<>"], ["operator", "?>"], ["operator", "?<"], ["operator", "?="], ["operator", "?+"], ["operator", "?-"], ["operator", "?*"], ["operator", "?/"], ["operator", "?%"],
+
+	["operator", "**"],
+
+	["operator", "<-"], ["operator", "->"],
+	["operator", ".."],
+	["operator", "::"],
+	["operator", ":="],
+	["operator", ":>"], ["operator", ":?"], ["operator", ":?>"],
+	["operator", "<<"], ["operator", ">>"],
+	["operator", "<<<"], ["operator", ">>>"], ["operator", "~~~"], ["operator", "^^^"], ["operator", "&&&"], ["operator", "|||"],
+	["operator", "|"], ["operator", "||"],
+	["operator", "<|"], ["operator", "<||"], ["operator", "<|||"],
+	["operator", "|>"], ["operator", "||>"], ["operator", "|||>"],
+	["operator", "~~"], ["operator", "~-"], ["operator", "~+"],
+
+	["operator", "?"], ["operator", "^"], ["operator", "!"],
+	["operator", "!="], ["operator", "=="],
+	["operator", "&"], ["operator", "&&"]
+]
+
+----------------------------------------------------
+
+Checks for operators.