2023-10-03 11:14:36 +08:00
/ * *
* @ param { string } value
* @ returns { RegExp }
* * /
function escape ( value ) {
return new RegExp ( value . replace ( /[-/\\^$*+?.()|[\]{}]/g , '\\$&' ) , 'm' ) ;
}
/ * *
* @ param { RegExp | string } re
* @ returns { string }
* /
function source ( re ) {
if ( ! re ) return null ;
if ( typeof re === "string" ) return re ;
return re . source ;
}
/ * *
* @ param { RegExp | string } re
* @ returns { string }
* /
function lookahead ( re ) {
return concat ( '(?=' , re , ')' ) ;
}
/ * *
* @ param { ... ( RegExp | string ) } args
* @ returns { string }
* /
function concat ( ... args ) {
const joined = args . map ( ( x ) => source ( x ) ) . join ( "" ) ;
return joined ;
}
/ * *
* @ param { Array < string | RegExp | Object > } args
* @ returns { object }
* /
function stripOptionsFromArgs ( args ) {
const opts = args [ args . length - 1 ] ;
if ( typeof opts === 'object' && opts . constructor === Object ) {
args . splice ( args . length - 1 , 1 ) ;
return opts ;
} else {
return { } ;
}
}
/** @typedef { {capture?: boolean} } RegexEitherOptions */
/ * *
* Any of the passed expresssions may match
*
* Creates a huge this | this | that | that match
* @ param { ( RegExp | string ) [ ] | [ ... ( RegExp | string ) [ ] , RegexEitherOptions ] } args
* @ returns { string }
* /
function either ( ... args ) {
/** @type { object & {capture?: boolean} } */
const opts = stripOptionsFromArgs ( args ) ;
const joined = '('
+ ( opts . capture ? "" : "?:" )
+ args . map ( ( x ) => source ( x ) ) . join ( "|" ) + ")" ;
return joined ;
}
/ *
Language : F #
Author : Jonas Follesø < jonas @ follesoe . no >
Contributors : Troy Kershaw < hello @ troykershaw . com > , Henrik Feldt < henrik @ haf . se > , Melvyn Laïly < melvyn . laily @ gmail . com >
Website : https : //docs.microsoft.com/en-us/dotnet/fsharp/
Category : functional
* /
/** @type LanguageFn */
function fsharp ( hljs ) {
const KEYWORDS = [
"abstract" ,
"and" ,
"as" ,
"assert" ,
"base" ,
"begin" ,
"class" ,
"default" ,
"delegate" ,
"do" ,
"done" ,
"downcast" ,
"downto" ,
"elif" ,
"else" ,
"end" ,
"exception" ,
"extern" ,
// "false", // literal
"finally" ,
"fixed" ,
"for" ,
"fun" ,
"function" ,
"global" ,
"if" ,
"in" ,
"inherit" ,
"inline" ,
"interface" ,
"internal" ,
"lazy" ,
"let" ,
"match" ,
"member" ,
"module" ,
"mutable" ,
"namespace" ,
"new" ,
// "not", // built_in
// "null", // literal
"of" ,
"open" ,
"or" ,
"override" ,
"private" ,
"public" ,
"rec" ,
"return" ,
"static" ,
"struct" ,
"then" ,
"to" ,
// "true", // literal
"try" ,
"type" ,
"upcast" ,
"use" ,
"val" ,
"void" ,
"when" ,
"while" ,
"with" ,
"yield"
] ;
const BANG _KEYWORD _MODE = {
// monad builder keywords (matches before non-bang keywords)
scope : 'keyword' ,
match : /\b(yield|return|let|do|match|use)!/
} ;
const PREPROCESSOR _KEYWORDS = [
"if" ,
"else" ,
"endif" ,
"line" ,
"nowarn" ,
"light" ,
"r" ,
"i" ,
"I" ,
"load" ,
"time" ,
"help" ,
"quit"
] ;
const LITERALS = [
"true" ,
"false" ,
"null" ,
"Some" ,
"None" ,
"Ok" ,
"Error" ,
"infinity" ,
"infinityf" ,
"nan" ,
"nanf"
] ;
const SPECIAL _IDENTIFIERS = [
"__LINE__" ,
"__SOURCE_DIRECTORY__" ,
"__SOURCE_FILE__"
] ;
// Since it's possible to re-bind/shadow names (e.g. let char = 'c'),
// these builtin types should only be matched when a type name is expected.
const KNOWN _TYPES = [
// basic types
"bool" ,
"byte" ,
"sbyte" ,
"int8" ,
"int16" ,
"int32" ,
"uint8" ,
"uint16" ,
"uint32" ,
"int" ,
"uint" ,
"int64" ,
"uint64" ,
"nativeint" ,
"unativeint" ,
"decimal" ,
"float" ,
"double" ,
"float32" ,
"single" ,
"char" ,
"string" ,
"unit" ,
"bigint" ,
// other native types or lowercase aliases
"option" ,
"voption" ,
"list" ,
"array" ,
"seq" ,
"byref" ,
"exn" ,
"inref" ,
"nativeptr" ,
"obj" ,
"outref" ,
"voidptr" ,
// other important FSharp types
"Result"
] ;
const BUILTINS = [
// Somewhat arbitrary list of builtin functions and values.
// Most of them are declared in Microsoft.FSharp.Core
// I tried to stay relevant by adding only the most idiomatic
// and most used symbols that are not already declared as types.
"not" ,
"ref" ,
"raise" ,
"reraise" ,
"dict" ,
"readOnlyDict" ,
"set" ,
"get" ,
"enum" ,
"sizeof" ,
"typeof" ,
"typedefof" ,
"nameof" ,
"nullArg" ,
"invalidArg" ,
"invalidOp" ,
"id" ,
"fst" ,
"snd" ,
"ignore" ,
"lock" ,
"using" ,
"box" ,
"unbox" ,
"tryUnbox" ,
"printf" ,
"printfn" ,
"sprintf" ,
"eprintf" ,
"eprintfn" ,
"fprintf" ,
"fprintfn" ,
"failwith" ,
"failwithf"
] ;
const ALL _KEYWORDS = {
keyword : KEYWORDS ,
literal : LITERALS ,
built _in : BUILTINS ,
'variable.constant' : SPECIAL _IDENTIFIERS
} ;
// (* potentially multi-line Meta Language style comment *)
const ML _COMMENT =
hljs . COMMENT ( /\(\*(?!\))/ , /\*\)/ , {
contains : [ "self" ]
} ) ;
// Either a multi-line (* Meta Language style comment *) or a single line // C style comment.
const COMMENT = {
variants : [
ML _COMMENT ,
hljs . C _LINE _COMMENT _MODE ,
]
} ;
// Most identifiers can contain apostrophes
const IDENTIFIER _RE = /[a-zA-Z_](\w|')*/ ;
const QUOTED _IDENTIFIER = {
scope : 'variable' ,
begin : /``/ ,
end : /``/
} ;
// 'a or ^a where a can be a ``quoted identifier``
const BEGIN _GENERIC _TYPE _SYMBOL _RE = /\B('|\^)/ ;
const GENERIC _TYPE _SYMBOL = {
scope : 'symbol' ,
variants : [
// the type name is a quoted identifier:
{ match : concat ( BEGIN _GENERIC _TYPE _SYMBOL _RE , /``.*?``/ ) } ,
// the type name is a normal identifier (we don't use IDENTIFIER_RE because there cannot be another apostrophe here):
{ match : concat ( BEGIN _GENERIC _TYPE _SYMBOL _RE , hljs . UNDERSCORE _IDENT _RE ) }
] ,
relevance : 0
} ;
const makeOperatorMode = function ( { includeEqual } ) {
// List or symbolic operator characters from the FSharp Spec 4.1, minus the dot, and with `?` added, used for nullable operators.
let allOperatorChars ;
if ( includeEqual )
allOperatorChars = "!%&*+-/<=>@^|~?" ;
else
allOperatorChars = "!%&*+-/<>@^|~?" ;
const OPERATOR _CHARS = Array . from ( allOperatorChars ) ;
const OPERATOR _CHAR _RE = concat ( '[' , ... OPERATOR _CHARS . map ( escape ) , ']' ) ;
// The lone dot operator is special. It cannot be redefined, and we don't want to highlight it. It can be used as part of a multi-chars operator though.
const OPERATOR _CHAR _OR _DOT _RE = either ( OPERATOR _CHAR _RE , /\./ ) ;
// When a dot is present, it must be followed by another operator char:
const OPERATOR _FIRST _CHAR _OF _MULTIPLE _RE = concat ( OPERATOR _CHAR _OR _DOT _RE , lookahead ( OPERATOR _CHAR _OR _DOT _RE ) ) ;
const SYMBOLIC _OPERATOR _RE = either (
concat ( OPERATOR _FIRST _CHAR _OF _MULTIPLE _RE , OPERATOR _CHAR _OR _DOT _RE , '*' ) , // Matches at least 2 chars operators
concat ( OPERATOR _CHAR _RE , '+' ) , // Matches at least one char operators
) ;
return {
scope : 'operator' ,
match : either (
// symbolic operators:
SYMBOLIC _OPERATOR _RE ,
// other symbolic keywords:
// Type casting and conversion operators:
/:\?>/ ,
/:\?/ ,
/:>/ ,
/:=/ , // Reference cell assignment
/::?/ , // : or ::
/\$/ ) , // A single $ can be used as an operator
relevance : 0
} ;
} ;
const OPERATOR = makeOperatorMode ( { includeEqual : true } ) ;
// This variant is used when matching '=' should end a parent mode:
const OPERATOR _WITHOUT _EQUAL = makeOperatorMode ( { includeEqual : false } ) ;
const makeTypeAnnotationMode = function ( prefix , prefixScope ) {
return {
begin : concat ( // a type annotation is a
prefix , // should be a colon or the 'of' keyword
lookahead ( // that has to be followed by
concat (
/\s*/ , // optional space
either ( // then either of:
/\w/ , // word
/'/ , // generic type name
/\^/ , // generic type name
/#/ , // flexible type name
/``/ , // quoted type name
/\(/ , // parens type expression
/{\|/ , // anonymous type annotation
) ) ) ) ,
beginScope : prefixScope ,
// BUG: because ending with \n is necessary for some cases, multi-line type annotations are not properly supported.
// Examples where \n is required at the end:
// - abstract member definitions in classes: abstract Property : int * string
// - return type annotations: let f f' = f' () : returnTypeAnnotation
// - record fields definitions: { A : int \n B : string }
end : lookahead (
either (
/\n/ ,
/=/ ) ) ,
relevance : 0 ,
// we need the known types, and we need the type constraint keywords and literals. e.g.: when 'a : null
keywords : hljs . inherit ( ALL _KEYWORDS , { type : KNOWN _TYPES } ) ,
contains : [
COMMENT ,
GENERIC _TYPE _SYMBOL ,
hljs . inherit ( QUOTED _IDENTIFIER , { scope : null } ) , // match to avoid strange patterns inside that may break the parsing
OPERATOR _WITHOUT _EQUAL
]
} ;
} ;
const TYPE _ANNOTATION = makeTypeAnnotationMode ( /:/ , 'operator' ) ;
const DISCRIMINATED _UNION _TYPE _ANNOTATION = makeTypeAnnotationMode ( /\bof\b/ , 'keyword' ) ;
// type MyType<'a> = ...
const TYPE _DECLARATION = {
begin : [
/(^|\s+)/ , // prevents matching the following: `match s.stype with`
/type/ ,
/\s+/ ,
IDENTIFIER _RE
] ,
beginScope : {
2 : 'keyword' ,
4 : 'title.class'
} ,
end : lookahead ( /\(|=|$/ ) ,
keywords : ALL _KEYWORDS , // match keywords in type constraints. e.g.: when 'a : null
contains : [
COMMENT ,
hljs . inherit ( QUOTED _IDENTIFIER , { scope : null } ) , // match to avoid strange patterns inside that may break the parsing
GENERIC _TYPE _SYMBOL ,
{
// For visual consistency, highlight type brackets as operators.
scope : 'operator' ,
match : /<|>/
} ,
TYPE _ANNOTATION // generic types can have constraints, which are type annotations. e.g. type MyType<'T when 'T : delegate<obj * string>> =
]
} ;
const COMPUTATION _EXPRESSION = {
// computation expressions:
scope : 'computation-expression' ,
// BUG: might conflict with record deconstruction. e.g. let f { Name = name } = name // will highlight f
match : /\b[_a-z]\w*(?=\s*\{)/
} ;
const PREPROCESSOR = {
// preprocessor directives and fsi commands:
begin : [
/^\s*/ ,
concat ( /#/ , either ( ... PREPROCESSOR _KEYWORDS ) ) ,
/\b/
] ,
beginScope : { 2 : 'meta' } ,
end : lookahead ( /\s|$/ )
} ;
// TODO: this definition is missing support for type suffixes and octal notation.
// BUG: range operator without any space is wrongly interpreted as a single number (e.g. 1..10 )
const NUMBER = {
variants : [
hljs . BINARY _NUMBER _MODE ,
hljs . C _NUMBER _MODE
]
} ;
// All the following string definitions are potentially multi-line.
// BUG: these definitions are missing support for byte strings (suffixed with B)
// "..."
const QUOTED _STRING = {
scope : 'string' ,
begin : /"/ ,
end : /"/ ,
contains : [
hljs . BACKSLASH _ESCAPE
]
} ;
// @"..."
const VERBATIM _STRING = {
scope : 'string' ,
begin : /@"/ ,
end : /"/ ,
contains : [
{
match : /""/ // escaped "
} ,
hljs . BACKSLASH _ESCAPE
]
} ;
// """..."""
const TRIPLE _QUOTED _STRING = {
scope : 'string' ,
begin : /"""/ ,
end : /"""/ ,
relevance : 2
} ;
const SUBST = {
scope : 'subst' ,
begin : /\{/ ,
end : /\}/ ,
keywords : ALL _KEYWORDS
} ;
// $"...{1+1}..."
const INTERPOLATED _STRING = {
scope : 'string' ,
begin : /\$"/ ,
end : /"/ ,
contains : [
{
match : /\{\{/ // escaped {
} ,
{
match : /\}\}/ // escaped }
} ,
hljs . BACKSLASH _ESCAPE ,
SUBST
]
} ;
// $@"...{1+1}..."
const INTERPOLATED _VERBATIM _STRING = {
scope : 'string' ,
begin : /(\$@|@\$)"/ ,
end : /"/ ,
contains : [
{
match : /\{\{/ // escaped {
} ,
{
match : /\}\}/ // escaped }
} ,
{
match : /""/
} ,
hljs . BACKSLASH _ESCAPE ,
SUBST
]
} ;
// $"""...{1+1}..."""
const INTERPOLATED _TRIPLE _QUOTED _STRING = {
scope : 'string' ,
begin : /\$"""/ ,
end : /"""/ ,
contains : [
{
match : /\{\{/ // escaped {
} ,
{
match : /\}\}/ // escaped }
} ,
SUBST
] ,
relevance : 2
} ;
// '.'
const CHAR _LITERAL = {
scope : 'string' ,
match : concat (
/'/ ,
either (
/[^\\']/ , // either a single non escaped char...
/\\(?:.|\d{3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}|U[a-fA-F\d]{8})/ // ...or an escape sequence
) ,
/'/
)
} ;
// F# allows a lot of things inside string placeholders.
// Things that don't currently seem allowed by the compiler: types definition, attributes usage.
// (Strictly speaking, some of the followings are only allowed inside triple quoted interpolated strings...)
SUBST . contains = [
INTERPOLATED _VERBATIM _STRING ,
INTERPOLATED _STRING ,
VERBATIM _STRING ,
QUOTED _STRING ,
CHAR _LITERAL ,
BANG _KEYWORD _MODE ,
COMMENT ,
QUOTED _IDENTIFIER ,
TYPE _ANNOTATION ,
COMPUTATION _EXPRESSION ,
PREPROCESSOR ,
NUMBER ,
GENERIC _TYPE _SYMBOL ,
OPERATOR
] ;
const STRING = {
variants : [
INTERPOLATED _TRIPLE _QUOTED _STRING ,
INTERPOLATED _VERBATIM _STRING ,
INTERPOLATED _STRING ,
TRIPLE _QUOTED _STRING ,
VERBATIM _STRING ,
QUOTED _STRING ,
CHAR _LITERAL
]
} ;
return {
name : 'F#' ,
aliases : [
'fs' ,
'f#'
] ,
keywords : ALL _KEYWORDS ,
illegal : /\/\*/ ,
classNameAliases : {
'computation-expression' : 'keyword'
} ,
contains : [
BANG _KEYWORD _MODE ,
STRING ,
COMMENT ,
QUOTED _IDENTIFIER ,
TYPE _DECLARATION ,
{
// e.g. [<Attributes("")>] or [<``module``: MyCustomAttributeThatWorksOnModules>]
// or [<Sealed; NoEquality; NoComparison; CompiledName("FSharpAsync`1")>]
scope : 'meta' ,
begin : /\[</ ,
end : />\]/ ,
relevance : 2 ,
contains : [
QUOTED _IDENTIFIER ,
// can contain any constant value
TRIPLE _QUOTED _STRING ,
VERBATIM _STRING ,
QUOTED _STRING ,
CHAR _LITERAL ,
NUMBER
]
} ,
DISCRIMINATED _UNION _TYPE _ANNOTATION ,
TYPE _ANNOTATION ,
COMPUTATION _EXPRESSION ,
PREPROCESSOR ,
NUMBER ,
GENERIC _TYPE _SYMBOL ,
OPERATOR
]
} ;
}
module . exports = fsharp ;