mirror of https://github.com/jkjoy/sunpeiwen.git
449 lines
10 KiB
JavaScript
449 lines
10 KiB
JavaScript
/*
|
|
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
|
|
},
|
|
{ // 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 };
|