/*
Language: Ruby
Description: Ruby is a dynamic, open source programming language with a focus on simplicity and productivity.
Website: https://www.ruby-lang.org/
Author: Anton Kovalyov <anton@kovalyov.net>
Contributors: Peter Leonov <gojpeg@yandex.ru>, Vasily Polovnyov <vast@whiteants.net>, Loren Segal <lsegal@soen.ca>, Pascal Hurni <phi@ruby-reactive.org>, Cedric Sohrauer <sohrauer@googlemail.com>
Category: common
*/
function ruby(hljs) {
    const regex = hljs.regex;
    const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)';
    // TODO: move concepts like CAMEL_CASE into `modes.js`
    const CLASS_NAME_RE = regex.either(/\b([A-Z]+[a-z0-9]+)+/, 
    // ends in caps
    /\b([A-Z]+[a-z0-9]+)+[A-Z]+/);
    const CLASS_NAME_WITH_NAMESPACE_RE = regex.concat(CLASS_NAME_RE, /(::\w+)*/);
    // very popular ruby built-ins that one might even assume
    // are actual keywords (despite that not being the case)
    const PSEUDO_KWS = [
        "include",
        "extend",
        "prepend",
        "public",
        "private",
        "protected",
        "raise",
        "throw"
    ];
    const RUBY_KEYWORDS = {
        "variable.constant": [
            "__FILE__",
            "__LINE__",
            "__ENCODING__"
        ],
        "variable.language": [
            "self",
            "super",
        ],
        keyword: [
            "alias",
            "and",
            "begin",
            "BEGIN",
            "break",
            "case",
            "class",
            "defined",
            "do",
            "else",
            "elsif",
            "end",
            "END",
            "ensure",
            "for",
            "if",
            "in",
            "module",
            "next",
            "not",
            "or",
            "redo",
            "require",
            "rescue",
            "retry",
            "return",
            "then",
            "undef",
            "unless",
            "until",
            "when",
            "while",
            "yield",
            ...PSEUDO_KWS
        ],
        built_in: [
            "proc",
            "lambda",
            "attr_accessor",
            "attr_reader",
            "attr_writer",
            "define_method",
            "private_constant",
            "module_function"
        ],
        literal: [
            "true",
            "false",
            "nil"
        ]
    };
    const YARDOCTAG = {
        className: 'doctag',
        begin: '@[A-Za-z]+'
    };
    const IRB_OBJECT = {
        begin: '#<',
        end: '>'
    };
    const COMMENT_MODES = [
        hljs.COMMENT('#', '$', { contains: [YARDOCTAG] }),
        hljs.COMMENT('^=begin', '^=end', {
            contains: [YARDOCTAG],
            relevance: 10
        }),
        hljs.COMMENT('^__END__', hljs.MATCH_NOTHING_RE)
    ];
    const SUBST = {
        className: 'subst',
        begin: /#\{/,
        end: /\}/,
        keywords: RUBY_KEYWORDS
    };
    const STRING = {
        className: 'string',
        contains: [
            hljs.BACKSLASH_ESCAPE,
            SUBST
        ],
        variants: [
            {
                begin: /'/,
                end: /'/
            },
            {
                begin: /"/,
                end: /"/
            },
            {
                begin: /`/,
                end: /`/
            },
            {
                begin: /%[qQwWx]?\(/,
                end: /\)/
            },
            {
                begin: /%[qQwWx]?\[/,
                end: /\]/
            },
            {
                begin: /%[qQwWx]?\{/,
                end: /\}/
            },
            {
                begin: /%[qQwWx]?</,
                end: />/
            },
            {
                begin: /%[qQwWx]?\//,
                end: /\//
            },
            {
                begin: /%[qQwWx]?%/,
                end: /%/
            },
            {
                begin: /%[qQwWx]?-/,
                end: /-/
            },
            {
                begin: /%[qQwWx]?\|/,
                end: /\|/
            },
            // in the following expressions, \B in the beginning suppresses recognition of ?-sequences
            // where ? is the last character of a preceding identifier, as in: `func?4`
            { begin: /\B\?(\\\d{1,3})/ },
            { begin: /\B\?(\\x[A-Fa-f0-9]{1,2})/ },
            { begin: /\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/ },
            { begin: /\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/ },
            { begin: /\B\?\\(c|C-)[\x20-\x7e]/ },
            { begin: /\B\?\\?\S/ },
            // heredocs
            {
                // this guard makes sure that we have an entire heredoc and not a false
                // positive (auto-detect, etc.)
                begin: regex.concat(/<<[-~]?'?/, regex.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),
                contains: [
                    hljs.END_SAME_AS_BEGIN({
                        begin: /(\w+)/,
                        end: /(\w+)/,
                        contains: [
                            hljs.BACKSLASH_ESCAPE,
                            SUBST
                        ]
                    })
                ]
            }
        ]
    };
    // Ruby syntax is underdocumented, but this grammar seems to be accurate
    // as of version 2.7.2 (confirmed with (irb and `Ripper.sexp(...)`)
    // https://docs.ruby-lang.org/en/2.7.0/doc/syntax/literals_rdoc.html#label-Numbers
    const decimal = '[1-9](_?[0-9])*|0';
    const digits = '[0-9](_?[0-9])*';
    const NUMBER = {
        className: 'number',
        relevance: 0,
        variants: [
            // decimal integer/float, optionally exponential or rational, optionally imaginary
            { begin: `\\b(${decimal})(\\.(${digits}))?([eE][+-]?(${digits})|r)?i?\\b` },
            // explicit decimal/binary/octal/hexadecimal integer,
            // optionally rational and/or imaginary
            { begin: "\\b0[dD][0-9](_?[0-9])*r?i?\\b" },
            { begin: "\\b0[bB][0-1](_?[0-1])*r?i?\\b" },
            { begin: "\\b0[oO][0-7](_?[0-7])*r?i?\\b" },
            { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b" },
            // 0-prefixed implicit octal integer, optionally rational and/or imaginary
            { begin: "\\b0(_?[0-7])+r?i?\\b" }
        ]
    };
    const PARAMS = {
        variants: [
            {
                match: /\(\)/,
            },
            {
                className: 'params',
                begin: /\(/,
                end: /(?=\))/,
                excludeBegin: true,
                endsParent: true,
                keywords: RUBY_KEYWORDS,
            }
        ]
    };
    const INCLUDE_EXTEND = {
        match: [
            /(include|extend)\s+/,
            CLASS_NAME_WITH_NAMESPACE_RE
        ],
        scope: {
            2: "title.class"
        },
        keywords: RUBY_KEYWORDS
    };
    const CLASS_DEFINITION = {
        variants: [
            {
                match: [
                    /class\s+/,
                    CLASS_NAME_WITH_NAMESPACE_RE,
                    /\s+<\s+/,
                    CLASS_NAME_WITH_NAMESPACE_RE
                ]
            },
            {
                match: [
                    /\b(class|module)\s+/,
                    CLASS_NAME_WITH_NAMESPACE_RE
                ]
            }
        ],
        scope: {
            2: "title.class",
            4: "title.class.inherited"
        },
        keywords: RUBY_KEYWORDS
    };
    const UPPER_CASE_CONSTANT = {
        relevance: 0,
        match: /\b[A-Z][A-Z_0-9]+\b/,
        className: "variable.constant"
    };
    const METHOD_DEFINITION = {
        match: [
            /def/, /\s+/,
            RUBY_METHOD_RE
        ],
        scope: {
            1: "keyword",
            3: "title.function"
        },
        contains: [
            PARAMS
        ]
    };
    const OBJECT_CREATION = {
        relevance: 0,
        match: [
            CLASS_NAME_WITH_NAMESPACE_RE,
            /\.new[. (]/
        ],
        scope: {
            1: "title.class"
        }
    };
    // CamelCase
    const CLASS_REFERENCE = {
        relevance: 0,
        match: CLASS_NAME_RE,
        scope: "title.class"
    };
    const RUBY_DEFAULT_CONTAINS = [
        STRING,
        CLASS_DEFINITION,
        INCLUDE_EXTEND,
        OBJECT_CREATION,
        UPPER_CASE_CONSTANT,
        CLASS_REFERENCE,
        METHOD_DEFINITION,
        {
            // swallow namespace qualifiers before symbols
            begin: hljs.IDENT_RE + '::'
        },
        {
            className: 'symbol',
            begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:',
            relevance: 0
        },
        {
            className: 'symbol',
            begin: ':(?!\\s)',
            contains: [
                STRING,
                { begin: RUBY_METHOD_RE }
            ],
            relevance: 0
        },
        NUMBER,
        {
            // negative-look forward attempts to prevent false matches like:
            // @ident@ or $ident$ that might indicate this is not ruby at all
            className: "variable",
            begin: '(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])' + `(?![A-Za-z])(?![@$?'])`
        },
        {
            className: 'params',
            begin: /\|/,
            end: /\|/,
            excludeBegin: true,
            excludeEnd: true,
            relevance: 0, // this could be a lot of things (in other languages) other than params
            keywords: RUBY_KEYWORDS
        },
        {
            begin: '(' + hljs.RE_STARTERS_RE + '|unless)\\s*',
            keywords: 'unless',
            contains: [
                {
                    className: 'regexp',
                    contains: [
                        hljs.BACKSLASH_ESCAPE,
                        SUBST
                    ],
                    illegal: /\n/,
                    variants: [
                        {
                            begin: '/',
                            end: '/[a-z]*'
                        },
                        {
                            begin: /%r\{/,
                            end: /\}[a-z]*/
                        },
                        {
                            begin: '%r\\(',
                            end: '\\)[a-z]*'
                        },
                        {
                            begin: '%r!',
                            end: '![a-z]*'
                        },
                        {
                            begin: '%r\\[',
                            end: '\\][a-z]*'
                        }
                    ]
                }
            ].concat(IRB_OBJECT, COMMENT_MODES),
            relevance: 0
        }
    ].concat(IRB_OBJECT, COMMENT_MODES);
    SUBST.contains = RUBY_DEFAULT_CONTAINS;
    PARAMS.contains = RUBY_DEFAULT_CONTAINS;
    // >>
    // ?>
    const SIMPLE_PROMPT = "[>?]>";
    // irb(main):001:0>
    const DEFAULT_PROMPT = "[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]";
    const RVM_PROMPT = "(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>";
    const IRB_DEFAULT = [
        {
            begin: /^\s*=>/,
            starts: {
                end: '$',
                contains: RUBY_DEFAULT_CONTAINS
            }
        },
        {
            className: 'meta.prompt',
            begin: '^(' + SIMPLE_PROMPT + "|" + DEFAULT_PROMPT + '|' + RVM_PROMPT + ')(?=[ ])',
            starts: {
                end: '$',
                keywords: RUBY_KEYWORDS,
                contains: RUBY_DEFAULT_CONTAINS
            }
        }
    ];
    COMMENT_MODES.unshift(IRB_OBJECT);
    return {
        name: 'Ruby',
        aliases: [
            'rb',
            'gemspec',
            'podspec',
            'thor',
            'irb'
        ],
        keywords: RUBY_KEYWORDS,
        illegal: /\/\*/,
        contains: [hljs.SHEBANG({ binary: "ruby" })]
            .concat(IRB_DEFAULT)
            .concat(COMMENT_MODES)
            .concat(RUBY_DEFAULT_CONTAINS)
    };
}
export { ruby as default };
//# sourceMappingURL=ruby.js.map