erlang.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. /*jshint unused:true, eqnull:true, curly:true, bitwise:true */
  4. /*jshint undef:true, latedef:true, trailing:true */
  5. /*global CodeMirror:true */
  6. // erlang mode.
  7. // tokenizer -> token types -> CodeMirror styles
  8. // tokenizer maintains a parse stack
  9. // indenter uses the parse stack
  10. // TODO indenter:
  11. // bit syntax
  12. // old guard/bif/conversion clashes (e.g. "float/1")
  13. // type/spec/opaque
  14. ;(function (mod) {
  15. if (typeof exports == 'object' && typeof module == 'object')
  16. // CommonJS
  17. mod(require('../../lib/codemirror'))
  18. else if (typeof define == 'function' && define.amd)
  19. // AMD
  20. define(['../../lib/codemirror'], mod)
  21. // Plain browser env
  22. else mod(CodeMirror)
  23. })(function (CodeMirror) {
  24. 'use strict'
  25. CodeMirror.defineMIME('text/x-erlang', 'erlang')
  26. CodeMirror.defineMode('erlang', function (cmCfg) {
  27. 'use strict'
  28. /////////////////////////////////////////////////////////////////////////////
  29. // constants
  30. var typeWords = ['-type', '-spec', '-export_type', '-opaque']
  31. var keywordWords = ['after', 'begin', 'catch', 'case', 'cond', 'end', 'fun', 'if', 'let', 'of', 'query', 'receive', 'try', 'when']
  32. var separatorRE = /[\->,;]/
  33. var separatorWords = ['->', ';', ',']
  34. var operatorAtomWords = ['and', 'andalso', 'band', 'bnot', 'bor', 'bsl', 'bsr', 'bxor', 'div', 'not', 'or', 'orelse', 'rem', 'xor']
  35. var operatorSymbolRE = /[\+\-\*\/<>=\|:!]/
  36. var operatorSymbolWords = ['=', '+', '-', '*', '/', '>', '>=', '<', '=<', '=:=', '==', '=/=', '/=', '||', '<-', '!']
  37. var openParenRE = /[<\(\[\{]/
  38. var openParenWords = ['<<', '(', '[', '{']
  39. var closeParenRE = /[>\)\]\}]/
  40. var closeParenWords = ['}', ']', ')', '>>']
  41. var guardWords = [
  42. 'is_atom',
  43. 'is_binary',
  44. 'is_bitstring',
  45. 'is_boolean',
  46. 'is_float',
  47. 'is_function',
  48. 'is_integer',
  49. 'is_list',
  50. 'is_number',
  51. 'is_pid',
  52. 'is_port',
  53. 'is_record',
  54. 'is_reference',
  55. 'is_tuple',
  56. 'atom',
  57. 'binary',
  58. 'bitstring',
  59. 'boolean',
  60. 'function',
  61. 'integer',
  62. 'list',
  63. 'number',
  64. 'pid',
  65. 'port',
  66. 'record',
  67. 'reference',
  68. 'tuple',
  69. ]
  70. var bifWords = [
  71. 'abs',
  72. 'adler32',
  73. 'adler32_combine',
  74. 'alive',
  75. 'apply',
  76. 'atom_to_binary',
  77. 'atom_to_list',
  78. 'binary_to_atom',
  79. 'binary_to_existing_atom',
  80. 'binary_to_list',
  81. 'binary_to_term',
  82. 'bit_size',
  83. 'bitstring_to_list',
  84. 'byte_size',
  85. 'check_process_code',
  86. 'contact_binary',
  87. 'crc32',
  88. 'crc32_combine',
  89. 'date',
  90. 'decode_packet',
  91. 'delete_module',
  92. 'disconnect_node',
  93. 'element',
  94. 'erase',
  95. 'exit',
  96. 'float',
  97. 'float_to_list',
  98. 'garbage_collect',
  99. 'get',
  100. 'get_keys',
  101. 'group_leader',
  102. 'halt',
  103. 'hd',
  104. 'integer_to_list',
  105. 'internal_bif',
  106. 'iolist_size',
  107. 'iolist_to_binary',
  108. 'is_alive',
  109. 'is_atom',
  110. 'is_binary',
  111. 'is_bitstring',
  112. 'is_boolean',
  113. 'is_float',
  114. 'is_function',
  115. 'is_integer',
  116. 'is_list',
  117. 'is_number',
  118. 'is_pid',
  119. 'is_port',
  120. 'is_process_alive',
  121. 'is_record',
  122. 'is_reference',
  123. 'is_tuple',
  124. 'length',
  125. 'link',
  126. 'list_to_atom',
  127. 'list_to_binary',
  128. 'list_to_bitstring',
  129. 'list_to_existing_atom',
  130. 'list_to_float',
  131. 'list_to_integer',
  132. 'list_to_pid',
  133. 'list_to_tuple',
  134. 'load_module',
  135. 'make_ref',
  136. 'module_loaded',
  137. 'monitor_node',
  138. 'node',
  139. 'node_link',
  140. 'node_unlink',
  141. 'nodes',
  142. 'notalive',
  143. 'now',
  144. 'open_port',
  145. 'pid_to_list',
  146. 'port_close',
  147. 'port_command',
  148. 'port_connect',
  149. 'port_control',
  150. 'pre_loaded',
  151. 'process_flag',
  152. 'process_info',
  153. 'processes',
  154. 'purge_module',
  155. 'put',
  156. 'register',
  157. 'registered',
  158. 'round',
  159. 'self',
  160. 'setelement',
  161. 'size',
  162. 'spawn',
  163. 'spawn_link',
  164. 'spawn_monitor',
  165. 'spawn_opt',
  166. 'split_binary',
  167. 'statistics',
  168. 'term_to_binary',
  169. 'time',
  170. 'throw',
  171. 'tl',
  172. 'trunc',
  173. 'tuple_size',
  174. 'tuple_to_list',
  175. 'unlink',
  176. 'unregister',
  177. 'whereis',
  178. ]
  179. // upper case: [A-Z] [Ø-Þ] [À-Ö]
  180. // lower case: [a-z] [ß-ö] [ø-ÿ]
  181. var anumRE = /[\w@Ø-ÞÀ-Öß-öø-ÿ]/
  182. var escapesRE = /[0-7]{1,3}|[bdefnrstv\\"']|\^[a-zA-Z]|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}/
  183. /////////////////////////////////////////////////////////////////////////////
  184. // tokenizer
  185. function tokenizer(stream, state) {
  186. // in multi-line string
  187. if (state.in_string) {
  188. state.in_string = !doubleQuote(stream)
  189. return rval(state, stream, 'string')
  190. }
  191. // in multi-line atom
  192. if (state.in_atom) {
  193. state.in_atom = !singleQuote(stream)
  194. return rval(state, stream, 'atom')
  195. }
  196. // whitespace
  197. if (stream.eatSpace()) {
  198. return rval(state, stream, 'whitespace')
  199. }
  200. // attributes and type specs
  201. if (!peekToken(state) && stream.match(/-\s*[a-zß-öø-ÿ][\wØ-ÞÀ-Öß-öø-ÿ]*/)) {
  202. if (is_member(stream.current(), typeWords)) {
  203. return rval(state, stream, 'type')
  204. } else {
  205. return rval(state, stream, 'attribute')
  206. }
  207. }
  208. var ch = stream.next()
  209. // comment
  210. if (ch == '%') {
  211. stream.skipToEnd()
  212. return rval(state, stream, 'comment')
  213. }
  214. // colon
  215. if (ch == ':') {
  216. return rval(state, stream, 'colon')
  217. }
  218. // macro
  219. if (ch == '?') {
  220. stream.eatSpace()
  221. stream.eatWhile(anumRE)
  222. return rval(state, stream, 'macro')
  223. }
  224. // record
  225. if (ch == '#') {
  226. stream.eatSpace()
  227. stream.eatWhile(anumRE)
  228. return rval(state, stream, 'record')
  229. }
  230. // dollar escape
  231. if (ch == '$') {
  232. if (stream.next() == '\\' && !stream.match(escapesRE)) {
  233. return rval(state, stream, 'error')
  234. }
  235. return rval(state, stream, 'number')
  236. }
  237. // dot
  238. if (ch == '.') {
  239. return rval(state, stream, 'dot')
  240. }
  241. // quoted atom
  242. if (ch == "'") {
  243. if (!(state.in_atom = !singleQuote(stream))) {
  244. if (stream.match(/\s*\/\s*[0-9]/, false)) {
  245. stream.match(/\s*\/\s*[0-9]/, true)
  246. return rval(state, stream, 'fun') // 'f'/0 style fun
  247. }
  248. if (stream.match(/\s*\(/, false) || stream.match(/\s*:/, false)) {
  249. return rval(state, stream, 'function')
  250. }
  251. }
  252. return rval(state, stream, 'atom')
  253. }
  254. // string
  255. if (ch == '"') {
  256. state.in_string = !doubleQuote(stream)
  257. return rval(state, stream, 'string')
  258. }
  259. // variable
  260. if (/[A-Z_Ø-ÞÀ-Ö]/.test(ch)) {
  261. stream.eatWhile(anumRE)
  262. return rval(state, stream, 'variable')
  263. }
  264. // atom/keyword/BIF/function
  265. if (/[a-z_ß-öø-ÿ]/.test(ch)) {
  266. stream.eatWhile(anumRE)
  267. if (stream.match(/\s*\/\s*[0-9]/, false)) {
  268. stream.match(/\s*\/\s*[0-9]/, true)
  269. return rval(state, stream, 'fun') // f/0 style fun
  270. }
  271. var w = stream.current()
  272. if (is_member(w, keywordWords)) {
  273. return rval(state, stream, 'keyword')
  274. } else if (is_member(w, operatorAtomWords)) {
  275. return rval(state, stream, 'operator')
  276. } else if (stream.match(/\s*\(/, false)) {
  277. // 'put' and 'erlang:put' are bifs, 'foo:put' is not
  278. if (is_member(w, bifWords) && (peekToken(state).token != ':' || peekToken(state, 2).token == 'erlang')) {
  279. return rval(state, stream, 'builtin')
  280. } else if (is_member(w, guardWords)) {
  281. return rval(state, stream, 'guard')
  282. } else {
  283. return rval(state, stream, 'function')
  284. }
  285. } else if (lookahead(stream) == ':') {
  286. if (w == 'erlang') {
  287. return rval(state, stream, 'builtin')
  288. } else {
  289. return rval(state, stream, 'function')
  290. }
  291. } else if (is_member(w, ['true', 'false'])) {
  292. return rval(state, stream, 'boolean')
  293. } else {
  294. return rval(state, stream, 'atom')
  295. }
  296. }
  297. // number
  298. var digitRE = /[0-9]/
  299. var radixRE = /[0-9a-zA-Z]/ // 36#zZ style int
  300. if (digitRE.test(ch)) {
  301. stream.eatWhile(digitRE)
  302. if (stream.eat('#')) {
  303. // 36#aZ style integer
  304. if (!stream.eatWhile(radixRE)) {
  305. stream.backUp(1) //"36#" - syntax error
  306. }
  307. } else if (stream.eat('.')) {
  308. // float
  309. if (!stream.eatWhile(digitRE)) {
  310. stream.backUp(1) // "3." - probably end of function
  311. } else {
  312. if (stream.eat(/[eE]/)) {
  313. // float with exponent
  314. if (stream.eat(/[-+]/)) {
  315. if (!stream.eatWhile(digitRE)) {
  316. stream.backUp(2) // "2e-" - syntax error
  317. }
  318. } else {
  319. if (!stream.eatWhile(digitRE)) {
  320. stream.backUp(1) // "2e" - syntax error
  321. }
  322. }
  323. }
  324. }
  325. }
  326. return rval(state, stream, 'number') // normal integer
  327. }
  328. // open parens
  329. if (nongreedy(stream, openParenRE, openParenWords)) {
  330. return rval(state, stream, 'open_paren')
  331. }
  332. // close parens
  333. if (nongreedy(stream, closeParenRE, closeParenWords)) {
  334. return rval(state, stream, 'close_paren')
  335. }
  336. // separators
  337. if (greedy(stream, separatorRE, separatorWords)) {
  338. return rval(state, stream, 'separator')
  339. }
  340. // operators
  341. if (greedy(stream, operatorSymbolRE, operatorSymbolWords)) {
  342. return rval(state, stream, 'operator')
  343. }
  344. return rval(state, stream, null)
  345. }
  346. /////////////////////////////////////////////////////////////////////////////
  347. // utilities
  348. function nongreedy(stream, re, words) {
  349. if (stream.current().length == 1 && re.test(stream.current())) {
  350. stream.backUp(1)
  351. while (re.test(stream.peek())) {
  352. stream.next()
  353. if (is_member(stream.current(), words)) {
  354. return true
  355. }
  356. }
  357. stream.backUp(stream.current().length - 1)
  358. }
  359. return false
  360. }
  361. function greedy(stream, re, words) {
  362. if (stream.current().length == 1 && re.test(stream.current())) {
  363. while (re.test(stream.peek())) {
  364. stream.next()
  365. }
  366. while (0 < stream.current().length) {
  367. if (is_member(stream.current(), words)) {
  368. return true
  369. } else {
  370. stream.backUp(1)
  371. }
  372. }
  373. stream.next()
  374. }
  375. return false
  376. }
  377. function doubleQuote(stream) {
  378. return quote(stream, '"', '\\')
  379. }
  380. function singleQuote(stream) {
  381. return quote(stream, "'", '\\')
  382. }
  383. function quote(stream, quoteChar, escapeChar) {
  384. while (!stream.eol()) {
  385. var ch = stream.next()
  386. if (ch == quoteChar) {
  387. return true
  388. } else if (ch == escapeChar) {
  389. stream.next()
  390. }
  391. }
  392. return false
  393. }
  394. function lookahead(stream) {
  395. var m = stream.match(/^\s*([^\s%])/, false)
  396. return m ? m[1] : ''
  397. }
  398. function is_member(element, list) {
  399. return -1 < list.indexOf(element)
  400. }
  401. function rval(state, stream, type) {
  402. // parse stack
  403. pushToken(state, realToken(type, stream))
  404. // map erlang token type to CodeMirror style class
  405. // erlang -> CodeMirror tag
  406. switch (type) {
  407. case 'atom':
  408. return 'atom'
  409. case 'attribute':
  410. return 'attribute'
  411. case 'boolean':
  412. return 'atom'
  413. case 'builtin':
  414. return 'builtin'
  415. case 'close_paren':
  416. return null
  417. case 'colon':
  418. return null
  419. case 'comment':
  420. return 'comment'
  421. case 'dot':
  422. return null
  423. case 'error':
  424. return 'error'
  425. case 'fun':
  426. return 'meta'
  427. case 'function':
  428. return 'tag'
  429. case 'guard':
  430. return 'property'
  431. case 'keyword':
  432. return 'keyword'
  433. case 'macro':
  434. return 'variable-2'
  435. case 'number':
  436. return 'number'
  437. case 'open_paren':
  438. return null
  439. case 'operator':
  440. return 'operator'
  441. case 'record':
  442. return 'bracket'
  443. case 'separator':
  444. return null
  445. case 'string':
  446. return 'string'
  447. case 'type':
  448. return 'def'
  449. case 'variable':
  450. return 'variable'
  451. default:
  452. return null
  453. }
  454. }
  455. function aToken(tok, col, ind, typ) {
  456. return { token: tok, column: col, indent: ind, type: typ }
  457. }
  458. function realToken(type, stream) {
  459. return aToken(stream.current(), stream.column(), stream.indentation(), type)
  460. }
  461. function fakeToken(type) {
  462. return aToken(type, 0, 0, type)
  463. }
  464. function peekToken(state, depth) {
  465. var len = state.tokenStack.length
  466. var dep = depth ? depth : 1
  467. if (len < dep) {
  468. return false
  469. } else {
  470. return state.tokenStack[len - dep]
  471. }
  472. }
  473. function pushToken(state, token) {
  474. if (!(token.type == 'comment' || token.type == 'whitespace')) {
  475. state.tokenStack = maybe_drop_pre(state.tokenStack, token)
  476. state.tokenStack = maybe_drop_post(state.tokenStack)
  477. }
  478. }
  479. function maybe_drop_pre(s, token) {
  480. var last = s.length - 1
  481. if (0 < last && s[last].type === 'record' && token.type === 'dot') {
  482. s.pop()
  483. } else if (0 < last && s[last].type === 'group') {
  484. s.pop()
  485. s.push(token)
  486. } else {
  487. s.push(token)
  488. }
  489. return s
  490. }
  491. function maybe_drop_post(s) {
  492. if (!s.length) return s
  493. var last = s.length - 1
  494. if (s[last].type === 'dot') {
  495. return []
  496. }
  497. if (last > 1 && s[last].type === 'fun' && s[last - 1].token === 'fun') {
  498. return s.slice(0, last - 1)
  499. }
  500. switch (s[last].token) {
  501. case '}':
  502. return d(s, { g: ['{'] })
  503. case ']':
  504. return d(s, { i: ['['] })
  505. case ')':
  506. return d(s, { i: ['('] })
  507. case '>>':
  508. return d(s, { i: ['<<'] })
  509. case 'end':
  510. return d(s, { i: ['begin', 'case', 'fun', 'if', 'receive', 'try'] })
  511. case ',':
  512. return d(s, { e: ['begin', 'try', 'when', '->', ',', '(', '[', '{', '<<'] })
  513. case '->':
  514. return d(s, { r: ['when'], m: ['try', 'if', 'case', 'receive'] })
  515. case ';':
  516. return d(s, { E: ['case', 'fun', 'if', 'receive', 'try', 'when'] })
  517. case 'catch':
  518. return d(s, { e: ['try'] })
  519. case 'of':
  520. return d(s, { e: ['case'] })
  521. case 'after':
  522. return d(s, { e: ['receive', 'try'] })
  523. default:
  524. return s
  525. }
  526. }
  527. function d(stack, tt) {
  528. // stack is a stack of Token objects.
  529. // tt is an object; {type:tokens}
  530. // type is a char, tokens is a list of token strings.
  531. // The function returns (possibly truncated) stack.
  532. // It will descend the stack, looking for a Token such that Token.token
  533. // is a member of tokens. If it does not find that, it will normally (but
  534. // see "E" below) return stack. If it does find a match, it will remove
  535. // all the Tokens between the top and the matched Token.
  536. // If type is "m", that is all it does.
  537. // If type is "i", it will also remove the matched Token and the top Token.
  538. // If type is "g", like "i", but add a fake "group" token at the top.
  539. // If type is "r", it will remove the matched Token, but not the top Token.
  540. // If type is "e", it will keep the matched Token but not the top Token.
  541. // If type is "E", it behaves as for type "e", except if there is no match,
  542. // in which case it will return an empty stack.
  543. for (var type in tt) {
  544. var len = stack.length - 1
  545. var tokens = tt[type]
  546. for (var i = len - 1; -1 < i; i--) {
  547. if (is_member(stack[i].token, tokens)) {
  548. var ss = stack.slice(0, i)
  549. switch (type) {
  550. case 'm':
  551. return ss.concat(stack[i]).concat(stack[len])
  552. case 'r':
  553. return ss.concat(stack[len])
  554. case 'i':
  555. return ss
  556. case 'g':
  557. return ss.concat(fakeToken('group'))
  558. case 'E':
  559. return ss.concat(stack[i])
  560. case 'e':
  561. return ss.concat(stack[i])
  562. }
  563. }
  564. }
  565. }
  566. return type == 'E' ? [] : stack
  567. }
  568. /////////////////////////////////////////////////////////////////////////////
  569. // indenter
  570. function indenter(state, textAfter) {
  571. var t
  572. var unit = cmCfg.indentUnit
  573. var wordAfter = wordafter(textAfter)
  574. var currT = peekToken(state, 1)
  575. var prevT = peekToken(state, 2)
  576. if (state.in_string || state.in_atom) {
  577. return CodeMirror.Pass
  578. } else if (!prevT) {
  579. return 0
  580. } else if (currT.token == 'when') {
  581. return currT.column + unit
  582. } else if (wordAfter === 'when' && prevT.type === 'function') {
  583. return prevT.indent + unit
  584. } else if (wordAfter === '(' && currT.token === 'fun') {
  585. return currT.column + 3
  586. } else if (wordAfter === 'catch' && (t = getToken(state, ['try']))) {
  587. return t.column
  588. } else if (is_member(wordAfter, ['end', 'after', 'of'])) {
  589. t = getToken(state, ['begin', 'case', 'fun', 'if', 'receive', 'try'])
  590. return t ? t.column : CodeMirror.Pass
  591. } else if (is_member(wordAfter, closeParenWords)) {
  592. t = getToken(state, openParenWords)
  593. return t ? t.column : CodeMirror.Pass
  594. } else if (is_member(currT.token, [',', '|', '||']) || is_member(wordAfter, [',', '|', '||'])) {
  595. t = postcommaToken(state)
  596. return t ? t.column + t.token.length : unit
  597. } else if (currT.token == '->') {
  598. if (is_member(prevT.token, ['receive', 'case', 'if', 'try'])) {
  599. return prevT.column + unit + unit
  600. } else {
  601. return prevT.column + unit
  602. }
  603. } else if (is_member(currT.token, openParenWords)) {
  604. return currT.column + currT.token.length
  605. } else {
  606. t = defaultToken(state)
  607. return truthy(t) ? t.column + unit : 0
  608. }
  609. }
  610. function wordafter(str) {
  611. var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/)
  612. return truthy(m) && m.index === 0 ? m[0] : ''
  613. }
  614. function postcommaToken(state) {
  615. var objs = state.tokenStack.slice(0, -1)
  616. var i = getTokenIndex(objs, 'type', ['open_paren'])
  617. return truthy(objs[i]) ? objs[i] : false
  618. }
  619. function defaultToken(state) {
  620. var objs = state.tokenStack
  621. var stop = getTokenIndex(objs, 'type', ['open_paren', 'separator', 'keyword'])
  622. var oper = getTokenIndex(objs, 'type', ['operator'])
  623. if (truthy(stop) && truthy(oper) && stop < oper) {
  624. return objs[stop + 1]
  625. } else if (truthy(stop)) {
  626. return objs[stop]
  627. } else {
  628. return false
  629. }
  630. }
  631. function getToken(state, tokens) {
  632. var objs = state.tokenStack
  633. var i = getTokenIndex(objs, 'token', tokens)
  634. return truthy(objs[i]) ? objs[i] : false
  635. }
  636. function getTokenIndex(objs, propname, propvals) {
  637. for (var i = objs.length - 1; -1 < i; i--) {
  638. if (is_member(objs[i][propname], propvals)) {
  639. return i
  640. }
  641. }
  642. return false
  643. }
  644. function truthy(x) {
  645. return x !== false && x != null
  646. }
  647. /////////////////////////////////////////////////////////////////////////////
  648. // this object defines the mode
  649. return {
  650. startState: function () {
  651. return { tokenStack: [], in_string: false, in_atom: false }
  652. },
  653. token: function (stream, state) {
  654. return tokenizer(stream, state)
  655. },
  656. indent: function (state, textAfter) {
  657. return indenter(state, textAfter)
  658. },
  659. lineComment: '%',
  660. }
  661. })
  662. })