fsharp.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /**
  2. * @param {string} value
  3. * @returns {RegExp}
  4. * */
  5. function escape(value) {
  6. return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm');
  7. }
  8. /**
  9. * @param {RegExp | string } re
  10. * @returns {string}
  11. */
  12. function source(re) {
  13. if (!re) return null;
  14. if (typeof re === "string") return re;
  15. return re.source;
  16. }
  17. /**
  18. * @param {RegExp | string } re
  19. * @returns {string}
  20. */
  21. function lookahead(re) {
  22. return concat('(?=', re, ')');
  23. }
  24. /**
  25. * @param {...(RegExp | string) } args
  26. * @returns {string}
  27. */
  28. function concat(...args) {
  29. const joined = args.map((x) => source(x)).join("");
  30. return joined;
  31. }
  32. /**
  33. * @param { Array<string | RegExp | Object> } args
  34. * @returns {object}
  35. */
  36. function stripOptionsFromArgs(args) {
  37. const opts = args[args.length - 1];
  38. if (typeof opts === 'object' && opts.constructor === Object) {
  39. args.splice(args.length - 1, 1);
  40. return opts;
  41. } else {
  42. return {};
  43. }
  44. }
  45. /** @typedef { {capture?: boolean} } RegexEitherOptions */
  46. /**
  47. * Any of the passed expresssions may match
  48. *
  49. * Creates a huge this | this | that | that match
  50. * @param {(RegExp | string)[] | [...(RegExp | string)[], RegexEitherOptions]} args
  51. * @returns {string}
  52. */
  53. function either(...args) {
  54. /** @type { object & {capture?: boolean} } */
  55. const opts = stripOptionsFromArgs(args);
  56. const joined = '('
  57. + (opts.capture ? "" : "?:")
  58. + args.map((x) => source(x)).join("|") + ")";
  59. return joined;
  60. }
  61. /*
  62. Language: F#
  63. Author: Jonas Follesø <jonas@follesoe.no>
  64. Contributors: Troy Kershaw <hello@troykershaw.com>, Henrik Feldt <henrik@haf.se>, Melvyn Laïly <melvyn.laily@gmail.com>
  65. Website: https://docs.microsoft.com/en-us/dotnet/fsharp/
  66. Category: functional
  67. */
  68. /** @type LanguageFn */
  69. function fsharp(hljs) {
  70. const KEYWORDS = [
  71. "abstract",
  72. "and",
  73. "as",
  74. "assert",
  75. "base",
  76. "begin",
  77. "class",
  78. "default",
  79. "delegate",
  80. "do",
  81. "done",
  82. "downcast",
  83. "downto",
  84. "elif",
  85. "else",
  86. "end",
  87. "exception",
  88. "extern",
  89. // "false", // literal
  90. "finally",
  91. "fixed",
  92. "for",
  93. "fun",
  94. "function",
  95. "global",
  96. "if",
  97. "in",
  98. "inherit",
  99. "inline",
  100. "interface",
  101. "internal",
  102. "lazy",
  103. "let",
  104. "match",
  105. "member",
  106. "module",
  107. "mutable",
  108. "namespace",
  109. "new",
  110. // "not", // built_in
  111. // "null", // literal
  112. "of",
  113. "open",
  114. "or",
  115. "override",
  116. "private",
  117. "public",
  118. "rec",
  119. "return",
  120. "static",
  121. "struct",
  122. "then",
  123. "to",
  124. // "true", // literal
  125. "try",
  126. "type",
  127. "upcast",
  128. "use",
  129. "val",
  130. "void",
  131. "when",
  132. "while",
  133. "with",
  134. "yield"
  135. ];
  136. const BANG_KEYWORD_MODE = {
  137. // monad builder keywords (matches before non-bang keywords)
  138. scope: 'keyword',
  139. match: /\b(yield|return|let|do|match|use)!/
  140. };
  141. const PREPROCESSOR_KEYWORDS = [
  142. "if",
  143. "else",
  144. "endif",
  145. "line",
  146. "nowarn",
  147. "light",
  148. "r",
  149. "i",
  150. "I",
  151. "load",
  152. "time",
  153. "help",
  154. "quit"
  155. ];
  156. const LITERALS = [
  157. "true",
  158. "false",
  159. "null",
  160. "Some",
  161. "None",
  162. "Ok",
  163. "Error",
  164. "infinity",
  165. "infinityf",
  166. "nan",
  167. "nanf"
  168. ];
  169. const SPECIAL_IDENTIFIERS = [
  170. "__LINE__",
  171. "__SOURCE_DIRECTORY__",
  172. "__SOURCE_FILE__"
  173. ];
  174. // Since it's possible to re-bind/shadow names (e.g. let char = 'c'),
  175. // these builtin types should only be matched when a type name is expected.
  176. const KNOWN_TYPES = [
  177. // basic types
  178. "bool",
  179. "byte",
  180. "sbyte",
  181. "int8",
  182. "int16",
  183. "int32",
  184. "uint8",
  185. "uint16",
  186. "uint32",
  187. "int",
  188. "uint",
  189. "int64",
  190. "uint64",
  191. "nativeint",
  192. "unativeint",
  193. "decimal",
  194. "float",
  195. "double",
  196. "float32",
  197. "single",
  198. "char",
  199. "string",
  200. "unit",
  201. "bigint",
  202. // other native types or lowercase aliases
  203. "option",
  204. "voption",
  205. "list",
  206. "array",
  207. "seq",
  208. "byref",
  209. "exn",
  210. "inref",
  211. "nativeptr",
  212. "obj",
  213. "outref",
  214. "voidptr",
  215. // other important FSharp types
  216. "Result"
  217. ];
  218. const BUILTINS = [
  219. // Somewhat arbitrary list of builtin functions and values.
  220. // Most of them are declared in Microsoft.FSharp.Core
  221. // I tried to stay relevant by adding only the most idiomatic
  222. // and most used symbols that are not already declared as types.
  223. "not",
  224. "ref",
  225. "raise",
  226. "reraise",
  227. "dict",
  228. "readOnlyDict",
  229. "set",
  230. "get",
  231. "enum",
  232. "sizeof",
  233. "typeof",
  234. "typedefof",
  235. "nameof",
  236. "nullArg",
  237. "invalidArg",
  238. "invalidOp",
  239. "id",
  240. "fst",
  241. "snd",
  242. "ignore",
  243. "lock",
  244. "using",
  245. "box",
  246. "unbox",
  247. "tryUnbox",
  248. "printf",
  249. "printfn",
  250. "sprintf",
  251. "eprintf",
  252. "eprintfn",
  253. "fprintf",
  254. "fprintfn",
  255. "failwith",
  256. "failwithf"
  257. ];
  258. const ALL_KEYWORDS = {
  259. keyword: KEYWORDS,
  260. literal: LITERALS,
  261. built_in: BUILTINS,
  262. 'variable.constant': SPECIAL_IDENTIFIERS
  263. };
  264. // (* potentially multi-line Meta Language style comment *)
  265. const ML_COMMENT =
  266. hljs.COMMENT(/\(\*(?!\))/, /\*\)/, {
  267. contains: ["self"]
  268. });
  269. // Either a multi-line (* Meta Language style comment *) or a single line // C style comment.
  270. const COMMENT = {
  271. variants: [
  272. ML_COMMENT,
  273. hljs.C_LINE_COMMENT_MODE,
  274. ]
  275. };
  276. // Most identifiers can contain apostrophes
  277. const IDENTIFIER_RE = /[a-zA-Z_](\w|')*/;
  278. const QUOTED_IDENTIFIER = {
  279. scope: 'variable',
  280. begin: /``/,
  281. end: /``/
  282. };
  283. // 'a or ^a where a can be a ``quoted identifier``
  284. const BEGIN_GENERIC_TYPE_SYMBOL_RE = /\B('|\^)/;
  285. const GENERIC_TYPE_SYMBOL = {
  286. scope: 'symbol',
  287. variants: [
  288. // the type name is a quoted identifier:
  289. { match: concat(BEGIN_GENERIC_TYPE_SYMBOL_RE, /``.*?``/) },
  290. // the type name is a normal identifier (we don't use IDENTIFIER_RE because there cannot be another apostrophe here):
  291. { match: concat(BEGIN_GENERIC_TYPE_SYMBOL_RE, hljs.UNDERSCORE_IDENT_RE) }
  292. ],
  293. relevance: 0
  294. };
  295. const makeOperatorMode = function({ includeEqual }) {
  296. // List or symbolic operator characters from the FSharp Spec 4.1, minus the dot, and with `?` added, used for nullable operators.
  297. let allOperatorChars;
  298. if (includeEqual)
  299. allOperatorChars = "!%&*+-/<=>@^|~?";
  300. else
  301. allOperatorChars = "!%&*+-/<>@^|~?";
  302. const OPERATOR_CHARS = Array.from(allOperatorChars);
  303. const OPERATOR_CHAR_RE = concat('[', ...OPERATOR_CHARS.map(escape), ']');
  304. // 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.
  305. const OPERATOR_CHAR_OR_DOT_RE = either(OPERATOR_CHAR_RE, /\./);
  306. // When a dot is present, it must be followed by another operator char:
  307. const OPERATOR_FIRST_CHAR_OF_MULTIPLE_RE = concat(OPERATOR_CHAR_OR_DOT_RE, lookahead(OPERATOR_CHAR_OR_DOT_RE));
  308. const SYMBOLIC_OPERATOR_RE = either(
  309. concat(OPERATOR_FIRST_CHAR_OF_MULTIPLE_RE, OPERATOR_CHAR_OR_DOT_RE, '*'), // Matches at least 2 chars operators
  310. concat(OPERATOR_CHAR_RE, '+'), // Matches at least one char operators
  311. );
  312. return {
  313. scope: 'operator',
  314. match: either(
  315. // symbolic operators:
  316. SYMBOLIC_OPERATOR_RE,
  317. // other symbolic keywords:
  318. // Type casting and conversion operators:
  319. /:\?>/,
  320. /:\?/,
  321. /:>/,
  322. /:=/, // Reference cell assignment
  323. /::?/, // : or ::
  324. /\$/), // A single $ can be used as an operator
  325. relevance: 0
  326. };
  327. };
  328. const OPERATOR = makeOperatorMode({ includeEqual: true });
  329. // This variant is used when matching '=' should end a parent mode:
  330. const OPERATOR_WITHOUT_EQUAL = makeOperatorMode({ includeEqual: false });
  331. const makeTypeAnnotationMode = function(prefix, prefixScope) {
  332. return {
  333. begin: concat( // a type annotation is a
  334. prefix, // should be a colon or the 'of' keyword
  335. lookahead( // that has to be followed by
  336. concat(
  337. /\s*/, // optional space
  338. either( // then either of:
  339. /\w/, // word
  340. /'/, // generic type name
  341. /\^/, // generic type name
  342. /#/, // flexible type name
  343. /``/, // quoted type name
  344. /\(/, // parens type expression
  345. /{\|/, // anonymous type annotation
  346. )))),
  347. beginScope: prefixScope,
  348. // BUG: because ending with \n is necessary for some cases, multi-line type annotations are not properly supported.
  349. // Examples where \n is required at the end:
  350. // - abstract member definitions in classes: abstract Property : int * string
  351. // - return type annotations: let f f' = f' () : returnTypeAnnotation
  352. // - record fields definitions: { A : int \n B : string }
  353. end: lookahead(
  354. either(
  355. /\n/,
  356. /=/)),
  357. relevance: 0,
  358. // we need the known types, and we need the type constraint keywords and literals. e.g.: when 'a : null
  359. keywords: hljs.inherit(ALL_KEYWORDS, { type: KNOWN_TYPES }),
  360. contains: [
  361. COMMENT,
  362. GENERIC_TYPE_SYMBOL,
  363. hljs.inherit(QUOTED_IDENTIFIER, { scope: null }), // match to avoid strange patterns inside that may break the parsing
  364. OPERATOR_WITHOUT_EQUAL
  365. ]
  366. };
  367. };
  368. const TYPE_ANNOTATION = makeTypeAnnotationMode(/:/, 'operator');
  369. const DISCRIMINATED_UNION_TYPE_ANNOTATION = makeTypeAnnotationMode(/\bof\b/, 'keyword');
  370. // type MyType<'a> = ...
  371. const TYPE_DECLARATION = {
  372. begin: [
  373. /(^|\s+)/, // prevents matching the following: `match s.stype with`
  374. /type/,
  375. /\s+/,
  376. IDENTIFIER_RE
  377. ],
  378. beginScope: {
  379. 2: 'keyword',
  380. 4: 'title.class'
  381. },
  382. end: lookahead(/\(|=|$/),
  383. keywords: ALL_KEYWORDS, // match keywords in type constraints. e.g.: when 'a : null
  384. contains: [
  385. COMMENT,
  386. hljs.inherit(QUOTED_IDENTIFIER, { scope: null }), // match to avoid strange patterns inside that may break the parsing
  387. GENERIC_TYPE_SYMBOL,
  388. {
  389. // For visual consistency, highlight type brackets as operators.
  390. scope: 'operator',
  391. match: /<|>/
  392. },
  393. TYPE_ANNOTATION // generic types can have constraints, which are type annotations. e.g. type MyType<'T when 'T : delegate<obj * string>> =
  394. ]
  395. };
  396. const COMPUTATION_EXPRESSION = {
  397. // computation expressions:
  398. scope: 'computation-expression',
  399. // BUG: might conflict with record deconstruction. e.g. let f { Name = name } = name // will highlight f
  400. match: /\b[_a-z]\w*(?=\s*\{)/
  401. };
  402. const PREPROCESSOR = {
  403. // preprocessor directives and fsi commands:
  404. begin: [
  405. /^\s*/,
  406. concat(/#/, either(...PREPROCESSOR_KEYWORDS)),
  407. /\b/
  408. ],
  409. beginScope: { 2: 'meta' },
  410. end: lookahead(/\s|$/)
  411. };
  412. // TODO: this definition is missing support for type suffixes and octal notation.
  413. // BUG: range operator without any space is wrongly interpreted as a single number (e.g. 1..10 )
  414. const NUMBER = {
  415. variants: [
  416. hljs.BINARY_NUMBER_MODE,
  417. hljs.C_NUMBER_MODE
  418. ]
  419. };
  420. // All the following string definitions are potentially multi-line.
  421. // BUG: these definitions are missing support for byte strings (suffixed with B)
  422. // "..."
  423. const QUOTED_STRING = {
  424. scope: 'string',
  425. begin: /"/,
  426. end: /"/,
  427. contains: [
  428. hljs.BACKSLASH_ESCAPE
  429. ]
  430. };
  431. // @"..."
  432. const VERBATIM_STRING = {
  433. scope: 'string',
  434. begin: /@"/,
  435. end: /"/,
  436. contains: [
  437. {
  438. match: /""/ // escaped "
  439. },
  440. hljs.BACKSLASH_ESCAPE
  441. ]
  442. };
  443. // """..."""
  444. const TRIPLE_QUOTED_STRING = {
  445. scope: 'string',
  446. begin: /"""/,
  447. end: /"""/,
  448. relevance: 2
  449. };
  450. const SUBST = {
  451. scope: 'subst',
  452. begin: /\{/,
  453. end: /\}/,
  454. keywords: ALL_KEYWORDS
  455. };
  456. // $"...{1+1}..."
  457. const INTERPOLATED_STRING = {
  458. scope: 'string',
  459. begin: /\$"/,
  460. end: /"/,
  461. contains: [
  462. {
  463. match: /\{\{/ // escaped {
  464. },
  465. {
  466. match: /\}\}/ // escaped }
  467. },
  468. hljs.BACKSLASH_ESCAPE,
  469. SUBST
  470. ]
  471. };
  472. // $@"...{1+1}..."
  473. const INTERPOLATED_VERBATIM_STRING = {
  474. scope: 'string',
  475. begin: /(\$@|@\$)"/,
  476. end: /"/,
  477. contains: [
  478. {
  479. match: /\{\{/ // escaped {
  480. },
  481. {
  482. match: /\}\}/ // escaped }
  483. },
  484. {
  485. match: /""/
  486. },
  487. hljs.BACKSLASH_ESCAPE,
  488. SUBST
  489. ]
  490. };
  491. // $"""...{1+1}..."""
  492. const INTERPOLATED_TRIPLE_QUOTED_STRING = {
  493. scope: 'string',
  494. begin: /\$"""/,
  495. end: /"""/,
  496. contains: [
  497. {
  498. match: /\{\{/ // escaped {
  499. },
  500. {
  501. match: /\}\}/ // escaped }
  502. },
  503. SUBST
  504. ],
  505. relevance: 2
  506. };
  507. // '.'
  508. const CHAR_LITERAL = {
  509. scope: 'string',
  510. match: concat(
  511. /'/,
  512. either(
  513. /[^\\']/, // either a single non escaped char...
  514. /\\(?:.|\d{3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}|U[a-fA-F\d]{8})/ // ...or an escape sequence
  515. ),
  516. /'/
  517. )
  518. };
  519. // F# allows a lot of things inside string placeholders.
  520. // Things that don't currently seem allowed by the compiler: types definition, attributes usage.
  521. // (Strictly speaking, some of the followings are only allowed inside triple quoted interpolated strings...)
  522. SUBST.contains = [
  523. INTERPOLATED_VERBATIM_STRING,
  524. INTERPOLATED_STRING,
  525. VERBATIM_STRING,
  526. QUOTED_STRING,
  527. CHAR_LITERAL,
  528. BANG_KEYWORD_MODE,
  529. COMMENT,
  530. QUOTED_IDENTIFIER,
  531. TYPE_ANNOTATION,
  532. COMPUTATION_EXPRESSION,
  533. PREPROCESSOR,
  534. NUMBER,
  535. GENERIC_TYPE_SYMBOL,
  536. OPERATOR
  537. ];
  538. const STRING = {
  539. variants: [
  540. INTERPOLATED_TRIPLE_QUOTED_STRING,
  541. INTERPOLATED_VERBATIM_STRING,
  542. INTERPOLATED_STRING,
  543. TRIPLE_QUOTED_STRING,
  544. VERBATIM_STRING,
  545. QUOTED_STRING,
  546. CHAR_LITERAL
  547. ]
  548. };
  549. return {
  550. name: 'F#',
  551. aliases: [
  552. 'fs',
  553. 'f#'
  554. ],
  555. keywords: ALL_KEYWORDS,
  556. illegal: /\/\*/,
  557. classNameAliases: {
  558. 'computation-expression': 'keyword'
  559. },
  560. contains: [
  561. BANG_KEYWORD_MODE,
  562. STRING,
  563. COMMENT,
  564. QUOTED_IDENTIFIER,
  565. TYPE_DECLARATION,
  566. {
  567. // e.g. [<Attributes("")>] or [<``module``: MyCustomAttributeThatWorksOnModules>]
  568. // or [<Sealed; NoEquality; NoComparison; CompiledName("FSharpAsync`1")>]
  569. scope: 'meta',
  570. begin: /\[</,
  571. end: />\]/,
  572. relevance: 2,
  573. contains: [
  574. QUOTED_IDENTIFIER,
  575. // can contain any constant value
  576. TRIPLE_QUOTED_STRING,
  577. VERBATIM_STRING,
  578. QUOTED_STRING,
  579. CHAR_LITERAL,
  580. NUMBER
  581. ]
  582. },
  583. DISCRIMINATED_UNION_TYPE_ANNOTATION,
  584. TYPE_ANNOTATION,
  585. COMPUTATION_EXPRESSION,
  586. PREPROCESSOR,
  587. NUMBER,
  588. GENERIC_TYPE_SYMBOL,
  589. OPERATOR
  590. ]
  591. };
  592. }
  593. export { fsharp as default };