ruby.js 8.6 KB

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