UNPKG

8.49 kBJavaScriptView Raw
1/*
2Language: Ruby
3Description: Ruby is a dynamic, open source programming language with a focus on simplicity and productivity.
4Website: https://www.ruby-lang.org/
5Author: Anton Kovalyov <anton@kovalyov.net>
6Contributors: 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>
7Category: common
8*/
9
10function ruby(hljs) {
11 const regex = hljs.regex;
12 const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)';
13 const RUBY_KEYWORDS = {
14 keyword:
15 'and then defined module in return redo if BEGIN retry end for self when ' +
16 'next until do begin unless END rescue else break undef not super class case ' +
17 'require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor ' +
18 '__FILE__',
19 built_in: 'proc lambda',
20 literal:
21 'true false nil'
22 };
23 const YARDOCTAG = {
24 className: 'doctag',
25 begin: '@[A-Za-z]+'
26 };
27 const IRB_OBJECT = {
28 begin: '#<',
29 end: '>'
30 };
31 const COMMENT_MODES = [
32 hljs.COMMENT(
33 '#',
34 '$',
35 {
36 contains: [ YARDOCTAG ]
37 }
38 ),
39 hljs.COMMENT(
40 '^=begin',
41 '^=end',
42 {
43 contains: [ YARDOCTAG ],
44 relevance: 10
45 }
46 ),
47 hljs.COMMENT('^__END__', '\\n$')
48 ];
49 const SUBST = {
50 className: 'subst',
51 begin: /#\{/,
52 end: /\}/,
53 keywords: RUBY_KEYWORDS
54 };
55 const STRING = {
56 className: 'string',
57 contains: [
58 hljs.BACKSLASH_ESCAPE,
59 SUBST
60 ],
61 variants: [
62 {
63 begin: /'/,
64 end: /'/
65 },
66 {
67 begin: /"/,
68 end: /"/
69 },
70 {
71 begin: /`/,
72 end: /`/
73 },
74 {
75 begin: /%[qQwWx]?\(/,
76 end: /\)/
77 },
78 {
79 begin: /%[qQwWx]?\[/,
80 end: /\]/
81 },
82 {
83 begin: /%[qQwWx]?\{/,
84 end: /\}/
85 },
86 {
87 begin: /%[qQwWx]?</,
88 end: />/
89 },
90 {
91 begin: /%[qQwWx]?\//,
92 end: /\//
93 },
94 {
95 begin: /%[qQwWx]?%/,
96 end: /%/
97 },
98 {
99 begin: /%[qQwWx]?-/,
100 end: /-/
101 },
102 {
103 begin: /%[qQwWx]?\|/,
104 end: /\|/
105 },
106 // in the following expressions, \B in the beginning suppresses recognition of ?-sequences
107 // where ? is the last character of a preceding identifier, as in: `func?4`
108 {
109 begin: /\B\?(\\\d{1,3})/
110 },
111 {
112 begin: /\B\?(\\x[A-Fa-f0-9]{1,2})/
113 },
114 {
115 begin: /\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/
116 },
117 {
118 begin: /\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/
119 },
120 {
121 begin: /\B\?\\(c|C-)[\x20-\x7e]/
122 },
123 {
124 begin: /\B\?\\?\S/
125 },
126 // heredocs
127 {
128 // this guard makes sure that we have an entire heredoc and not a false
129 // positive (auto-detect, etc.)
130 begin: regex.concat(
131 /<<[-~]?'?/,
132 regex.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)
133 ),
134 contains: [
135 hljs.END_SAME_AS_BEGIN({
136 begin: /(\w+)/,
137 end: /(\w+)/,
138 contains: [
139 hljs.BACKSLASH_ESCAPE,
140 SUBST
141 ]
142 })
143 ]
144 }
145 ]
146 };
147
148 // Ruby syntax is underdocumented, but this grammar seems to be accurate
149 // as of version 2.7.2 (confirmed with (irb and `Ripper.sexp(...)`)
150 // https://docs.ruby-lang.org/en/2.7.0/doc/syntax/literals_rdoc.html#label-Numbers
151 const decimal = '[1-9](_?[0-9])*|0';
152 const digits = '[0-9](_?[0-9])*';
153 const NUMBER = {
154 className: 'number',
155 relevance: 0,
156 variants: [
157 // decimal integer/float, optionally exponential or rational, optionally imaginary
158 {
159 begin: `\\b(${decimal})(\\.(${digits}))?([eE][+-]?(${digits})|r)?i?\\b`
160 },
161
162 // explicit decimal/binary/octal/hexadecimal integer,
163 // optionally rational and/or imaginary
164 {
165 begin: "\\b0[dD][0-9](_?[0-9])*r?i?\\b"
166 },
167 {
168 begin: "\\b0[bB][0-1](_?[0-1])*r?i?\\b"
169 },
170 {
171 begin: "\\b0[oO][0-7](_?[0-7])*r?i?\\b"
172 },
173 {
174 begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"
175 },
176
177 // 0-prefixed implicit octal integer, optionally rational and/or imaginary
178 {
179 begin: "\\b0(_?[0-7])+r?i?\\b"
180 }
181 ]
182 };
183
184 const PARAMS = {
185 className: 'params',
186 begin: '\\(',
187 end: '\\)',
188 endsParent: true,
189 keywords: RUBY_KEYWORDS
190 };
191
192 const RUBY_DEFAULT_CONTAINS = [
193 STRING,
194 {
195 className: 'class',
196 beginKeywords: 'class module',
197 end: '$|;',
198 illegal: /=/,
199 contains: [
200 hljs.inherit(hljs.TITLE_MODE, {
201 begin: '[A-Za-z_]\\w*(::\\w+)*(\\?|!)?'
202 }),
203 {
204 begin: '<\\s*',
205 contains: [
206 {
207 begin: '(' + hljs.IDENT_RE + '::)?' + hljs.IDENT_RE,
208 // we already get points for <, we don't need poitns
209 // for the name also
210 relevance: 0
211 }
212 ]
213 }
214 ].concat(COMMENT_MODES)
215 },
216 {
217 className: 'function',
218 // def method_name(
219 // def method_name;
220 // def method_name (end of line)
221 begin: regex.concat(/def\s+/, regex.lookahead(RUBY_METHOD_RE + "\\s*(\\(|;|$)")),
222 relevance: 0, // relevance comes from kewords
223 keywords: "def",
224 end: '$|;',
225 contains: [
226 hljs.inherit(hljs.TITLE_MODE, {
227 begin: RUBY_METHOD_RE
228 }),
229 PARAMS
230 ].concat(COMMENT_MODES)
231 },
232 {
233 // swallow namespace qualifiers before symbols
234 begin: hljs.IDENT_RE + '::'
235 },
236 {
237 className: 'symbol',
238 begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:',
239 relevance: 0
240 },
241 {
242 className: 'symbol',
243 begin: ':(?!\\s)',
244 contains: [
245 STRING,
246 {
247 begin: RUBY_METHOD_RE
248 }
249 ],
250 relevance: 0
251 },
252 NUMBER,
253 {
254 // negative-look forward attempts to prevent false matches like:
255 // @ident@ or $ident$ that might indicate this is not ruby at all
256 className: "variable",
257 begin: '(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])' + `(?![A-Za-z])(?![@$?'])`
258 },
259 {
260 className: 'params',
261 begin: /\|/,
262 end: /\|/,
263 relevance: 0, // this could be a lot of things (in other languages) other than params
264 keywords: RUBY_KEYWORDS
265 },
266 { // regexp container
267 begin: '(' + hljs.RE_STARTERS_RE + '|unless)\\s*',
268 keywords: 'unless',
269 contains: [
270 {
271 className: 'regexp',
272 contains: [
273 hljs.BACKSLASH_ESCAPE,
274 SUBST
275 ],
276 illegal: /\n/,
277 variants: [
278 {
279 begin: '/',
280 end: '/[a-z]*'
281 },
282 {
283 begin: /%r\{/,
284 end: /\}[a-z]*/
285 },
286 {
287 begin: '%r\\(',
288 end: '\\)[a-z]*'
289 },
290 {
291 begin: '%r!',
292 end: '![a-z]*'
293 },
294 {
295 begin: '%r\\[',
296 end: '\\][a-z]*'
297 }
298 ]
299 }
300 ].concat(IRB_OBJECT, COMMENT_MODES),
301 relevance: 0
302 }
303 ].concat(IRB_OBJECT, COMMENT_MODES);
304
305 SUBST.contains = RUBY_DEFAULT_CONTAINS;
306 PARAMS.contains = RUBY_DEFAULT_CONTAINS;
307
308 // >>
309 // ?>
310 const SIMPLE_PROMPT = "[>?]>";
311 // irb(main):001:0>
312 const DEFAULT_PROMPT = "[\\w#]+\\(\\w+\\):\\d+:\\d+>";
313 const RVM_PROMPT = "(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>";
314
315 const IRB_DEFAULT = [
316 {
317 begin: /^\s*=>/,
318 starts: {
319 end: '$',
320 contains: RUBY_DEFAULT_CONTAINS
321 }
322 },
323 {
324 className: 'meta',
325 begin: '^(' + SIMPLE_PROMPT + "|" + DEFAULT_PROMPT + '|' + RVM_PROMPT + ')(?=[ ])',
326 starts: {
327 end: '$',
328 contains: RUBY_DEFAULT_CONTAINS
329 }
330 }
331 ];
332
333 COMMENT_MODES.unshift(IRB_OBJECT);
334
335 return {
336 name: 'Ruby',
337 aliases: [
338 'rb',
339 'gemspec',
340 'podspec',
341 'thor',
342 'irb'
343 ],
344 keywords: RUBY_KEYWORDS,
345 illegal: /\/\*/,
346 contains: [
347 hljs.SHEBANG({
348 binary: "ruby"
349 })
350 ]
351 .concat(IRB_DEFAULT)
352 .concat(COMMENT_MODES)
353 .concat(RUBY_DEFAULT_CONTAINS)
354 };
355}
356
357export { ruby as default };