]> luflow.net public git repositories - flow-web.git/blob - static/highlight/languages/php.js
Initial commit.
[flow-web.git] / static / highlight / languages / php.js
1 /*! `php` grammar compiled for Highlight.js 11.11.1 */
2 (function(){
3 var hljsGrammar = (function () {
4 'use strict';
5
6 /*
7 Language: PHP
8 Author: Victor Karamzin <Victor.Karamzin@enterra-inc.com>
9 Contributors: Evgeny Stepanischev <imbolk@gmail.com>, Ivan Sagalaev <maniac@softwaremaniacs.org>
10 Website: https://www.php.net
11 Category: common
12 */
13
14 /**
15 * @param {HLJSApi} hljs
16 * @returns {LanguageDetail}
17 * */
18 function php(hljs) {
19 const regex = hljs.regex;
20 // negative look-ahead tries to avoid matching patterns that are not
21 // Perl at all like $ident$, @ident@, etc.
22 const NOT_PERL_ETC = /(?![A-Za-z0-9])(?![$])/;
23 const IDENT_RE = regex.concat(
24 /[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,
25 NOT_PERL_ETC);
26 // Will not detect camelCase classes
27 const PASCAL_CASE_CLASS_NAME_RE = regex.concat(
28 /(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,
29 NOT_PERL_ETC);
30 const UPCASE_NAME_RE = regex.concat(
31 /[A-Z]+/,
32 NOT_PERL_ETC);
33 const VARIABLE = {
34 scope: 'variable',
35 match: '\\$+' + IDENT_RE,
36 };
37 const PREPROCESSOR = {
38 scope: "meta",
39 variants: [
40 { begin: /<\?php/, relevance: 10 }, // boost for obvious PHP
41 { begin: /<\?=/ },
42 // less relevant per PSR-1 which says not to use short-tags
43 { begin: /<\?/, relevance: 0.1 },
44 { begin: /\?>/ } // end php tag
45 ]
46 };
47 const SUBST = {
48 scope: 'subst',
49 variants: [
50 { begin: /\$\w+/ },
51 {
52 begin: /\{\$/,
53 end: /\}/
54 }
55 ]
56 };
57 const SINGLE_QUOTED = hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null, });
58 const DOUBLE_QUOTED = hljs.inherit(hljs.QUOTE_STRING_MODE, {
59 illegal: null,
60 contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
61 });
62
63 const HEREDOC = {
64 begin: /<<<[ \t]*(?:(\w+)|"(\w+)")\n/,
65 end: /[ \t]*(\w+)\b/,
66 contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
67 'on:begin': (m, resp) => { resp.data._beginMatch = m[1] || m[2]; },
68 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); },
69 };
70
71 const NOWDOC = hljs.END_SAME_AS_BEGIN({
72 begin: /<<<[ \t]*'(\w+)'\n/,
73 end: /[ \t]*(\w+)\b/,
74 });
75 // list of valid whitespaces because non-breaking space might be part of a IDENT_RE
76 const WHITESPACE = '[ \t\n]';
77 const STRING = {
78 scope: 'string',
79 variants: [
80 DOUBLE_QUOTED,
81 SINGLE_QUOTED,
82 HEREDOC,
83 NOWDOC
84 ]
85 };
86 const NUMBER = {
87 scope: 'number',
88 variants: [
89 { begin: `\\b0[bB][01]+(?:_[01]+)*\\b` }, // Binary w/ underscore support
90 { begin: `\\b0[oO][0-7]+(?:_[0-7]+)*\\b` }, // Octals w/ underscore support
91 { begin: `\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b` }, // Hex w/ underscore support
92 // Decimals w/ underscore support, with optional fragments and scientific exponent (e) suffix.
93 { begin: `(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?` }
94 ],
95 relevance: 0
96 };
97 const LITERALS = [
98 "false",
99 "null",
100 "true"
101 ];
102 const KWS = [
103 // Magic constants:
104 // <https://www.php.net/manual/en/language.constants.predefined.php>
105 "__CLASS__",
106 "__DIR__",
107 "__FILE__",
108 "__FUNCTION__",
109 "__COMPILER_HALT_OFFSET__",
110 "__LINE__",
111 "__METHOD__",
112 "__NAMESPACE__",
113 "__TRAIT__",
114 // Function that look like language construct or language construct that look like function:
115 // List of keywords that may not require parenthesis
116 "die",
117 "echo",
118 "exit",
119 "include",
120 "include_once",
121 "print",
122 "require",
123 "require_once",
124 // These are not language construct (function) but operate on the currently-executing function and can access the current symbol table
125 // 'compact extract func_get_arg func_get_args func_num_args get_called_class get_parent_class ' +
126 // Other keywords:
127 // <https://www.php.net/manual/en/reserved.php>
128 // <https://www.php.net/manual/en/language.types.type-juggling.php>
129 "array",
130 "abstract",
131 "and",
132 "as",
133 "binary",
134 "bool",
135 "boolean",
136 "break",
137 "callable",
138 "case",
139 "catch",
140 "class",
141 "clone",
142 "const",
143 "continue",
144 "declare",
145 "default",
146 "do",
147 "double",
148 "else",
149 "elseif",
150 "empty",
151 "enddeclare",
152 "endfor",
153 "endforeach",
154 "endif",
155 "endswitch",
156 "endwhile",
157 "enum",
158 "eval",
159 "extends",
160 "final",
161 "finally",
162 "float",
163 "for",
164 "foreach",
165 "from",
166 "global",
167 "goto",
168 "if",
169 "implements",
170 "instanceof",
171 "insteadof",
172 "int",
173 "integer",
174 "interface",
175 "isset",
176 "iterable",
177 "list",
178 "match|0",
179 "mixed",
180 "new",
181 "never",
182 "object",
183 "or",
184 "private",
185 "protected",
186 "public",
187 "readonly",
188 "real",
189 "return",
190 "string",
191 "switch",
192 "throw",
193 "trait",
194 "try",
195 "unset",
196 "use",
197 "var",
198 "void",
199 "while",
200 "xor",
201 "yield"
202 ];
203
204 const BUILT_INS = [
205 // Standard PHP library:
206 // <https://www.php.net/manual/en/book.spl.php>
207 "Error|0",
208 "AppendIterator",
209 "ArgumentCountError",
210 "ArithmeticError",
211 "ArrayIterator",
212 "ArrayObject",
213 "AssertionError",
214 "BadFunctionCallException",
215 "BadMethodCallException",
216 "CachingIterator",
217 "CallbackFilterIterator",
218 "CompileError",
219 "Countable",
220 "DirectoryIterator",
221 "DivisionByZeroError",
222 "DomainException",
223 "EmptyIterator",
224 "ErrorException",
225 "Exception",
226 "FilesystemIterator",
227 "FilterIterator",
228 "GlobIterator",
229 "InfiniteIterator",
230 "InvalidArgumentException",
231 "IteratorIterator",
232 "LengthException",
233 "LimitIterator",
234 "LogicException",
235 "MultipleIterator",
236 "NoRewindIterator",
237 "OutOfBoundsException",
238 "OutOfRangeException",
239 "OuterIterator",
240 "OverflowException",
241 "ParentIterator",
242 "ParseError",
243 "RangeException",
244 "RecursiveArrayIterator",
245 "RecursiveCachingIterator",
246 "RecursiveCallbackFilterIterator",
247 "RecursiveDirectoryIterator",
248 "RecursiveFilterIterator",
249 "RecursiveIterator",
250 "RecursiveIteratorIterator",
251 "RecursiveRegexIterator",
252 "RecursiveTreeIterator",
253 "RegexIterator",
254 "RuntimeException",
255 "SeekableIterator",
256 "SplDoublyLinkedList",
257 "SplFileInfo",
258 "SplFileObject",
259 "SplFixedArray",
260 "SplHeap",
261 "SplMaxHeap",
262 "SplMinHeap",
263 "SplObjectStorage",
264 "SplObserver",
265 "SplPriorityQueue",
266 "SplQueue",
267 "SplStack",
268 "SplSubject",
269 "SplTempFileObject",
270 "TypeError",
271 "UnderflowException",
272 "UnexpectedValueException",
273 "UnhandledMatchError",
274 // Reserved interfaces:
275 // <https://www.php.net/manual/en/reserved.interfaces.php>
276 "ArrayAccess",
277 "BackedEnum",
278 "Closure",
279 "Fiber",
280 "Generator",
281 "Iterator",
282 "IteratorAggregate",
283 "Serializable",
284 "Stringable",
285 "Throwable",
286 "Traversable",
287 "UnitEnum",
288 "WeakReference",
289 "WeakMap",
290 // Reserved classes:
291 // <https://www.php.net/manual/en/reserved.classes.php>
292 "Directory",
293 "__PHP_Incomplete_Class",
294 "parent",
295 "php_user_filter",
296 "self",
297 "static",
298 "stdClass"
299 ];
300
301 /** Dual-case keywords
302 *
303 * ["then","FILE"] =>
304 * ["then", "THEN", "FILE", "file"]
305 *
306 * @param {string[]} items */
307 const dualCase = (items) => {
308 /** @type string[] */
309 const result = [];
310 items.forEach(item => {
311 result.push(item);
312 if (item.toLowerCase() === item) {
313 result.push(item.toUpperCase());
314 } else {
315 result.push(item.toLowerCase());
316 }
317 });
318 return result;
319 };
320
321 const KEYWORDS = {
322 keyword: KWS,
323 literal: dualCase(LITERALS),
324 built_in: BUILT_INS,
325 };
326
327 /**
328 * @param {string[]} items */
329 const normalizeKeywords = (items) => {
330 return items.map(item => {
331 return item.replace(/\|\d+$/, "");
332 });
333 };
334
335 const CONSTRUCTOR_CALL = { variants: [
336 {
337 match: [
338 /new/,
339 regex.concat(WHITESPACE, "+"),
340 // to prevent built ins from being confused as the class constructor call
341 regex.concat("(?!", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
342 PASCAL_CASE_CLASS_NAME_RE,
343 ],
344 scope: {
345 1: "keyword",
346 4: "title.class",
347 },
348 }
349 ] };
350
351 const CONSTANT_REFERENCE = regex.concat(IDENT_RE, "\\b(?!\\()");
352
353 const LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON = { variants: [
354 {
355 match: [
356 regex.concat(
357 /::/,
358 regex.lookahead(/(?!class\b)/)
359 ),
360 CONSTANT_REFERENCE,
361 ],
362 scope: { 2: "variable.constant", },
363 },
364 {
365 match: [
366 /::/,
367 /class/,
368 ],
369 scope: { 2: "variable.language", },
370 },
371 {
372 match: [
373 PASCAL_CASE_CLASS_NAME_RE,
374 regex.concat(
375 /::/,
376 regex.lookahead(/(?!class\b)/)
377 ),
378 CONSTANT_REFERENCE,
379 ],
380 scope: {
381 1: "title.class",
382 3: "variable.constant",
383 },
384 },
385 {
386 match: [
387 PASCAL_CASE_CLASS_NAME_RE,
388 regex.concat(
389 "::",
390 regex.lookahead(/(?!class\b)/)
391 ),
392 ],
393 scope: { 1: "title.class", },
394 },
395 {
396 match: [
397 PASCAL_CASE_CLASS_NAME_RE,
398 /::/,
399 /class/,
400 ],
401 scope: {
402 1: "title.class",
403 3: "variable.language",
404 },
405 }
406 ] };
407
408 const NAMED_ARGUMENT = {
409 scope: 'attr',
410 match: regex.concat(IDENT_RE, regex.lookahead(':'), regex.lookahead(/(?!::)/)),
411 };
412 const PARAMS_MODE = {
413 relevance: 0,
414 begin: /\(/,
415 end: /\)/,
416 keywords: KEYWORDS,
417 contains: [
418 NAMED_ARGUMENT,
419 VARIABLE,
420 LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
421 hljs.C_BLOCK_COMMENT_MODE,
422 STRING,
423 NUMBER,
424 CONSTRUCTOR_CALL,
425 ],
426 };
427 const FUNCTION_INVOKE = {
428 relevance: 0,
429 match: [
430 /\b/,
431 // to prevent keywords from being confused as the function title
432 regex.concat("(?!fn\\b|function\\b|", normalizeKeywords(KWS).join("\\b|"), "|", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
433 IDENT_RE,
434 regex.concat(WHITESPACE, "*"),
435 regex.lookahead(/(?=\()/)
436 ],
437 scope: { 3: "title.function.invoke", },
438 contains: [ PARAMS_MODE ]
439 };
440 PARAMS_MODE.contains.push(FUNCTION_INVOKE);
441
442 const ATTRIBUTE_CONTAINS = [
443 NAMED_ARGUMENT,
444 LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
445 hljs.C_BLOCK_COMMENT_MODE,
446 STRING,
447 NUMBER,
448 CONSTRUCTOR_CALL,
449 ];
450
451 const ATTRIBUTES = {
452 begin: regex.concat(/#\[\s*\\?/,
453 regex.either(
454 PASCAL_CASE_CLASS_NAME_RE,
455 UPCASE_NAME_RE
456 )
457 ),
458 beginScope: "meta",
459 end: /]/,
460 endScope: "meta",
461 keywords: {
462 literal: LITERALS,
463 keyword: [
464 'new',
465 'array',
466 ]
467 },
468 contains: [
469 {
470 begin: /\[/,
471 end: /]/,
472 keywords: {
473 literal: LITERALS,
474 keyword: [
475 'new',
476 'array',
477 ]
478 },
479 contains: [
480 'self',
481 ...ATTRIBUTE_CONTAINS,
482 ]
483 },
484 ...ATTRIBUTE_CONTAINS,
485 {
486 scope: 'meta',
487 variants: [
488 { match: PASCAL_CASE_CLASS_NAME_RE },
489 { match: UPCASE_NAME_RE }
490 ]
491 }
492 ]
493 };
494
495 return {
496 case_insensitive: false,
497 keywords: KEYWORDS,
498 contains: [
499 ATTRIBUTES,
500 hljs.HASH_COMMENT_MODE,
501 hljs.COMMENT('//', '$'),
502 hljs.COMMENT(
503 '/\\*',
504 '\\*/',
505 { contains: [
506 {
507 scope: 'doctag',
508 match: '@[A-Za-z]+'
509 }
510 ] }
511 ),
512 {
513 match: /__halt_compiler\(\);/,
514 keywords: '__halt_compiler',
515 starts: {
516 scope: "comment",
517 end: hljs.MATCH_NOTHING_RE,
518 contains: [
519 {
520 match: /\?>/,
521 scope: "meta",
522 endsParent: true
523 }
524 ]
525 }
526 },
527 PREPROCESSOR,
528 {
529 scope: 'variable.language',
530 match: /\$this\b/
531 },
532 VARIABLE,
533 FUNCTION_INVOKE,
534 LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
535 {
536 match: [
537 /const/,
538 /\s/,
539 IDENT_RE,
540 ],
541 scope: {
542 1: "keyword",
543 3: "variable.constant",
544 },
545 },
546 CONSTRUCTOR_CALL,
547 {
548 scope: 'function',
549 relevance: 0,
550 beginKeywords: 'fn function',
551 end: /[;{]/,
552 excludeEnd: true,
553 illegal: '[$%\\[]',
554 contains: [
555 { beginKeywords: 'use', },
556 hljs.UNDERSCORE_TITLE_MODE,
557 {
558 begin: '=>', // No markup, just a relevance booster
559 endsParent: true
560 },
561 {
562 scope: 'params',
563 begin: '\\(',
564 end: '\\)',
565 excludeBegin: true,
566 excludeEnd: true,
567 keywords: KEYWORDS,
568 contains: [
569 'self',
570 ATTRIBUTES,
571 VARIABLE,
572 LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
573 hljs.C_BLOCK_COMMENT_MODE,
574 STRING,
575 NUMBER
576 ]
577 },
578 ]
579 },
580 {
581 scope: 'class',
582 variants: [
583 {
584 beginKeywords: "enum",
585 illegal: /[($"]/
586 },
587 {
588 beginKeywords: "class interface trait",
589 illegal: /[:($"]/
590 }
591 ],
592 relevance: 0,
593 end: /\{/,
594 excludeEnd: true,
595 contains: [
596 { beginKeywords: 'extends implements' },
597 hljs.UNDERSCORE_TITLE_MODE
598 ]
599 },
600 // both use and namespace still use "old style" rules (vs multi-match)
601 // because the namespace name can include `\` and we still want each
602 // element to be treated as its own *individual* title
603 {
604 beginKeywords: 'namespace',
605 relevance: 0,
606 end: ';',
607 illegal: /[.']/,
608 contains: [ hljs.inherit(hljs.UNDERSCORE_TITLE_MODE, { scope: "title.class" }) ]
609 },
610 {
611 beginKeywords: 'use',
612 relevance: 0,
613 end: ';',
614 contains: [
615 // TODO: title.function vs title.class
616 {
617 match: /\b(as|const|function)\b/,
618 scope: "keyword"
619 },
620 // TODO: could be title.class or title.function
621 hljs.UNDERSCORE_TITLE_MODE
622 ]
623 },
624 STRING,
625 NUMBER,
626 ]
627 };
628 }
629
630 return php;
631
632 })();
633
634 hljs.registerLanguage('php', hljsGrammar);
635 })();