elixir.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*
  2. Language: Elixir
  3. Author: Josh Adams <josh@isotope11.com>
  4. Description: language definition for Elixir source code files (.ex and .exs). Based on ruby language support.
  5. Category: functional
  6. Website: https://elixir-lang.org
  7. */
  8. /** @type LanguageFn */
  9. function elixir(hljs) {
  10. const regex = hljs.regex;
  11. const ELIXIR_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_.]*(!|\\?)?';
  12. const ELIXIR_METHOD_RE = '[a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?';
  13. const KEYWORDS = [
  14. "after",
  15. "alias",
  16. "and",
  17. "case",
  18. "catch",
  19. "cond",
  20. "defstruct",
  21. "defguard",
  22. "do",
  23. "else",
  24. "end",
  25. "fn",
  26. "for",
  27. "if",
  28. "import",
  29. "in",
  30. "not",
  31. "or",
  32. "quote",
  33. "raise",
  34. "receive",
  35. "require",
  36. "reraise",
  37. "rescue",
  38. "try",
  39. "unless",
  40. "unquote",
  41. "unquote_splicing",
  42. "use",
  43. "when",
  44. "with|0"
  45. ];
  46. const LITERALS = [
  47. "false",
  48. "nil",
  49. "true"
  50. ];
  51. const KWS = {
  52. $pattern: ELIXIR_IDENT_RE,
  53. keyword: KEYWORDS,
  54. literal: LITERALS
  55. };
  56. const SUBST = {
  57. className: 'subst',
  58. begin: /#\{/,
  59. end: /\}/,
  60. keywords: KWS
  61. };
  62. const NUMBER = {
  63. className: 'number',
  64. begin: '(\\b0o[0-7_]+)|(\\b0b[01_]+)|(\\b0x[0-9a-fA-F_]+)|(-?\\b[0-9][0-9_]*(\\.[0-9_]+([eE][-+]?[0-9]+)?)?)',
  65. relevance: 0
  66. };
  67. // TODO: could be tightened
  68. // https://elixir-lang.readthedocs.io/en/latest/intro/18.html
  69. // but you also need to include closing delemeters in the escape list per
  70. // individual sigil mode from what I can tell,
  71. // ie: \} might or might not be an escape depending on the sigil used
  72. const ESCAPES_RE = /\\[\s\S]/;
  73. // const ESCAPES_RE = /\\["'\\abdefnrstv0]/;
  74. const BACKSLASH_ESCAPE = {
  75. match: ESCAPES_RE,
  76. scope: "char.escape",
  77. relevance: 0
  78. };
  79. const SIGIL_DELIMITERS = '[/|([{<"\']';
  80. const SIGIL_DELIMITER_MODES = [
  81. {
  82. begin: /"/,
  83. end: /"/
  84. },
  85. {
  86. begin: /'/,
  87. end: /'/
  88. },
  89. {
  90. begin: /\//,
  91. end: /\//
  92. },
  93. {
  94. begin: /\|/,
  95. end: /\|/
  96. },
  97. {
  98. begin: /\(/,
  99. end: /\)/
  100. },
  101. {
  102. begin: /\[/,
  103. end: /\]/
  104. },
  105. {
  106. begin: /\{/,
  107. end: /\}/
  108. },
  109. {
  110. begin: /</,
  111. end: />/
  112. }
  113. ];
  114. const escapeSigilEnd = (end) => {
  115. return {
  116. scope: "char.escape",
  117. begin: regex.concat(/\\/, end),
  118. relevance: 0
  119. };
  120. };
  121. const LOWERCASE_SIGIL = {
  122. className: 'string',
  123. begin: '~[a-z]' + '(?=' + SIGIL_DELIMITERS + ')',
  124. contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x,
  125. { contains: [
  126. escapeSigilEnd(x.end),
  127. BACKSLASH_ESCAPE,
  128. SUBST
  129. ] }
  130. ))
  131. };
  132. const UPCASE_SIGIL = {
  133. className: 'string',
  134. begin: '~[A-Z]' + '(?=' + SIGIL_DELIMITERS + ')',
  135. contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x,
  136. { contains: [ escapeSigilEnd(x.end) ] }
  137. ))
  138. };
  139. const REGEX_SIGIL = {
  140. className: 'regex',
  141. variants: [
  142. {
  143. begin: '~r' + '(?=' + SIGIL_DELIMITERS + ')',
  144. contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x,
  145. {
  146. end: regex.concat(x.end, /[uismxfU]{0,7}/),
  147. contains: [
  148. escapeSigilEnd(x.end),
  149. BACKSLASH_ESCAPE,
  150. SUBST
  151. ]
  152. }
  153. ))
  154. },
  155. {
  156. begin: '~R' + '(?=' + SIGIL_DELIMITERS + ')',
  157. contains: SIGIL_DELIMITER_MODES.map(x => hljs.inherit(x,
  158. {
  159. end: regex.concat(x.end, /[uismxfU]{0,7}/),
  160. contains: [ escapeSigilEnd(x.end) ]
  161. })
  162. )
  163. }
  164. ]
  165. };
  166. const STRING = {
  167. className: 'string',
  168. contains: [
  169. hljs.BACKSLASH_ESCAPE,
  170. SUBST
  171. ],
  172. variants: [
  173. {
  174. begin: /"""/,
  175. end: /"""/
  176. },
  177. {
  178. begin: /'''/,
  179. end: /'''/
  180. },
  181. {
  182. begin: /~S"""/,
  183. end: /"""/,
  184. contains: [] // override default
  185. },
  186. {
  187. begin: /~S"/,
  188. end: /"/,
  189. contains: [] // override default
  190. },
  191. {
  192. begin: /~S'''/,
  193. end: /'''/,
  194. contains: [] // override default
  195. },
  196. {
  197. begin: /~S'/,
  198. end: /'/,
  199. contains: [] // override default
  200. },
  201. {
  202. begin: /'/,
  203. end: /'/
  204. },
  205. {
  206. begin: /"/,
  207. end: /"/
  208. }
  209. ]
  210. };
  211. const FUNCTION = {
  212. className: 'function',
  213. beginKeywords: 'def defp defmacro defmacrop',
  214. end: /\B\b/, // the mode is ended by the title
  215. contains: [
  216. hljs.inherit(hljs.TITLE_MODE, {
  217. begin: ELIXIR_IDENT_RE,
  218. endsParent: true
  219. })
  220. ]
  221. };
  222. const CLASS = hljs.inherit(FUNCTION, {
  223. className: 'class',
  224. beginKeywords: 'defimpl defmodule defprotocol defrecord',
  225. end: /\bdo\b|$|;/
  226. });
  227. const ELIXIR_DEFAULT_CONTAINS = [
  228. STRING,
  229. REGEX_SIGIL,
  230. UPCASE_SIGIL,
  231. LOWERCASE_SIGIL,
  232. hljs.HASH_COMMENT_MODE,
  233. CLASS,
  234. FUNCTION,
  235. { begin: '::' },
  236. {
  237. className: 'symbol',
  238. begin: ':(?![\\s:])',
  239. contains: [
  240. STRING,
  241. { begin: ELIXIR_METHOD_RE }
  242. ],
  243. relevance: 0
  244. },
  245. {
  246. className: 'symbol',
  247. begin: ELIXIR_IDENT_RE + ':(?!:)',
  248. relevance: 0
  249. },
  250. { // Usage of a module, struct, etc.
  251. className: 'title.class',
  252. begin: /(\b[A-Z][a-zA-Z0-9_]+)/,
  253. relevance: 0
  254. },
  255. NUMBER,
  256. {
  257. className: 'variable',
  258. begin: '(\\$\\W)|((\\$|@@?)(\\w+))'
  259. }
  260. // -> has been removed, capnproto always uses this grammar construct
  261. ];
  262. SUBST.contains = ELIXIR_DEFAULT_CONTAINS;
  263. return {
  264. name: 'Elixir',
  265. aliases: [
  266. 'ex',
  267. 'exs'
  268. ],
  269. keywords: KWS,
  270. contains: ELIXIR_DEFAULT_CONTAINS
  271. };
  272. }
  273. export { elixir as default };