crystal.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /*
  2. Language: Crystal
  3. Author: TSUYUSATO Kitsune <make.just.on@gmail.com>
  4. Website: https://crystal-lang.org
  5. */
  6. /** @type LanguageFn */
  7. function crystal(hljs) {
  8. const INT_SUFFIX = '(_?[ui](8|16|32|64|128))?';
  9. const FLOAT_SUFFIX = '(_?f(32|64))?';
  10. const CRYSTAL_IDENT_RE = '[a-zA-Z_]\\w*[!?=]?';
  11. const CRYSTAL_METHOD_RE = '[a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|[=!]~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~|]|//|//=|&[-+*]=?|&\\*\\*|\\[\\][=?]?';
  12. const CRYSTAL_PATH_RE = '[A-Za-z_]\\w*(::\\w+)*(\\?|!)?';
  13. const CRYSTAL_KEYWORDS = {
  14. $pattern: CRYSTAL_IDENT_RE,
  15. keyword:
  16. 'abstract alias annotation as as? asm begin break case class def do else elsif end ensure enum extend for fun if ' +
  17. 'include instance_sizeof is_a? lib macro module next nil? of out pointerof private protected rescue responds_to? ' +
  18. 'return require select self sizeof struct super then type typeof union uninitialized unless until verbatim when while with yield ' +
  19. '__DIR__ __END_LINE__ __FILE__ __LINE__',
  20. literal: 'false nil true'
  21. };
  22. const SUBST = {
  23. className: 'subst',
  24. begin: /#\{/,
  25. end: /\}/,
  26. keywords: CRYSTAL_KEYWORDS
  27. };
  28. const EXPANSION = {
  29. className: 'template-variable',
  30. variants: [
  31. {
  32. begin: '\\{\\{',
  33. end: '\\}\\}'
  34. },
  35. {
  36. begin: '\\{%',
  37. end: '%\\}'
  38. }
  39. ],
  40. keywords: CRYSTAL_KEYWORDS
  41. };
  42. function recursiveParen(begin, end) {
  43. const
  44. contains = [
  45. {
  46. begin: begin,
  47. end: end
  48. }
  49. ];
  50. contains[0].contains = contains;
  51. return contains;
  52. }
  53. const STRING = {
  54. className: 'string',
  55. contains: [
  56. hljs.BACKSLASH_ESCAPE,
  57. SUBST
  58. ],
  59. variants: [
  60. {
  61. begin: /'/,
  62. end: /'/
  63. },
  64. {
  65. begin: /"/,
  66. end: /"/
  67. },
  68. {
  69. begin: /`/,
  70. end: /`/
  71. },
  72. {
  73. begin: '%[Qwi]?\\(',
  74. end: '\\)',
  75. contains: recursiveParen('\\(', '\\)')
  76. },
  77. {
  78. begin: '%[Qwi]?\\[',
  79. end: '\\]',
  80. contains: recursiveParen('\\[', '\\]')
  81. },
  82. {
  83. begin: '%[Qwi]?\\{',
  84. end: /\}/,
  85. contains: recursiveParen(/\{/, /\}/)
  86. },
  87. {
  88. begin: '%[Qwi]?<',
  89. end: '>',
  90. contains: recursiveParen('<', '>')
  91. },
  92. {
  93. begin: '%[Qwi]?\\|',
  94. end: '\\|'
  95. },
  96. {
  97. begin: /<<-\w+$/,
  98. end: /^\s*\w+$/
  99. }
  100. ],
  101. relevance: 0
  102. };
  103. const Q_STRING = {
  104. className: 'string',
  105. variants: [
  106. {
  107. begin: '%q\\(',
  108. end: '\\)',
  109. contains: recursiveParen('\\(', '\\)')
  110. },
  111. {
  112. begin: '%q\\[',
  113. end: '\\]',
  114. contains: recursiveParen('\\[', '\\]')
  115. },
  116. {
  117. begin: '%q\\{',
  118. end: /\}/,
  119. contains: recursiveParen(/\{/, /\}/)
  120. },
  121. {
  122. begin: '%q<',
  123. end: '>',
  124. contains: recursiveParen('<', '>')
  125. },
  126. {
  127. begin: '%q\\|',
  128. end: '\\|'
  129. },
  130. {
  131. begin: /<<-'\w+'$/,
  132. end: /^\s*\w+$/
  133. }
  134. ],
  135. relevance: 0
  136. };
  137. const REGEXP = {
  138. begin: '(?!%\\})(' + hljs.RE_STARTERS_RE + '|\\n|\\b(case|if|select|unless|until|when|while)\\b)\\s*',
  139. keywords: 'case if select unless until when while',
  140. contains: [
  141. {
  142. className: 'regexp',
  143. contains: [
  144. hljs.BACKSLASH_ESCAPE,
  145. SUBST
  146. ],
  147. variants: [
  148. {
  149. begin: '//[a-z]*',
  150. relevance: 0
  151. },
  152. {
  153. begin: '/(?!\\/)',
  154. end: '/[a-z]*'
  155. }
  156. ]
  157. }
  158. ],
  159. relevance: 0
  160. };
  161. const REGEXP2 = {
  162. className: 'regexp',
  163. contains: [
  164. hljs.BACKSLASH_ESCAPE,
  165. SUBST
  166. ],
  167. variants: [
  168. {
  169. begin: '%r\\(',
  170. end: '\\)',
  171. contains: recursiveParen('\\(', '\\)')
  172. },
  173. {
  174. begin: '%r\\[',
  175. end: '\\]',
  176. contains: recursiveParen('\\[', '\\]')
  177. },
  178. {
  179. begin: '%r\\{',
  180. end: /\}/,
  181. contains: recursiveParen(/\{/, /\}/)
  182. },
  183. {
  184. begin: '%r<',
  185. end: '>',
  186. contains: recursiveParen('<', '>')
  187. },
  188. {
  189. begin: '%r\\|',
  190. end: '\\|'
  191. }
  192. ],
  193. relevance: 0
  194. };
  195. const ATTRIBUTE = {
  196. className: 'meta',
  197. begin: '@\\[',
  198. end: '\\]',
  199. contains: [
  200. hljs.inherit(hljs.QUOTE_STRING_MODE, {
  201. className: 'meta-string'
  202. })
  203. ]
  204. };
  205. const CRYSTAL_DEFAULT_CONTAINS = [
  206. EXPANSION,
  207. STRING,
  208. Q_STRING,
  209. REGEXP2,
  210. REGEXP,
  211. ATTRIBUTE,
  212. hljs.HASH_COMMENT_MODE,
  213. {
  214. className: 'class',
  215. beginKeywords: 'class module struct',
  216. end: '$|;',
  217. illegal: /=/,
  218. contains: [
  219. hljs.HASH_COMMENT_MODE,
  220. hljs.inherit(hljs.TITLE_MODE, {
  221. begin: CRYSTAL_PATH_RE
  222. }),
  223. { // relevance booster for inheritance
  224. begin: '<'
  225. }
  226. ]
  227. },
  228. {
  229. className: 'class',
  230. beginKeywords: 'lib enum union',
  231. end: '$|;',
  232. illegal: /=/,
  233. contains: [
  234. hljs.HASH_COMMENT_MODE,
  235. hljs.inherit(hljs.TITLE_MODE, {
  236. begin: CRYSTAL_PATH_RE
  237. })
  238. ]
  239. },
  240. {
  241. beginKeywords: 'annotation',
  242. end: '$|;',
  243. illegal: /=/,
  244. contains: [
  245. hljs.HASH_COMMENT_MODE,
  246. hljs.inherit(hljs.TITLE_MODE, {
  247. begin: CRYSTAL_PATH_RE
  248. })
  249. ],
  250. relevance: 2
  251. },
  252. {
  253. className: 'function',
  254. beginKeywords: 'def',
  255. end: /\B\b/,
  256. contains: [
  257. hljs.inherit(hljs.TITLE_MODE, {
  258. begin: CRYSTAL_METHOD_RE,
  259. endsParent: true
  260. })
  261. ]
  262. },
  263. {
  264. className: 'function',
  265. beginKeywords: 'fun macro',
  266. end: /\B\b/,
  267. contains: [
  268. hljs.inherit(hljs.TITLE_MODE, {
  269. begin: CRYSTAL_METHOD_RE,
  270. endsParent: true
  271. })
  272. ],
  273. relevance: 2
  274. },
  275. {
  276. className: 'symbol',
  277. begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:',
  278. relevance: 0
  279. },
  280. {
  281. className: 'symbol',
  282. begin: ':',
  283. contains: [
  284. STRING,
  285. {
  286. begin: CRYSTAL_METHOD_RE
  287. }
  288. ],
  289. relevance: 0
  290. },
  291. {
  292. className: 'number',
  293. variants: [
  294. {
  295. begin: '\\b0b([01_]+)' + INT_SUFFIX
  296. },
  297. {
  298. begin: '\\b0o([0-7_]+)' + INT_SUFFIX
  299. },
  300. {
  301. begin: '\\b0x([A-Fa-f0-9_]+)' + INT_SUFFIX
  302. },
  303. {
  304. begin: '\\b([1-9][0-9_]*[0-9]|[0-9])(\\.[0-9][0-9_]*)?([eE]_?[-+]?[0-9_]*)?' + FLOAT_SUFFIX + '(?!_)'
  305. },
  306. {
  307. begin: '\\b([1-9][0-9_]*|0)' + INT_SUFFIX
  308. }
  309. ],
  310. relevance: 0
  311. }
  312. ];
  313. SUBST.contains = CRYSTAL_DEFAULT_CONTAINS;
  314. EXPANSION.contains = CRYSTAL_DEFAULT_CONTAINS.slice(1); // without EXPANSION
  315. return {
  316. name: 'Crystal',
  317. aliases: [ 'cr' ],
  318. keywords: CRYSTAL_KEYWORDS,
  319. contains: CRYSTAL_DEFAULT_CONTAINS
  320. };
  321. }
  322. module.exports = crystal;