/*
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 = {
        begin: /<<<[ \t]*(?:(\w+)|"(\w+)")\n/,
        end: /[ \t]*(\w+)\b/,
        contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
        'on:begin': (m, resp) => { resp.data._beginMatch = m[1] || m[2]; },
        'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1])
            resp.ignoreMatch(); },
    };
    const NOWDOC = hljs.END_SAME_AS_BEGIN({
        begin: /<<<[ \t]*'(\w+)'\n/,
        end: /[ \t]*(\w+)\b/,
    });
    // 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,
            NOWDOC
        ]
    };
    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,
        ]
    };
}
export { php as default };
//# sourceMappingURL=php.js.map