ruby.js
1 /*! `ruby` grammar compiled for Highlight.js 11.10.0 */ 2 (function(){ 3 var hljsGrammar = (function () { 4 'use strict'; 5 6 /* 7 Language: Ruby 8 Description: Ruby is a dynamic, open source programming language with a focus on simplicity and productivity. 9 Website: https://www.ruby-lang.org/ 10 Author: Anton Kovalyov <anton@kovalyov.net> 11 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> 12 Category: common, scripting 13 */ 14 15 function ruby(hljs) { 16 const regex = hljs.regex; 17 const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)'; 18 // TODO: move concepts like CAMEL_CASE into `modes.js` 19 const CLASS_NAME_RE = regex.either( 20 /\b([A-Z]+[a-z0-9]+)+/, 21 // ends in caps 22 /\b([A-Z]+[a-z0-9]+)+[A-Z]+/, 23 ) 24 ; 25 const CLASS_NAME_WITH_NAMESPACE_RE = regex.concat(CLASS_NAME_RE, /(::\w+)*/); 26 // very popular ruby built-ins that one might even assume 27 // are actual keywords (despite that not being the case) 28 const PSEUDO_KWS = [ 29 "include", 30 "extend", 31 "prepend", 32 "public", 33 "private", 34 "protected", 35 "raise", 36 "throw" 37 ]; 38 const RUBY_KEYWORDS = { 39 "variable.constant": [ 40 "__FILE__", 41 "__LINE__", 42 "__ENCODING__" 43 ], 44 "variable.language": [ 45 "self", 46 "super", 47 ], 48 keyword: [ 49 "alias", 50 "and", 51 "begin", 52 "BEGIN", 53 "break", 54 "case", 55 "class", 56 "defined", 57 "do", 58 "else", 59 "elsif", 60 "end", 61 "END", 62 "ensure", 63 "for", 64 "if", 65 "in", 66 "module", 67 "next", 68 "not", 69 "or", 70 "redo", 71 "require", 72 "rescue", 73 "retry", 74 "return", 75 "then", 76 "undef", 77 "unless", 78 "until", 79 "when", 80 "while", 81 "yield", 82 ...PSEUDO_KWS 83 ], 84 built_in: [ 85 "proc", 86 "lambda", 87 "attr_accessor", 88 "attr_reader", 89 "attr_writer", 90 "define_method", 91 "private_constant", 92 "module_function" 93 ], 94 literal: [ 95 "true", 96 "false", 97 "nil" 98 ] 99 }; 100 const YARDOCTAG = { 101 className: 'doctag', 102 begin: '@[A-Za-z]+' 103 }; 104 const IRB_OBJECT = { 105 begin: '#<', 106 end: '>' 107 }; 108 const COMMENT_MODES = [ 109 hljs.COMMENT( 110 '#', 111 '$', 112 { contains: [ YARDOCTAG ] } 113 ), 114 hljs.COMMENT( 115 '^=begin', 116 '^=end', 117 { 118 contains: [ YARDOCTAG ], 119 relevance: 10 120 } 121 ), 122 hljs.COMMENT('^__END__', hljs.MATCH_NOTHING_RE) 123 ]; 124 const SUBST = { 125 className: 'subst', 126 begin: /#\{/, 127 end: /\}/, 128 keywords: RUBY_KEYWORDS 129 }; 130 const STRING = { 131 className: 'string', 132 contains: [ 133 hljs.BACKSLASH_ESCAPE, 134 SUBST 135 ], 136 variants: [ 137 { 138 begin: /'/, 139 end: /'/ 140 }, 141 { 142 begin: /"/, 143 end: /"/ 144 }, 145 { 146 begin: /`/, 147 end: /`/ 148 }, 149 { 150 begin: /%[qQwWx]?\(/, 151 end: /\)/ 152 }, 153 { 154 begin: /%[qQwWx]?\[/, 155 end: /\]/ 156 }, 157 { 158 begin: /%[qQwWx]?\{/, 159 end: /\}/ 160 }, 161 { 162 begin: /%[qQwWx]?</, 163 end: />/ 164 }, 165 { 166 begin: /%[qQwWx]?\//, 167 end: /\// 168 }, 169 { 170 begin: /%[qQwWx]?%/, 171 end: /%/ 172 }, 173 { 174 begin: /%[qQwWx]?-/, 175 end: /-/ 176 }, 177 { 178 begin: /%[qQwWx]?\|/, 179 end: /\|/ 180 }, 181 // in the following expressions, \B in the beginning suppresses recognition of ?-sequences 182 // where ? is the last character of a preceding identifier, as in: `func?4` 183 { begin: /\B\?(\\\d{1,3})/ }, 184 { begin: /\B\?(\\x[A-Fa-f0-9]{1,2})/ }, 185 { begin: /\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/ }, 186 { begin: /\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/ }, 187 { begin: /\B\?\\(c|C-)[\x20-\x7e]/ }, 188 { begin: /\B\?\\?\S/ }, 189 // heredocs 190 { 191 // this guard makes sure that we have an entire heredoc and not a false 192 // positive (auto-detect, etc.) 193 begin: regex.concat( 194 /<<[-~]?'?/, 195 regex.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/) 196 ), 197 contains: [ 198 hljs.END_SAME_AS_BEGIN({ 199 begin: /(\w+)/, 200 end: /(\w+)/, 201 contains: [ 202 hljs.BACKSLASH_ESCAPE, 203 SUBST 204 ] 205 }) 206 ] 207 } 208 ] 209 }; 210 211 // Ruby syntax is underdocumented, but this grammar seems to be accurate 212 // as of version 2.7.2 (confirmed with (irb and `Ripper.sexp(...)`) 213 // https://docs.ruby-lang.org/en/2.7.0/doc/syntax/literals_rdoc.html#label-Numbers 214 const decimal = '[1-9](_?[0-9])*|0'; 215 const digits = '[0-9](_?[0-9])*'; 216 const NUMBER = { 217 className: 'number', 218 relevance: 0, 219 variants: [ 220 // decimal integer/float, optionally exponential or rational, optionally imaginary 221 { begin: `\\b(${decimal})(\\.(${digits}))?([eE][+-]?(${digits})|r)?i?\\b` }, 222 223 // explicit decimal/binary/octal/hexadecimal integer, 224 // optionally rational and/or imaginary 225 { begin: "\\b0[dD][0-9](_?[0-9])*r?i?\\b" }, 226 { begin: "\\b0[bB][0-1](_?[0-1])*r?i?\\b" }, 227 { begin: "\\b0[oO][0-7](_?[0-7])*r?i?\\b" }, 228 { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b" }, 229 230 // 0-prefixed implicit octal integer, optionally rational and/or imaginary 231 { begin: "\\b0(_?[0-7])+r?i?\\b" } 232 ] 233 }; 234 235 const PARAMS = { 236 variants: [ 237 { 238 match: /\(\)/, 239 }, 240 { 241 className: 'params', 242 begin: /\(/, 243 end: /(?=\))/, 244 excludeBegin: true, 245 endsParent: true, 246 keywords: RUBY_KEYWORDS, 247 } 248 ] 249 }; 250 251 const INCLUDE_EXTEND = { 252 match: [ 253 /(include|extend)\s+/, 254 CLASS_NAME_WITH_NAMESPACE_RE 255 ], 256 scope: { 257 2: "title.class" 258 }, 259 keywords: RUBY_KEYWORDS 260 }; 261 262 const CLASS_DEFINITION = { 263 variants: [ 264 { 265 match: [ 266 /class\s+/, 267 CLASS_NAME_WITH_NAMESPACE_RE, 268 /\s+<\s+/, 269 CLASS_NAME_WITH_NAMESPACE_RE 270 ] 271 }, 272 { 273 match: [ 274 /\b(class|module)\s+/, 275 CLASS_NAME_WITH_NAMESPACE_RE 276 ] 277 } 278 ], 279 scope: { 280 2: "title.class", 281 4: "title.class.inherited" 282 }, 283 keywords: RUBY_KEYWORDS 284 }; 285 286 const UPPER_CASE_CONSTANT = { 287 relevance: 0, 288 match: /\b[A-Z][A-Z_0-9]+\b/, 289 className: "variable.constant" 290 }; 291 292 const METHOD_DEFINITION = { 293 match: [ 294 /def/, /\s+/, 295 RUBY_METHOD_RE 296 ], 297 scope: { 298 1: "keyword", 299 3: "title.function" 300 }, 301 contains: [ 302 PARAMS 303 ] 304 }; 305 306 const OBJECT_CREATION = { 307 relevance: 0, 308 match: [ 309 CLASS_NAME_WITH_NAMESPACE_RE, 310 /\.new[. (]/ 311 ], 312 scope: { 313 1: "title.class" 314 } 315 }; 316 317 // CamelCase 318 const CLASS_REFERENCE = { 319 relevance: 0, 320 match: CLASS_NAME_RE, 321 scope: "title.class" 322 }; 323 324 const RUBY_DEFAULT_CONTAINS = [ 325 STRING, 326 CLASS_DEFINITION, 327 INCLUDE_EXTEND, 328 OBJECT_CREATION, 329 UPPER_CASE_CONSTANT, 330 CLASS_REFERENCE, 331 METHOD_DEFINITION, 332 { 333 // swallow namespace qualifiers before symbols 334 begin: hljs.IDENT_RE + '::' }, 335 { 336 className: 'symbol', 337 begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:', 338 relevance: 0 339 }, 340 { 341 className: 'symbol', 342 begin: ':(?!\\s)', 343 contains: [ 344 STRING, 345 { begin: RUBY_METHOD_RE } 346 ], 347 relevance: 0 348 }, 349 NUMBER, 350 { 351 // negative-look forward attempts to prevent false matches like: 352 // @ident@ or $ident$ that might indicate this is not ruby at all 353 className: "variable", 354 begin: '(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])' + `(?![A-Za-z])(?![@$?'])` 355 }, 356 { 357 className: 'params', 358 begin: /\|/, 359 end: /\|/, 360 excludeBegin: true, 361 excludeEnd: true, 362 relevance: 0, // this could be a lot of things (in other languages) other than params 363 keywords: RUBY_KEYWORDS 364 }, 365 { // regexp container 366 begin: '(' + hljs.RE_STARTERS_RE + '|unless)\\s*', 367 keywords: 'unless', 368 contains: [ 369 { 370 className: 'regexp', 371 contains: [ 372 hljs.BACKSLASH_ESCAPE, 373 SUBST 374 ], 375 illegal: /\n/, 376 variants: [ 377 { 378 begin: '/', 379 end: '/[a-z]*' 380 }, 381 { 382 begin: /%r\{/, 383 end: /\}[a-z]*/ 384 }, 385 { 386 begin: '%r\\(', 387 end: '\\)[a-z]*' 388 }, 389 { 390 begin: '%r!', 391 end: '![a-z]*' 392 }, 393 { 394 begin: '%r\\[', 395 end: '\\][a-z]*' 396 } 397 ] 398 } 399 ].concat(IRB_OBJECT, COMMENT_MODES), 400 relevance: 0 401 } 402 ].concat(IRB_OBJECT, COMMENT_MODES); 403 404 SUBST.contains = RUBY_DEFAULT_CONTAINS; 405 PARAMS.contains = RUBY_DEFAULT_CONTAINS; 406 407 // >> 408 // ?> 409 const SIMPLE_PROMPT = "[>?]>"; 410 // irb(main):001:0> 411 const DEFAULT_PROMPT = "[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]"; 412 const RVM_PROMPT = "(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>"; 413 414 const IRB_DEFAULT = [ 415 { 416 begin: /^\s*=>/, 417 starts: { 418 end: '$', 419 contains: RUBY_DEFAULT_CONTAINS 420 } 421 }, 422 { 423 className: 'meta.prompt', 424 begin: '^(' + SIMPLE_PROMPT + "|" + DEFAULT_PROMPT + '|' + RVM_PROMPT + ')(?=[ ])', 425 starts: { 426 end: '$', 427 keywords: RUBY_KEYWORDS, 428 contains: RUBY_DEFAULT_CONTAINS 429 } 430 } 431 ]; 432 433 COMMENT_MODES.unshift(IRB_OBJECT); 434 435 return { 436 name: 'Ruby', 437 aliases: [ 438 'rb', 439 'gemspec', 440 'podspec', 441 'thor', 442 'irb' 443 ], 444 keywords: RUBY_KEYWORDS, 445 illegal: /\/\*/, 446 contains: [ hljs.SHEBANG({ binary: "ruby" }) ] 447 .concat(IRB_DEFAULT) 448 .concat(COMMENT_MODES) 449 .concat(RUBY_DEFAULT_CONTAINS) 450 }; 451 } 452 453 return ruby; 454 455 })(); 456 457 hljs.registerLanguage('ruby', hljsGrammar); 458 })();