highlight.js
Version:
Syntax highlighting with language autodetection.
305 lines (294 loc) • 5.99 kB
JavaScript
/**
* @param {string} value
* @returns {RegExp}
* */
/**
* @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) } args
* @returns {string}
*/
function concat(...args) {
const joined = args.map((x) => source(x)).join("");
return joined;
}
/*
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 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",
"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: 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: 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: 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
},
NUMBER,
{
className: 'variable',
begin: '(\\$\\W)|((\\$|@@?)(\\w+))'
},
{
begin: '->'
}
];
SUBST.contains = ELIXIR_DEFAULT_CONTAINS;
return {
name: 'Elixir',
aliases: ['ex', 'exs'],
keywords: KWS,
contains: ELIXIR_DEFAULT_CONTAINS
};
}
export default elixir;