crystal.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. CodeMirror.defineMode('crystal', function (config) {
  15. function wordRegExp(words, end) {
  16. return new RegExp((end ? '' : '^') + '(?:' + words.join('|') + ')' + (end ? '$' : '\\b'))
  17. }
  18. function chain(tokenize, stream, state) {
  19. state.tokenize.push(tokenize)
  20. return tokenize(stream, state)
  21. }
  22. var operators = /^(?:[-+/%|&^]|\*\*?|[<>]{2})/
  23. var conditionalOperators = /^(?:[=!]~|===|<=>|[<>=!]=?|[|&]{2}|~)/
  24. var indexingOperators = /^(?:\[\][?=]?)/
  25. var anotherOperators = /^(?:\.(?:\.{2})?|->|[?:])/
  26. var idents = /^[a-z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/
  27. var types = /^[A-Z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/
  28. var keywords = wordRegExp([
  29. 'abstract',
  30. 'alias',
  31. 'as',
  32. 'asm',
  33. 'begin',
  34. 'break',
  35. 'case',
  36. 'class',
  37. 'def',
  38. 'do',
  39. 'else',
  40. 'elsif',
  41. 'end',
  42. 'ensure',
  43. 'enum',
  44. 'extend',
  45. 'for',
  46. 'fun',
  47. 'if',
  48. 'include',
  49. 'instance_sizeof',
  50. 'lib',
  51. 'macro',
  52. 'module',
  53. 'next',
  54. 'of',
  55. 'out',
  56. 'pointerof',
  57. 'private',
  58. 'protected',
  59. 'rescue',
  60. 'return',
  61. 'require',
  62. 'select',
  63. 'sizeof',
  64. 'struct',
  65. 'super',
  66. 'then',
  67. 'type',
  68. 'typeof',
  69. 'uninitialized',
  70. 'union',
  71. 'unless',
  72. 'until',
  73. 'when',
  74. 'while',
  75. 'with',
  76. 'yield',
  77. '__DIR__',
  78. '__END_LINE__',
  79. '__FILE__',
  80. '__LINE__',
  81. ])
  82. var atomWords = wordRegExp(['true', 'false', 'nil', 'self'])
  83. var indentKeywordsArray = ['def', 'fun', 'macro', 'class', 'module', 'struct', 'lib', 'enum', 'union', 'do', 'for']
  84. var indentKeywords = wordRegExp(indentKeywordsArray)
  85. var indentExpressionKeywordsArray = ['if', 'unless', 'case', 'while', 'until', 'begin', 'then']
  86. var indentExpressionKeywords = wordRegExp(indentExpressionKeywordsArray)
  87. var dedentKeywordsArray = ['end', 'else', 'elsif', 'rescue', 'ensure']
  88. var dedentKeywords = wordRegExp(dedentKeywordsArray)
  89. var dedentPunctualsArray = ['\\)', '\\}', '\\]']
  90. var dedentPunctuals = new RegExp('^(?:' + dedentPunctualsArray.join('|') + ')$')
  91. var nextTokenizer = {
  92. def: tokenFollowIdent,
  93. fun: tokenFollowIdent,
  94. macro: tokenMacroDef,
  95. class: tokenFollowType,
  96. module: tokenFollowType,
  97. struct: tokenFollowType,
  98. lib: tokenFollowType,
  99. enum: tokenFollowType,
  100. union: tokenFollowType,
  101. }
  102. var matching = { '[': ']', '{': '}', '(': ')', '<': '>' }
  103. function tokenBase(stream, state) {
  104. if (stream.eatSpace()) {
  105. return null
  106. }
  107. // Macros
  108. if (state.lastToken != '\\' && stream.match('{%', false)) {
  109. return chain(tokenMacro('%', '%'), stream, state)
  110. }
  111. if (state.lastToken != '\\' && stream.match('{{', false)) {
  112. return chain(tokenMacro('{', '}'), stream, state)
  113. }
  114. // Comments
  115. if (stream.peek() == '#') {
  116. stream.skipToEnd()
  117. return 'comment'
  118. }
  119. // Variables and keywords
  120. var matched
  121. if (stream.match(idents)) {
  122. stream.eat(/[?!]/)
  123. matched = stream.current()
  124. if (stream.eat(':')) {
  125. return 'atom'
  126. } else if (state.lastToken == '.') {
  127. return 'property'
  128. } else if (keywords.test(matched)) {
  129. if (indentKeywords.test(matched)) {
  130. if (!(matched == 'fun' && state.blocks.indexOf('lib') >= 0) && !(matched == 'def' && state.lastToken == 'abstract')) {
  131. state.blocks.push(matched)
  132. state.currentIndent += 1
  133. }
  134. } else if ((state.lastStyle == 'operator' || !state.lastStyle) && indentExpressionKeywords.test(matched)) {
  135. state.blocks.push(matched)
  136. state.currentIndent += 1
  137. } else if (matched == 'end') {
  138. state.blocks.pop()
  139. state.currentIndent -= 1
  140. }
  141. if (nextTokenizer.hasOwnProperty(matched)) {
  142. state.tokenize.push(nextTokenizer[matched])
  143. }
  144. return 'keyword'
  145. } else if (atomWords.test(matched)) {
  146. return 'atom'
  147. }
  148. return 'variable'
  149. }
  150. // Class variables and instance variables
  151. // or attributes
  152. if (stream.eat('@')) {
  153. if (stream.peek() == '[') {
  154. return chain(tokenNest('[', ']', 'meta'), stream, state)
  155. }
  156. stream.eat('@')
  157. stream.match(idents) || stream.match(types)
  158. return 'variable-2'
  159. }
  160. // Constants and types
  161. if (stream.match(types)) {
  162. return 'tag'
  163. }
  164. // Symbols or ':' operator
  165. if (stream.eat(':')) {
  166. if (stream.eat('"')) {
  167. return chain(tokenQuote('"', 'atom', false), stream, state)
  168. } else if (stream.match(idents) || stream.match(types) || stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators)) {
  169. return 'atom'
  170. }
  171. stream.eat(':')
  172. return 'operator'
  173. }
  174. // Strings
  175. if (stream.eat('"')) {
  176. return chain(tokenQuote('"', 'string', true), stream, state)
  177. }
  178. // Strings or regexps or macro variables or '%' operator
  179. if (stream.peek() == '%') {
  180. var style = 'string'
  181. var embed = true
  182. var delim
  183. if (stream.match('%r')) {
  184. // Regexps
  185. style = 'string-2'
  186. delim = stream.next()
  187. } else if (stream.match('%w')) {
  188. embed = false
  189. delim = stream.next()
  190. } else if (stream.match('%q')) {
  191. embed = false
  192. delim = stream.next()
  193. } else {
  194. if ((delim = stream.match(/^%([^\w\s=])/))) {
  195. delim = delim[1]
  196. } else if (stream.match(/^%[a-zA-Z_\u009F-\uFFFF][\w\u009F-\uFFFF]*/)) {
  197. // Macro variables
  198. return 'meta'
  199. } else if (stream.eat('%')) {
  200. // '%' operator
  201. return 'operator'
  202. }
  203. }
  204. if (matching.hasOwnProperty(delim)) {
  205. delim = matching[delim]
  206. }
  207. return chain(tokenQuote(delim, style, embed), stream, state)
  208. }
  209. // Here Docs
  210. if ((matched = stream.match(/^<<-('?)([A-Z]\w*)\1/))) {
  211. return chain(tokenHereDoc(matched[2], !matched[1]), stream, state)
  212. }
  213. // Characters
  214. if (stream.eat("'")) {
  215. stream.match(/^(?:[^']|\\(?:[befnrtv0'"]|[0-7]{3}|u(?:[0-9a-fA-F]{4}|\{[0-9a-fA-F]{1,6}\})))/)
  216. stream.eat("'")
  217. return 'atom'
  218. }
  219. // Numbers
  220. if (stream.eat('0')) {
  221. if (stream.eat('x')) {
  222. stream.match(/^[0-9a-fA-F_]+/)
  223. } else if (stream.eat('o')) {
  224. stream.match(/^[0-7_]+/)
  225. } else if (stream.eat('b')) {
  226. stream.match(/^[01_]+/)
  227. }
  228. return 'number'
  229. }
  230. if (stream.eat(/^\d/)) {
  231. stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?/)
  232. return 'number'
  233. }
  234. // Operators
  235. if (stream.match(operators)) {
  236. stream.eat('=') // Operators can follow assign symbol.
  237. return 'operator'
  238. }
  239. if (stream.match(conditionalOperators) || stream.match(anotherOperators)) {
  240. return 'operator'
  241. }
  242. // Parens and braces
  243. if ((matched = stream.match(/[({[]/, false))) {
  244. matched = matched[0]
  245. return chain(tokenNest(matched, matching[matched], null), stream, state)
  246. }
  247. // Escapes
  248. if (stream.eat('\\')) {
  249. stream.next()
  250. return 'meta'
  251. }
  252. stream.next()
  253. return null
  254. }
  255. function tokenNest(begin, end, style, started) {
  256. return function (stream, state) {
  257. if (!started && stream.match(begin)) {
  258. state.tokenize[state.tokenize.length - 1] = tokenNest(begin, end, style, true)
  259. state.currentIndent += 1
  260. return style
  261. }
  262. var nextStyle = tokenBase(stream, state)
  263. if (stream.current() === end) {
  264. state.tokenize.pop()
  265. state.currentIndent -= 1
  266. nextStyle = style
  267. }
  268. return nextStyle
  269. }
  270. }
  271. function tokenMacro(begin, end, started) {
  272. return function (stream, state) {
  273. if (!started && stream.match('{' + begin)) {
  274. state.currentIndent += 1
  275. state.tokenize[state.tokenize.length - 1] = tokenMacro(begin, end, true)
  276. return 'meta'
  277. }
  278. if (stream.match(end + '}')) {
  279. state.currentIndent -= 1
  280. state.tokenize.pop()
  281. return 'meta'
  282. }
  283. return tokenBase(stream, state)
  284. }
  285. }
  286. function tokenMacroDef(stream, state) {
  287. if (stream.eatSpace()) {
  288. return null
  289. }
  290. var matched
  291. if ((matched = stream.match(idents))) {
  292. if (matched == 'def') {
  293. return 'keyword'
  294. }
  295. stream.eat(/[?!]/)
  296. }
  297. state.tokenize.pop()
  298. return 'def'
  299. }
  300. function tokenFollowIdent(stream, state) {
  301. if (stream.eatSpace()) {
  302. return null
  303. }
  304. if (stream.match(idents)) {
  305. stream.eat(/[!?]/)
  306. } else {
  307. stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators)
  308. }
  309. state.tokenize.pop()
  310. return 'def'
  311. }
  312. function tokenFollowType(stream, state) {
  313. if (stream.eatSpace()) {
  314. return null
  315. }
  316. stream.match(types)
  317. state.tokenize.pop()
  318. return 'def'
  319. }
  320. function tokenQuote(end, style, embed) {
  321. return function (stream, state) {
  322. var escaped = false
  323. while (stream.peek()) {
  324. if (!escaped) {
  325. if (stream.match('{%', false)) {
  326. state.tokenize.push(tokenMacro('%', '%'))
  327. return style
  328. }
  329. if (stream.match('{{', false)) {
  330. state.tokenize.push(tokenMacro('{', '}'))
  331. return style
  332. }
  333. if (embed && stream.match('#{', false)) {
  334. state.tokenize.push(tokenNest('#{', '}', 'meta'))
  335. return style
  336. }
  337. var ch = stream.next()
  338. if (ch == end) {
  339. state.tokenize.pop()
  340. return style
  341. }
  342. escaped = embed && ch == '\\'
  343. } else {
  344. stream.next()
  345. escaped = false
  346. }
  347. }
  348. return style
  349. }
  350. }
  351. function tokenHereDoc(phrase, embed) {
  352. return function (stream, state) {
  353. if (stream.sol()) {
  354. stream.eatSpace()
  355. if (stream.match(phrase)) {
  356. state.tokenize.pop()
  357. return 'string'
  358. }
  359. }
  360. var escaped = false
  361. while (stream.peek()) {
  362. if (!escaped) {
  363. if (stream.match('{%', false)) {
  364. state.tokenize.push(tokenMacro('%', '%'))
  365. return 'string'
  366. }
  367. if (stream.match('{{', false)) {
  368. state.tokenize.push(tokenMacro('{', '}'))
  369. return 'string'
  370. }
  371. if (embed && stream.match('#{', false)) {
  372. state.tokenize.push(tokenNest('#{', '}', 'meta'))
  373. return 'string'
  374. }
  375. escaped = embed && stream.next() == '\\'
  376. } else {
  377. stream.next()
  378. escaped = false
  379. }
  380. }
  381. return 'string'
  382. }
  383. }
  384. return {
  385. startState: function () {
  386. return {
  387. tokenize: [tokenBase],
  388. currentIndent: 0,
  389. lastToken: null,
  390. lastStyle: null,
  391. blocks: [],
  392. }
  393. },
  394. token: function (stream, state) {
  395. var style = state.tokenize[state.tokenize.length - 1](stream, state)
  396. var token = stream.current()
  397. if (style && style != 'comment') {
  398. state.lastToken = token
  399. state.lastStyle = style
  400. }
  401. return style
  402. },
  403. indent: function (state, textAfter) {
  404. textAfter = textAfter.replace(/^\s*(?:\{%)?\s*|\s*(?:%\})?\s*$/g, '')
  405. if (dedentKeywords.test(textAfter) || dedentPunctuals.test(textAfter)) {
  406. return config.indentUnit * (state.currentIndent - 1)
  407. }
  408. return config.indentUnit * state.currentIndent
  409. },
  410. fold: 'indent',
  411. electricInput: wordRegExp(dedentPunctualsArray.concat(dedentKeywordsArray), true),
  412. lineComment: '#',
  413. }
  414. })
  415. CodeMirror.defineMIME('text/x-crystal', 'crystal')
  416. })