oz.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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('oz', function (conf) {
  15. function wordRegexp(words) {
  16. return new RegExp('^((' + words.join(')|(') + '))\\b')
  17. }
  18. var singleOperators = /[\^@!\|<>#~\.\*\-\+\\/,=]/
  19. var doubleOperators = /(<-)|(:=)|(=<)|(>=)|(<=)|(<:)|(>:)|(=:)|(\\=)|(\\=:)|(!!)|(==)|(::)/
  20. var tripleOperators = /(:::)|(\.\.\.)|(=<:)|(>=:)/
  21. var middle = ['in', 'then', 'else', 'of', 'elseof', 'elsecase', 'elseif', 'catch', 'finally', 'with', 'require', 'prepare', 'import', 'export', 'define', 'do']
  22. var end = ['end']
  23. var atoms = wordRegexp(['true', 'false', 'nil', 'unit'])
  24. var commonKeywords = wordRegexp(['andthen', 'at', 'attr', 'declare', 'feat', 'from', 'lex', 'mod', 'div', 'mode', 'orelse', 'parser', 'prod', 'prop', 'scanner', 'self', 'syn', 'token'])
  25. var openingKeywords = wordRegexp(['local', 'proc', 'fun', 'case', 'class', 'if', 'cond', 'or', 'dis', 'choice', 'not', 'thread', 'try', 'raise', 'lock', 'for', 'suchthat', 'meth', 'functor'])
  26. var middleKeywords = wordRegexp(middle)
  27. var endKeywords = wordRegexp(end)
  28. // Tokenizers
  29. function tokenBase(stream, state) {
  30. if (stream.eatSpace()) {
  31. return null
  32. }
  33. // Brackets
  34. if (stream.match(/[{}]/)) {
  35. return 'bracket'
  36. }
  37. // Special [] keyword
  38. if (stream.match('[]')) {
  39. return 'keyword'
  40. }
  41. // Operators
  42. if (stream.match(tripleOperators) || stream.match(doubleOperators)) {
  43. return 'operator'
  44. }
  45. // Atoms
  46. if (stream.match(atoms)) {
  47. return 'atom'
  48. }
  49. // Opening keywords
  50. var matched = stream.match(openingKeywords)
  51. if (matched) {
  52. if (!state.doInCurrentLine) state.currentIndent++
  53. else state.doInCurrentLine = false
  54. // Special matching for signatures
  55. if (matched[0] == 'proc' || matched[0] == 'fun') state.tokenize = tokenFunProc
  56. else if (matched[0] == 'class') state.tokenize = tokenClass
  57. else if (matched[0] == 'meth') state.tokenize = tokenMeth
  58. return 'keyword'
  59. }
  60. // Middle and other keywords
  61. if (stream.match(middleKeywords) || stream.match(commonKeywords)) {
  62. return 'keyword'
  63. }
  64. // End keywords
  65. if (stream.match(endKeywords)) {
  66. state.currentIndent--
  67. return 'keyword'
  68. }
  69. // Eat the next char for next comparisons
  70. var ch = stream.next()
  71. // Strings
  72. if (ch == '"' || ch == "'") {
  73. state.tokenize = tokenString(ch)
  74. return state.tokenize(stream, state)
  75. }
  76. // Numbers
  77. if (/[~\d]/.test(ch)) {
  78. if (ch == '~') {
  79. if (!/^[0-9]/.test(stream.peek())) return null
  80. else if ((stream.next() == '0' && stream.match(/^[xX][0-9a-fA-F]+/)) || stream.match(/^[0-9]*(\.[0-9]+)?([eE][~+]?[0-9]+)?/)) return 'number'
  81. }
  82. if ((ch == '0' && stream.match(/^[xX][0-9a-fA-F]+/)) || stream.match(/^[0-9]*(\.[0-9]+)?([eE][~+]?[0-9]+)?/)) return 'number'
  83. return null
  84. }
  85. // Comments
  86. if (ch == '%') {
  87. stream.skipToEnd()
  88. return 'comment'
  89. } else if (ch == '/') {
  90. if (stream.eat('*')) {
  91. state.tokenize = tokenComment
  92. return tokenComment(stream, state)
  93. }
  94. }
  95. // Single operators
  96. if (singleOperators.test(ch)) {
  97. return 'operator'
  98. }
  99. // If nothing match, we skip the entire alphanumeric block
  100. stream.eatWhile(/\w/)
  101. return 'variable'
  102. }
  103. function tokenClass(stream, state) {
  104. if (stream.eatSpace()) {
  105. return null
  106. }
  107. stream.match(/([A-Z][A-Za-z0-9_]*)|(`.+`)/)
  108. state.tokenize = tokenBase
  109. return 'variable-3'
  110. }
  111. function tokenMeth(stream, state) {
  112. if (stream.eatSpace()) {
  113. return null
  114. }
  115. stream.match(/([a-zA-Z][A-Za-z0-9_]*)|(`.+`)/)
  116. state.tokenize = tokenBase
  117. return 'def'
  118. }
  119. function tokenFunProc(stream, state) {
  120. if (stream.eatSpace()) {
  121. return null
  122. }
  123. if (!state.hasPassedFirstStage && stream.eat('{')) {
  124. state.hasPassedFirstStage = true
  125. return 'bracket'
  126. } else if (state.hasPassedFirstStage) {
  127. stream.match(/([A-Z][A-Za-z0-9_]*)|(`.+`)|\$/)
  128. state.hasPassedFirstStage = false
  129. state.tokenize = tokenBase
  130. return 'def'
  131. } else {
  132. state.tokenize = tokenBase
  133. return null
  134. }
  135. }
  136. function tokenComment(stream, state) {
  137. var maybeEnd = false,
  138. ch
  139. while ((ch = stream.next())) {
  140. if (ch == '/' && maybeEnd) {
  141. state.tokenize = tokenBase
  142. break
  143. }
  144. maybeEnd = ch == '*'
  145. }
  146. return 'comment'
  147. }
  148. function tokenString(quote) {
  149. return function (stream, state) {
  150. var escaped = false,
  151. next,
  152. end = false
  153. while ((next = stream.next()) != null) {
  154. if (next == quote && !escaped) {
  155. end = true
  156. break
  157. }
  158. escaped = !escaped && next == '\\'
  159. }
  160. if (end || !escaped) state.tokenize = tokenBase
  161. return 'string'
  162. }
  163. }
  164. function buildElectricInputRegEx() {
  165. // Reindentation should occur on [] or on a match of any of
  166. // the block closing keywords, at the end of a line.
  167. var allClosings = middle.concat(end)
  168. return new RegExp('[\\[\\]]|(' + allClosings.join('|') + ')$')
  169. }
  170. return {
  171. startState: function () {
  172. return {
  173. tokenize: tokenBase,
  174. currentIndent: 0,
  175. doInCurrentLine: false,
  176. hasPassedFirstStage: false,
  177. }
  178. },
  179. token: function (stream, state) {
  180. if (stream.sol()) state.doInCurrentLine = 0
  181. return state.tokenize(stream, state)
  182. },
  183. indent: function (state, textAfter) {
  184. var trueText = textAfter.replace(/^\s+|\s+$/g, '')
  185. if (trueText.match(endKeywords) || trueText.match(middleKeywords) || trueText.match(/(\[])/)) return conf.indentUnit * (state.currentIndent - 1)
  186. if (state.currentIndent < 0) return 0
  187. return state.currentIndent * conf.indentUnit
  188. },
  189. fold: 'indent',
  190. electricInput: buildElectricInputRegEx(),
  191. lineComment: '%',
  192. blockCommentStart: '/*',
  193. blockCommentEnd: '*/',
  194. }
  195. })
  196. CodeMirror.defineMIME('text/x-oz', 'oz')
  197. })