/** * @param {string} value * @returns {RegExp} * */ /** * @param {RegExp | string } re * @returns {string} */ function source(re) { if (!re) return null; if (typeof re === "string") return re; return re.source; } /** * @param {RegExp | string } re * @returns {string} */ function lookahead(re) { return concat('(?=', re, ')'); } /** * @param {...(RegExp | string) } args * @returns {string} */ function concat(...args) { const joined = args.map((x) => source(x)).join(""); return joined; } /* 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 Contributors: Peter Leonov , Vasily Polovnyov , Loren Segal , Pascal Hurni , Cedric Sohrauer Category: common */ function ruby(hljs) { const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)'; const RUBY_KEYWORDS = { keyword: 'and then defined module in return redo if BEGIN retry end for self when ' + 'next until do begin unless END rescue else break undef not super class case ' + 'require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor ' + '__FILE__', built_in: 'proc lambda', 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__', '\\n$') ]; 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]?/ }, { 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 begin: /<<[-~]?'?(\w+)\n(?:[^\n]*\n)*?\s*\1\b/, returnBegin: true, contains: [ { begin: /<<[-~]?'?/ }, 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 = { className: 'params', begin: '\\(', end: '\\)', endsParent: true, keywords: RUBY_KEYWORDS }; const RUBY_DEFAULT_CONTAINS = [ STRING, { className: 'class', beginKeywords: 'class module', end: '$|;', illegal: /=/, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: '[A-Za-z_]\\w*(::\\w+)*(\\?|!)?' }), { begin: '<\\s*', contains: [ { begin: '(' + hljs.IDENT_RE + '::)?' + hljs.IDENT_RE, // we already get points for <, we don't need poitns // for the name also relevance: 0 } ] } ].concat(COMMENT_MODES) }, { className: 'function', // def method_name( // def method_name; // def method_name (end of line) begin: concat(/def\s+/, lookahead(RUBY_METHOD_RE + "\\s*(\\(|;|$)")), relevance: 0, // relevance comes from kewords keywords: "def", end: '$|;', contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: RUBY_METHOD_RE }), PARAMS ].concat(COMMENT_MODES) }, { // 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 attemps 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: /\|/, 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', begin: '^(' + SIMPLE_PROMPT + "|" + DEFAULT_PROMPT + '|' + RVM_PROMPT + ')(?=[ ])', starts: { end: '$', 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) }; } module.exports = ruby;