/** * @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) { var RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)'; var 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' }; var YARDOCTAG = { className: 'doctag', begin: '@[A-Za-z]+' }; var IRB_OBJECT = { begin: '#<', end: '>' }; var COMMENT_MODES = [ hljs.COMMENT( '#', '$', { contains: [YARDOCTAG] } ), hljs.COMMENT( '^=begin', '^=end', { contains: [YARDOCTAG], relevance: 10 } ), hljs.COMMENT('^__END__', '\\n$') ]; var SUBST = { className: 'subst', begin: /#\{/, end: /\}/, keywords: RUBY_KEYWORDS }; var 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: /\|/}, { // \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}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/ }, { // heredocs begin: /<<[-~]?'?(\w+)(?:.|\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 var decimal = '[1-9](_?[0-9])*|0'; var digits = '[0-9](_?[0-9])*'; var 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" }, ] }; var PARAMS = { className: 'params', begin: '\\(', end: '\\)', endsParent: true, keywords: RUBY_KEYWORDS }; var 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 }] } ].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*(\\(|;|$)")), 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; // >> // ?> var SIMPLE_PROMPT = "[>?]>"; // irb(main):001:0> var DEFAULT_PROMPT = "[\\w#]+\\(\\w+\\):\\d+:\\d+>"; var RVM_PROMPT = "(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>"; var 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;