You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
8.6 KiB

/**
* @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 <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 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]?</,
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
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;