373 lines
6.8 KiB
JavaScript
373 lines
6.8 KiB
JavaScript
/*
|
|
Language: Nix
|
|
Author: Domen Kožar <domen@dev.si>
|
|
Description: Nix functional language
|
|
Website: http://nixos.org/nix
|
|
Category: system
|
|
*/
|
|
|
|
/** @type LanguageFn */
|
|
function nix(hljs) {
|
|
const regex = hljs.regex;
|
|
const KEYWORDS = {
|
|
keyword: [
|
|
"assert",
|
|
"else",
|
|
"if",
|
|
"in",
|
|
"inherit",
|
|
"let",
|
|
"or",
|
|
"rec",
|
|
"then",
|
|
"with",
|
|
],
|
|
literal: [
|
|
"true",
|
|
"false",
|
|
"null",
|
|
],
|
|
built_in: [
|
|
// toplevel builtins
|
|
"abort",
|
|
"baseNameOf",
|
|
"builtins",
|
|
"derivation",
|
|
"derivationStrict",
|
|
"dirOf",
|
|
"fetchGit",
|
|
"fetchMercurial",
|
|
"fetchTarball",
|
|
"fetchTree",
|
|
"fromTOML",
|
|
"import",
|
|
"isNull",
|
|
"map",
|
|
"placeholder",
|
|
"removeAttrs",
|
|
"scopedImport",
|
|
"throw",
|
|
"toString",
|
|
],
|
|
};
|
|
|
|
const BUILTINS = {
|
|
scope: 'built_in',
|
|
match: regex.either(...[
|
|
"abort",
|
|
"add",
|
|
"addDrvOutputDependencies",
|
|
"addErrorContext",
|
|
"all",
|
|
"any",
|
|
"appendContext",
|
|
"attrNames",
|
|
"attrValues",
|
|
"baseNameOf",
|
|
"bitAnd",
|
|
"bitOr",
|
|
"bitXor",
|
|
"break",
|
|
"builtins",
|
|
"catAttrs",
|
|
"ceil",
|
|
"compareVersions",
|
|
"concatLists",
|
|
"concatMap",
|
|
"concatStringsSep",
|
|
"convertHash",
|
|
"currentSystem",
|
|
"currentTime",
|
|
"deepSeq",
|
|
"derivation",
|
|
"derivationStrict",
|
|
"dirOf",
|
|
"div",
|
|
"elem",
|
|
"elemAt",
|
|
"false",
|
|
"fetchGit",
|
|
"fetchMercurial",
|
|
"fetchTarball",
|
|
"fetchTree",
|
|
"fetchurl",
|
|
"filter",
|
|
"filterSource",
|
|
"findFile",
|
|
"flakeRefToString",
|
|
"floor",
|
|
"foldl'",
|
|
"fromJSON",
|
|
"fromTOML",
|
|
"functionArgs",
|
|
"genList",
|
|
"genericClosure",
|
|
"getAttr",
|
|
"getContext",
|
|
"getEnv",
|
|
"getFlake",
|
|
"groupBy",
|
|
"hasAttr",
|
|
"hasContext",
|
|
"hashFile",
|
|
"hashString",
|
|
"head",
|
|
"import",
|
|
"intersectAttrs",
|
|
"isAttrs",
|
|
"isBool",
|
|
"isFloat",
|
|
"isFunction",
|
|
"isInt",
|
|
"isList",
|
|
"isNull",
|
|
"isPath",
|
|
"isString",
|
|
"langVersion",
|
|
"length",
|
|
"lessThan",
|
|
"listToAttrs",
|
|
"map",
|
|
"mapAttrs",
|
|
"match",
|
|
"mul",
|
|
"nixPath",
|
|
"nixVersion",
|
|
"null",
|
|
"parseDrvName",
|
|
"parseFlakeRef",
|
|
"partition",
|
|
"path",
|
|
"pathExists",
|
|
"placeholder",
|
|
"readDir",
|
|
"readFile",
|
|
"readFileType",
|
|
"removeAttrs",
|
|
"replaceStrings",
|
|
"scopedImport",
|
|
"seq",
|
|
"sort",
|
|
"split",
|
|
"splitVersion",
|
|
"storeDir",
|
|
"storePath",
|
|
"stringLength",
|
|
"sub",
|
|
"substring",
|
|
"tail",
|
|
"throw",
|
|
"toFile",
|
|
"toJSON",
|
|
"toPath",
|
|
"toString",
|
|
"toXML",
|
|
"trace",
|
|
"traceVerbose",
|
|
"true",
|
|
"tryEval",
|
|
"typeOf",
|
|
"unsafeDiscardOutputDependency",
|
|
"unsafeDiscardStringContext",
|
|
"unsafeGetAttrPos",
|
|
"warn",
|
|
"zipAttrsWith",
|
|
].map(b => `builtins\\.${b}`)),
|
|
relevance: 10,
|
|
};
|
|
|
|
const IDENTIFIER_REGEX = '[A-Za-z_][A-Za-z0-9_\'-]*';
|
|
|
|
const LOOKUP_PATH = {
|
|
scope: 'symbol',
|
|
match: new RegExp(`<${IDENTIFIER_REGEX}(/${IDENTIFIER_REGEX})*>`),
|
|
};
|
|
|
|
const PATH_PIECE = "[A-Za-z0-9_\\+\\.-]+";
|
|
const PATH = {
|
|
scope: 'symbol',
|
|
match: new RegExp(`(\\.\\.|\\.|~)?/(${PATH_PIECE})?(/${PATH_PIECE})*(?=[\\s;])`),
|
|
};
|
|
|
|
const OPERATOR_WITHOUT_MINUS_REGEX = regex.either(...[
|
|
'==',
|
|
'=',
|
|
'\\+\\+',
|
|
'\\+',
|
|
'<=',
|
|
'<\\|',
|
|
'<',
|
|
'>=',
|
|
'>',
|
|
'->',
|
|
'//',
|
|
'/',
|
|
'!=',
|
|
'!',
|
|
'\\|\\|',
|
|
'\\|>',
|
|
'\\?',
|
|
'\\*',
|
|
'&&',
|
|
]);
|
|
|
|
const OPERATOR = {
|
|
scope: 'operator',
|
|
match: regex.concat(OPERATOR_WITHOUT_MINUS_REGEX, /(?!-)/),
|
|
relevance: 0,
|
|
};
|
|
|
|
// '-' is being handled by itself to ensure we are able to tell the difference
|
|
// between a dash in an identifier and a minus operator
|
|
const NUMBER = {
|
|
scope: 'number',
|
|
match: new RegExp(`${hljs.NUMBER_RE}(?!-)`),
|
|
relevance: 0,
|
|
};
|
|
const MINUS_OPERATOR = {
|
|
variants: [
|
|
{
|
|
scope: 'operator',
|
|
beforeMatch: /\s/,
|
|
// The (?!>) is used to ensure this doesn't collide with the '->' operator
|
|
begin: /-(?!>)/,
|
|
},
|
|
{
|
|
begin: [
|
|
new RegExp(`${hljs.NUMBER_RE}`),
|
|
/-/,
|
|
/(?!>)/,
|
|
],
|
|
beginScope: {
|
|
1: 'number',
|
|
2: 'operator'
|
|
},
|
|
},
|
|
{
|
|
begin: [
|
|
OPERATOR_WITHOUT_MINUS_REGEX,
|
|
/-/,
|
|
/(?!>)/,
|
|
],
|
|
beginScope: {
|
|
1: 'operator',
|
|
2: 'operator'
|
|
},
|
|
},
|
|
],
|
|
relevance: 0,
|
|
};
|
|
|
|
const ATTRS = {
|
|
beforeMatch: /(^|\{|;)\s*/,
|
|
begin: new RegExp(`${IDENTIFIER_REGEX}(\\.${IDENTIFIER_REGEX})*\\s*=(?!=)`),
|
|
returnBegin: true,
|
|
relevance: 0,
|
|
contains: [
|
|
{
|
|
scope: 'attr',
|
|
match: new RegExp(`${IDENTIFIER_REGEX}(\\.${IDENTIFIER_REGEX})*(?=\\s*=)`),
|
|
relevance: 0.2,
|
|
}
|
|
],
|
|
};
|
|
|
|
const NORMAL_ESCAPED_DOLLAR = {
|
|
scope: 'char.escape',
|
|
match: /\\\$/,
|
|
};
|
|
const INDENTED_ESCAPED_DOLLAR = {
|
|
scope: 'char.escape',
|
|
match: /''\$/,
|
|
};
|
|
const ANTIQUOTE = {
|
|
scope: 'subst',
|
|
begin: /\$\{/,
|
|
end: /\}/,
|
|
keywords: KEYWORDS,
|
|
};
|
|
const ESCAPED_DOUBLEQUOTE = {
|
|
scope: 'char.escape',
|
|
match: /'''/,
|
|
};
|
|
const ESCAPED_LITERAL = {
|
|
scope: 'char.escape',
|
|
match: /\\(?!\$)./,
|
|
};
|
|
const STRING = {
|
|
scope: 'string',
|
|
variants: [
|
|
{
|
|
begin: "''",
|
|
end: "''",
|
|
contains: [
|
|
INDENTED_ESCAPED_DOLLAR,
|
|
ANTIQUOTE,
|
|
ESCAPED_DOUBLEQUOTE,
|
|
ESCAPED_LITERAL,
|
|
],
|
|
},
|
|
{
|
|
begin: '"',
|
|
end: '"',
|
|
contains: [
|
|
NORMAL_ESCAPED_DOLLAR,
|
|
ANTIQUOTE,
|
|
ESCAPED_LITERAL,
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const FUNCTION_PARAMS = {
|
|
scope: 'params',
|
|
match: new RegExp(`${IDENTIFIER_REGEX}\\s*:(?=\\s)`),
|
|
};
|
|
|
|
const EXPRESSIONS = [
|
|
NUMBER,
|
|
hljs.HASH_COMMENT_MODE,
|
|
hljs.C_BLOCK_COMMENT_MODE,
|
|
hljs.COMMENT(
|
|
/\/\*\*(?!\/)/,
|
|
/\*\//,
|
|
{
|
|
subLanguage: 'markdown',
|
|
relevance: 0
|
|
}
|
|
),
|
|
BUILTINS,
|
|
STRING,
|
|
LOOKUP_PATH,
|
|
PATH,
|
|
FUNCTION_PARAMS,
|
|
ATTRS,
|
|
MINUS_OPERATOR,
|
|
OPERATOR,
|
|
];
|
|
|
|
ANTIQUOTE.contains = EXPRESSIONS;
|
|
|
|
const REPL = [
|
|
{
|
|
scope: 'meta.prompt',
|
|
match: /^nix-repl>(?=\s)/,
|
|
relevance: 10,
|
|
},
|
|
{
|
|
scope: 'meta',
|
|
beforeMatch: /\s+/,
|
|
begin: /:([a-z]+|\?)/,
|
|
},
|
|
];
|
|
|
|
return {
|
|
name: 'Nix',
|
|
aliases: [ "nixos" ],
|
|
keywords: KEYWORDS,
|
|
contains: EXPRESSIONS.concat(REPL),
|
|
};
|
|
}
|
|
|
|
module.exports = nix;
|