From 00bfc969e50359c1fcf41d06468159d7ac421670 Mon Sep 17 00:00:00 2001 From: Michael Schmidt Date: Sat, 1 Dec 2018 21:27:42 +0100 Subject: [PATCH] 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/). --- components/prism-fsharp.js | 35 +++++++- components/prism-fsharp.min.js | 2 +- .../languages/fsharp/annotation_feature.test | 35 ++++++++ .../languages/fsharp/class-name_feature.test | 86 +++++++++++++++++++ .../computation-expression_feature.test | 17 ++++ tests/languages/fsharp/issue1480.test | 5 +- tests/languages/fsharp/keyword_feature.test | 20 +++-- tests/languages/fsharp/operator_feature.test | 53 ++++++++++++ 8 files changed, 238 insertions(+), 15 deletions(-) create mode 100644 tests/languages/fsharp/annotation_feature.test create mode 100644 tests/languages/fsharp/class-name_feature.test create mode 100644 tests/languages/fsharp/computation-expression_feature.test create mode 100644 tests/languages/fsharp/operator_feature.test diff --git a/components/prism-fsharp.js b/components/prism-fsharp.js index 375ab8cc15..86bd106069 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|<-|->|[!=:]=|?|\??(?:<=|>=|<>|[-+*/%=<>])\??|[!?^&]|~[+~-]|:>|:\?>?/ }); 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 3579777708..ffe5bc2562 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|<-|->|[!=:]=|?|\??(?:<=|>=|<>|[-+*\/%=<>])\??|[!?^&]|~[+~-]|:>|:\?>?/}),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 0000000000..3445236d0d --- /dev/null +++ b/tests/languages/fsharp/annotation_feature.test @@ -0,0 +1,35 @@ +[] +[] + +---------------------------------------------------- + +[ + ["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 0000000000..7939964246 --- /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 0000000000..a49c6e3535 --- /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 f46fce23df..2c692a33df 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 d2ef996f47..17f058a5e3 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 0000000000..7e15afc40d --- /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", "&&"] +] + +---------------------------------------------------- + +Checks for operators.