/* Language: Elixir Author: Josh Adams Description: language definition for Elixir source code files (.ex and .exs). Based on ruby language support. Category: functional Website: https://elixir-lang.org */ /** @type LanguageFn */ function elixir(hljs) { const regex = hljs.regex; const ELIXIR_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_.]*(!|\\?)?'; const ELIXIR_METHOD_RE = '[a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?'; const KEYWORDS = [ "after", "alias", "and", "case", "catch", "cond", "defstruct", "defguard", "do", "else", "end", "fn", "for", "if", "import", "in", "not", "or", "quote", "raise", "receive", "require", "reraise", "rescue", "try", "unless", "unquote", "unquote_splicing", "use", "when", "with|0" ]; const LITERALS = [ "false", "nil", "true" ]; const KWS = { $pattern: ELIXIR_IDENT_RE, keyword: KEYWORDS, literal: LITERALS }; const SUBST = { className: 'subst', begin: /#\{/, end: /\}/, keywords: KWS }; const NUMBER = { className: 'number', begin: '(\\b0o[0-7_]+)|(\\b0b[01_]+)|(\\b0x[0-9a-fA-F_]+)|(-?\\b[0-9][0-9_]*(\\.[0-9_]+([eE][-+]?[0-9]+)?)?)', relevance: 0 }; // TODO: could be tightened // https://elixir-lang.readthedocs.io/en/latest/intro/18.html // but you also need to include closing delemeters in the escape list per // individual sigil mode from what I can tell, // ie: \} might or might not be an escape depending on the sigil used const ESCAPES_RE = /\\[\s\S]/; // const ESCAPES_RE = /\\["'\\abdefnrstv0]/; const BACKSLASH_ESCAPE = { match: ESCAPES_RE, scope: "char.escape", relevance: 0 }; const SIGIL_DELIMITERS = '[/|([{<"\']'; const SIGIL_DELIMITER_MODES = [ { begin: /"/, end: /"/ }, { begin: /'/, end: /'/ }, { begin: /\//, end: /\// }, { begin: /\|/, end: /\|/ }, { begin: /\(/, end: /\)/ }, { begin: /\[/, end: /\]/ }, { begin: /\{/, end: /\}/ }, { begin: // } ]; const escapeSigilEnd = (end) => { return { scope: "char.escape", begin: regex.concat(/\\/, end), relevance: 0 }; }; const LOWERCASE_SIGIL = { className: 'string', begin: '~[a-z]' + '(?=' + SIGIL_DELIMITERS + ')', contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x, { contains: [ escapeSigilEnd(x.end), BACKSLASH_ESCAPE, SUBST ] } )) }; const UPCASE_SIGIL = { className: 'string', begin: '~[A-Z]' + '(?=' + SIGIL_DELIMITERS + ')', contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x, { contains: [ escapeSigilEnd(x.end) ] } )) }; const REGEX_SIGIL = { className: 'regex', variants: [ { begin: '~r' + '(?=' + SIGIL_DELIMITERS + ')', contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x, { end: regex.concat(x.end, /[uismxfU]{0,7}/), contains: [ escapeSigilEnd(x.end), BACKSLASH_ESCAPE, SUBST ] } )) }, { begin: '~R' + '(?=' + SIGIL_DELIMITERS + ')', contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x, { end: regex.concat(x.end, /[uismxfU]{0,7}/), contains: [ escapeSigilEnd(x.end) ] }) ) } ] }; const STRING = { className: 'string', contains: [ hljs.BACKSLASH_ESCAPE, SUBST ], variants: [ { begin: /"""/, end: /"""/ }, { begin: /'''/, end: /'''/ }, { begin: /~S"""/, end: /"""/, contains: [] // override default }, { begin: /~S"/, end: /"/, contains: [] // override default }, { begin: /~S'''/, end: /'''/, contains: [] // override default }, { begin: /~S'/, end: /'/, contains: [] // override default }, { begin: /'/, end: /'/ }, { begin: /"/, end: /"/ } ] }; const FUNCTION = { className: 'function', beginKeywords: 'def defp defmacro defmacrop', end: /\B\b/, // the mode is ended by the title contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: ELIXIR_IDENT_RE, endsParent: true }) ] }; const CLASS = hljs.inherit(FUNCTION, { className: 'class', beginKeywords: 'defimpl defmodule defprotocol defrecord', end: /\bdo\b|$|;/ }); const ELIXIR_DEFAULT_CONTAINS = [ STRING, REGEX_SIGIL, UPCASE_SIGIL, LOWERCASE_SIGIL, hljs.HASH_COMMENT_MODE, CLASS, FUNCTION, { begin: '::' }, { className: 'symbol', begin: ':(?![\\s:])', contains: [ STRING, { begin: ELIXIR_METHOD_RE } ], relevance: 0 }, { className: 'symbol', begin: ELIXIR_IDENT_RE + ':(?!:)', relevance: 0 }, { // Usage of a module, struct, etc. className: 'title.class', begin: /(\b[A-Z][a-zA-Z0-9_]+)/, relevance: 0 }, NUMBER, { className: 'variable', begin: '(\\$\\W)|((\\$|@@?)(\\w+))' } // -> has been removed, capnproto always uses this grammar construct ]; SUBST.contains = ELIXIR_DEFAULT_CONTAINS; return { name: 'Elixir', aliases: [ 'ex', 'exs' ], keywords: KWS, contains: ELIXIR_DEFAULT_CONTAINS }; } export { elixir as default };