mirror of https://github.com/jkjoy/sunpeiwen.git
605 lines
14 KiB
JavaScript
605 lines
14 KiB
JavaScript
/*
|
|
Language: PHP
|
|
Author: Victor Karamzin <Victor.Karamzin@enterra-inc.com>
|
|
Contributors: Evgeny Stepanischev <imbolk@gmail.com>, Ivan Sagalaev <maniac@softwaremaniacs.org>
|
|
Website: https://www.php.net
|
|
Category: common
|
|
*/
|
|
|
|
/**
|
|
* @param {HLJSApi} hljs
|
|
* @returns {LanguageDetail}
|
|
* */
|
|
function php(hljs) {
|
|
const regex = hljs.regex;
|
|
// negative look-ahead tries to avoid matching patterns that are not
|
|
// Perl at all like $ident$, @ident@, etc.
|
|
const NOT_PERL_ETC = /(?![A-Za-z0-9])(?![$])/;
|
|
const IDENT_RE = regex.concat(
|
|
/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,
|
|
NOT_PERL_ETC);
|
|
// Will not detect camelCase classes
|
|
const PASCAL_CASE_CLASS_NAME_RE = regex.concat(
|
|
/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,
|
|
NOT_PERL_ETC);
|
|
const VARIABLE = {
|
|
scope: 'variable',
|
|
match: '\\$+' + IDENT_RE,
|
|
};
|
|
const PREPROCESSOR = {
|
|
scope: 'meta',
|
|
variants: [
|
|
{ begin: /<\?php/, relevance: 10 }, // boost for obvious PHP
|
|
{ begin: /<\?=/ },
|
|
// less relevant per PSR-1 which says not to use short-tags
|
|
{ begin: /<\?/, relevance: 0.1 },
|
|
{ begin: /\?>/ } // end php tag
|
|
]
|
|
};
|
|
const SUBST = {
|
|
scope: 'subst',
|
|
variants: [
|
|
{ begin: /\$\w+/ },
|
|
{
|
|
begin: /\{\$/,
|
|
end: /\}/
|
|
}
|
|
]
|
|
};
|
|
const SINGLE_QUOTED = hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null, });
|
|
const DOUBLE_QUOTED = hljs.inherit(hljs.QUOTE_STRING_MODE, {
|
|
illegal: null,
|
|
contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
|
|
});
|
|
const HEREDOC = hljs.END_SAME_AS_BEGIN({
|
|
begin: /<<<[ \t]*(\w+)\n/,
|
|
end: /[ \t]*(\w+)\b/,
|
|
contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
|
|
});
|
|
// list of valid whitespaces because non-breaking space might be part of a IDENT_RE
|
|
const WHITESPACE = '[ \t\n]';
|
|
const STRING = {
|
|
scope: 'string',
|
|
variants: [
|
|
DOUBLE_QUOTED,
|
|
SINGLE_QUOTED,
|
|
HEREDOC
|
|
]
|
|
};
|
|
const NUMBER = {
|
|
scope: 'number',
|
|
variants: [
|
|
{ begin: `\\b0[bB][01]+(?:_[01]+)*\\b` }, // Binary w/ underscore support
|
|
{ begin: `\\b0[oO][0-7]+(?:_[0-7]+)*\\b` }, // Octals w/ underscore support
|
|
{ begin: `\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b` }, // Hex w/ underscore support
|
|
// Decimals w/ underscore support, with optional fragments and scientific exponent (e) suffix.
|
|
{ begin: `(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?` }
|
|
],
|
|
relevance: 0
|
|
};
|
|
const LITERALS = [
|
|
"false",
|
|
"null",
|
|
"true"
|
|
];
|
|
const KWS = [
|
|
// Magic constants:
|
|
// <https://www.php.net/manual/en/language.constants.predefined.php>
|
|
"__CLASS__",
|
|
"__DIR__",
|
|
"__FILE__",
|
|
"__FUNCTION__",
|
|
"__COMPILER_HALT_OFFSET__",
|
|
"__LINE__",
|
|
"__METHOD__",
|
|
"__NAMESPACE__",
|
|
"__TRAIT__",
|
|
// Function that look like language construct or language construct that look like function:
|
|
// List of keywords that may not require parenthesis
|
|
"die",
|
|
"echo",
|
|
"exit",
|
|
"include",
|
|
"include_once",
|
|
"print",
|
|
"require",
|
|
"require_once",
|
|
// These are not language construct (function) but operate on the currently-executing function and can access the current symbol table
|
|
// 'compact extract func_get_arg func_get_args func_num_args get_called_class get_parent_class ' +
|
|
// Other keywords:
|
|
// <https://www.php.net/manual/en/reserved.php>
|
|
// <https://www.php.net/manual/en/language.types.type-juggling.php>
|
|
"array",
|
|
"abstract",
|
|
"and",
|
|
"as",
|
|
"binary",
|
|
"bool",
|
|
"boolean",
|
|
"break",
|
|
"callable",
|
|
"case",
|
|
"catch",
|
|
"class",
|
|
"clone",
|
|
"const",
|
|
"continue",
|
|
"declare",
|
|
"default",
|
|
"do",
|
|
"double",
|
|
"else",
|
|
"elseif",
|
|
"empty",
|
|
"enddeclare",
|
|
"endfor",
|
|
"endforeach",
|
|
"endif",
|
|
"endswitch",
|
|
"endwhile",
|
|
"enum",
|
|
"eval",
|
|
"extends",
|
|
"final",
|
|
"finally",
|
|
"float",
|
|
"for",
|
|
"foreach",
|
|
"from",
|
|
"global",
|
|
"goto",
|
|
"if",
|
|
"implements",
|
|
"instanceof",
|
|
"insteadof",
|
|
"int",
|
|
"integer",
|
|
"interface",
|
|
"isset",
|
|
"iterable",
|
|
"list",
|
|
"match|0",
|
|
"mixed",
|
|
"new",
|
|
"never",
|
|
"object",
|
|
"or",
|
|
"private",
|
|
"protected",
|
|
"public",
|
|
"readonly",
|
|
"real",
|
|
"return",
|
|
"string",
|
|
"switch",
|
|
"throw",
|
|
"trait",
|
|
"try",
|
|
"unset",
|
|
"use",
|
|
"var",
|
|
"void",
|
|
"while",
|
|
"xor",
|
|
"yield"
|
|
];
|
|
|
|
const BUILT_INS = [
|
|
// Standard PHP library:
|
|
// <https://www.php.net/manual/en/book.spl.php>
|
|
"Error|0",
|
|
"AppendIterator",
|
|
"ArgumentCountError",
|
|
"ArithmeticError",
|
|
"ArrayIterator",
|
|
"ArrayObject",
|
|
"AssertionError",
|
|
"BadFunctionCallException",
|
|
"BadMethodCallException",
|
|
"CachingIterator",
|
|
"CallbackFilterIterator",
|
|
"CompileError",
|
|
"Countable",
|
|
"DirectoryIterator",
|
|
"DivisionByZeroError",
|
|
"DomainException",
|
|
"EmptyIterator",
|
|
"ErrorException",
|
|
"Exception",
|
|
"FilesystemIterator",
|
|
"FilterIterator",
|
|
"GlobIterator",
|
|
"InfiniteIterator",
|
|
"InvalidArgumentException",
|
|
"IteratorIterator",
|
|
"LengthException",
|
|
"LimitIterator",
|
|
"LogicException",
|
|
"MultipleIterator",
|
|
"NoRewindIterator",
|
|
"OutOfBoundsException",
|
|
"OutOfRangeException",
|
|
"OuterIterator",
|
|
"OverflowException",
|
|
"ParentIterator",
|
|
"ParseError",
|
|
"RangeException",
|
|
"RecursiveArrayIterator",
|
|
"RecursiveCachingIterator",
|
|
"RecursiveCallbackFilterIterator",
|
|
"RecursiveDirectoryIterator",
|
|
"RecursiveFilterIterator",
|
|
"RecursiveIterator",
|
|
"RecursiveIteratorIterator",
|
|
"RecursiveRegexIterator",
|
|
"RecursiveTreeIterator",
|
|
"RegexIterator",
|
|
"RuntimeException",
|
|
"SeekableIterator",
|
|
"SplDoublyLinkedList",
|
|
"SplFileInfo",
|
|
"SplFileObject",
|
|
"SplFixedArray",
|
|
"SplHeap",
|
|
"SplMaxHeap",
|
|
"SplMinHeap",
|
|
"SplObjectStorage",
|
|
"SplObserver",
|
|
"SplPriorityQueue",
|
|
"SplQueue",
|
|
"SplStack",
|
|
"SplSubject",
|
|
"SplTempFileObject",
|
|
"TypeError",
|
|
"UnderflowException",
|
|
"UnexpectedValueException",
|
|
"UnhandledMatchError",
|
|
// Reserved interfaces:
|
|
// <https://www.php.net/manual/en/reserved.interfaces.php>
|
|
"ArrayAccess",
|
|
"BackedEnum",
|
|
"Closure",
|
|
"Fiber",
|
|
"Generator",
|
|
"Iterator",
|
|
"IteratorAggregate",
|
|
"Serializable",
|
|
"Stringable",
|
|
"Throwable",
|
|
"Traversable",
|
|
"UnitEnum",
|
|
"WeakReference",
|
|
"WeakMap",
|
|
// Reserved classes:
|
|
// <https://www.php.net/manual/en/reserved.classes.php>
|
|
"Directory",
|
|
"__PHP_Incomplete_Class",
|
|
"parent",
|
|
"php_user_filter",
|
|
"self",
|
|
"static",
|
|
"stdClass"
|
|
];
|
|
|
|
/** Dual-case keywords
|
|
*
|
|
* ["then","FILE"] =>
|
|
* ["then", "THEN", "FILE", "file"]
|
|
*
|
|
* @param {string[]} items */
|
|
const dualCase = (items) => {
|
|
/** @type string[] */
|
|
const result = [];
|
|
items.forEach(item => {
|
|
result.push(item);
|
|
if (item.toLowerCase() === item) {
|
|
result.push(item.toUpperCase());
|
|
} else {
|
|
result.push(item.toLowerCase());
|
|
}
|
|
});
|
|
return result;
|
|
};
|
|
|
|
const KEYWORDS = {
|
|
keyword: KWS,
|
|
literal: dualCase(LITERALS),
|
|
built_in: BUILT_INS,
|
|
};
|
|
|
|
/**
|
|
* @param {string[]} items */
|
|
const normalizeKeywords = (items) => {
|
|
return items.map(item => {
|
|
return item.replace(/\|\d+$/, "");
|
|
});
|
|
};
|
|
|
|
const CONSTRUCTOR_CALL = { variants: [
|
|
{
|
|
match: [
|
|
/new/,
|
|
regex.concat(WHITESPACE, "+"),
|
|
// to prevent built ins from being confused as the class constructor call
|
|
regex.concat("(?!", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
|
|
PASCAL_CASE_CLASS_NAME_RE,
|
|
],
|
|
scope: {
|
|
1: "keyword",
|
|
4: "title.class",
|
|
},
|
|
}
|
|
] };
|
|
|
|
const CONSTANT_REFERENCE = regex.concat(IDENT_RE, "\\b(?!\\()");
|
|
|
|
const LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON = { variants: [
|
|
{
|
|
match: [
|
|
regex.concat(
|
|
/::/,
|
|
regex.lookahead(/(?!class\b)/)
|
|
),
|
|
CONSTANT_REFERENCE,
|
|
],
|
|
scope: { 2: "variable.constant", },
|
|
},
|
|
{
|
|
match: [
|
|
/::/,
|
|
/class/,
|
|
],
|
|
scope: { 2: "variable.language", },
|
|
},
|
|
{
|
|
match: [
|
|
PASCAL_CASE_CLASS_NAME_RE,
|
|
regex.concat(
|
|
/::/,
|
|
regex.lookahead(/(?!class\b)/)
|
|
),
|
|
CONSTANT_REFERENCE,
|
|
],
|
|
scope: {
|
|
1: "title.class",
|
|
3: "variable.constant",
|
|
},
|
|
},
|
|
{
|
|
match: [
|
|
PASCAL_CASE_CLASS_NAME_RE,
|
|
regex.concat(
|
|
"::",
|
|
regex.lookahead(/(?!class\b)/)
|
|
),
|
|
],
|
|
scope: { 1: "title.class", },
|
|
},
|
|
{
|
|
match: [
|
|
PASCAL_CASE_CLASS_NAME_RE,
|
|
/::/,
|
|
/class/,
|
|
],
|
|
scope: {
|
|
1: "title.class",
|
|
3: "variable.language",
|
|
},
|
|
}
|
|
] };
|
|
|
|
const NAMED_ARGUMENT = {
|
|
scope: 'attr',
|
|
match: regex.concat(IDENT_RE, regex.lookahead(':'), regex.lookahead(/(?!::)/)),
|
|
};
|
|
const PARAMS_MODE = {
|
|
relevance: 0,
|
|
begin: /\(/,
|
|
end: /\)/,
|
|
keywords: KEYWORDS,
|
|
contains: [
|
|
NAMED_ARGUMENT,
|
|
VARIABLE,
|
|
LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
|
|
hljs.C_BLOCK_COMMENT_MODE,
|
|
STRING,
|
|
NUMBER,
|
|
CONSTRUCTOR_CALL,
|
|
],
|
|
};
|
|
const FUNCTION_INVOKE = {
|
|
relevance: 0,
|
|
match: [
|
|
/\b/,
|
|
// to prevent keywords from being confused as the function title
|
|
regex.concat("(?!fn\\b|function\\b|", normalizeKeywords(KWS).join("\\b|"), "|", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
|
|
IDENT_RE,
|
|
regex.concat(WHITESPACE, "*"),
|
|
regex.lookahead(/(?=\()/)
|
|
],
|
|
scope: { 3: "title.function.invoke", },
|
|
contains: [ PARAMS_MODE ]
|
|
};
|
|
PARAMS_MODE.contains.push(FUNCTION_INVOKE);
|
|
|
|
const ATTRIBUTE_CONTAINS = [
|
|
NAMED_ARGUMENT,
|
|
LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
|
|
hljs.C_BLOCK_COMMENT_MODE,
|
|
STRING,
|
|
NUMBER,
|
|
CONSTRUCTOR_CALL,
|
|
];
|
|
|
|
const ATTRIBUTES = {
|
|
begin: regex.concat(/#\[\s*/, PASCAL_CASE_CLASS_NAME_RE),
|
|
beginScope: "meta",
|
|
end: /]/,
|
|
endScope: "meta",
|
|
keywords: {
|
|
literal: LITERALS,
|
|
keyword: [
|
|
'new',
|
|
'array',
|
|
]
|
|
},
|
|
contains: [
|
|
{
|
|
begin: /\[/,
|
|
end: /]/,
|
|
keywords: {
|
|
literal: LITERALS,
|
|
keyword: [
|
|
'new',
|
|
'array',
|
|
]
|
|
},
|
|
contains: [
|
|
'self',
|
|
...ATTRIBUTE_CONTAINS,
|
|
]
|
|
},
|
|
...ATTRIBUTE_CONTAINS,
|
|
{
|
|
scope: 'meta',
|
|
match: PASCAL_CASE_CLASS_NAME_RE
|
|
}
|
|
]
|
|
};
|
|
|
|
return {
|
|
case_insensitive: false,
|
|
keywords: KEYWORDS,
|
|
contains: [
|
|
ATTRIBUTES,
|
|
hljs.HASH_COMMENT_MODE,
|
|
hljs.COMMENT('//', '$'),
|
|
hljs.COMMENT(
|
|
'/\\*',
|
|
'\\*/',
|
|
{ contains: [
|
|
{
|
|
scope: 'doctag',
|
|
match: '@[A-Za-z]+'
|
|
}
|
|
] }
|
|
),
|
|
{
|
|
match: /__halt_compiler\(\);/,
|
|
keywords: '__halt_compiler',
|
|
starts: {
|
|
scope: "comment",
|
|
end: hljs.MATCH_NOTHING_RE,
|
|
contains: [
|
|
{
|
|
match: /\?>/,
|
|
scope: "meta",
|
|
endsParent: true
|
|
}
|
|
]
|
|
}
|
|
},
|
|
PREPROCESSOR,
|
|
{
|
|
scope: 'variable.language',
|
|
match: /\$this\b/
|
|
},
|
|
VARIABLE,
|
|
FUNCTION_INVOKE,
|
|
LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
|
|
{
|
|
match: [
|
|
/const/,
|
|
/\s/,
|
|
IDENT_RE,
|
|
],
|
|
scope: {
|
|
1: "keyword",
|
|
3: "variable.constant",
|
|
},
|
|
},
|
|
CONSTRUCTOR_CALL,
|
|
{
|
|
scope: 'function',
|
|
relevance: 0,
|
|
beginKeywords: 'fn function',
|
|
end: /[;{]/,
|
|
excludeEnd: true,
|
|
illegal: '[$%\\[]',
|
|
contains: [
|
|
{ beginKeywords: 'use', },
|
|
hljs.UNDERSCORE_TITLE_MODE,
|
|
{
|
|
begin: '=>', // No markup, just a relevance booster
|
|
endsParent: true
|
|
},
|
|
{
|
|
scope: 'params',
|
|
begin: '\\(',
|
|
end: '\\)',
|
|
excludeBegin: true,
|
|
excludeEnd: true,
|
|
keywords: KEYWORDS,
|
|
contains: [
|
|
'self',
|
|
VARIABLE,
|
|
LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
|
|
hljs.C_BLOCK_COMMENT_MODE,
|
|
STRING,
|
|
NUMBER
|
|
]
|
|
},
|
|
]
|
|
},
|
|
{
|
|
scope: 'class',
|
|
variants: [
|
|
{
|
|
beginKeywords: "enum",
|
|
illegal: /[($"]/
|
|
},
|
|
{
|
|
beginKeywords: "class interface trait",
|
|
illegal: /[:($"]/
|
|
}
|
|
],
|
|
relevance: 0,
|
|
end: /\{/,
|
|
excludeEnd: true,
|
|
contains: [
|
|
{ beginKeywords: 'extends implements' },
|
|
hljs.UNDERSCORE_TITLE_MODE
|
|
]
|
|
},
|
|
// both use and namespace still use "old style" rules (vs multi-match)
|
|
// because the namespace name can include `\` and we still want each
|
|
// element to be treated as its own *individual* title
|
|
{
|
|
beginKeywords: 'namespace',
|
|
relevance: 0,
|
|
end: ';',
|
|
illegal: /[.']/,
|
|
contains: [ hljs.inherit(hljs.UNDERSCORE_TITLE_MODE, { scope: "title.class" }) ]
|
|
},
|
|
{
|
|
beginKeywords: 'use',
|
|
relevance: 0,
|
|
end: ';',
|
|
contains: [
|
|
// TODO: title.function vs title.class
|
|
{
|
|
match: /\b(as|const|function)\b/,
|
|
scope: "keyword"
|
|
},
|
|
// TODO: could be title.class or title.function
|
|
hljs.UNDERSCORE_TITLE_MODE
|
|
]
|
|
},
|
|
STRING,
|
|
NUMBER,
|
|
]
|
|
};
|
|
}
|
|
|
|
module.exports = php;
|