123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- /*
- 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, scripting
- */
- 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
- },
- { // regexp container
- 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 };
|