oz.js 6.5 KB

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