2023-10-03 11:14:36 +08:00
|
|
|
/*
|
|
|
|
Language: Elixir
|
|
|
|
Author: Josh Adams <josh@isotope11.com>
|
|
|
|
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: /</,
|
|
|
|
end: />/
|
|
|
|
}
|
|
|
|
];
|
|
|
|
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 };
|