|  | @@ -0,0 +1,128 @@
 | 
	
		
			
				|  |  | +const prefixRE = (words) => {
 | 
	
		
			
				|  |  | +  return new RegExp("^(?:" + words.join("|") + ")", "i");
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +const wordRE = (words) => {
 | 
	
		
			
				|  |  | +  return new RegExp("^(?:" + words.join("|") + ")$", "i");
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const builtins = wordRE([
 | 
	
		
			
				|  |  | +  // useful helpers
 | 
	
		
			
				|  |  | +  "rep",
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // string manipulation
 | 
	
		
			
				|  |  | +  "str/upper",
 | 
	
		
			
				|  |  | +  "str/lower",
 | 
	
		
			
				|  |  | +  "str/capitalize",
 | 
	
		
			
				|  |  | +  "wd", "se",
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // arithmetic
 | 
	
		
			
				|  |  | +  "add", "sub", "mul",
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // tuple stuff
 | 
	
		
			
				|  |  | +  "tuple/len", "tuple/concat", "tuple/flatten", "tuple/join",
 | 
	
		
			
				|  |  | +  "tuple/index", "tuple/rep", "tuple/replace", "tuple/fold",
 | 
	
		
			
				|  |  | +  "tuple/map",
 | 
	
		
			
				|  |  | +]);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const keywords = wordRE([
 | 
	
		
			
				|  |  | +  "puts",
 | 
	
		
			
				|  |  | +  "case",
 | 
	
		
			
				|  |  | +  "let",
 | 
	
		
			
				|  |  | +  "in",
 | 
	
		
			
				|  |  | +  "fix",
 | 
	
		
			
				|  |  | +  "record",
 | 
	
		
			
				|  |  | +]);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const normal = (stream, state) => {
 | 
	
		
			
				|  |  | +  let ch = stream.next();
 | 
	
		
			
				|  |  | +  if (ch == "(") {
 | 
	
		
			
				|  |  | +    if (stream.eat("*")) {
 | 
	
		
			
				|  |  | +      comment(stream, state);
 | 
	
		
			
				|  |  | +      return "comment";
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (ch == "\"" || ch == "'")
 | 
	
		
			
				|  |  | +    return (state.cur = string(ch))(stream, state);
 | 
	
		
			
				|  |  | +  if (ch == "[" && /[\[=]/.test(stream.peek()))
 | 
	
		
			
				|  |  | +    return (state.cur = bracketed(readBracket(stream), "string"))(stream, state);
 | 
	
		
			
				|  |  | +  if (/\d/.test(ch)) {
 | 
	
		
			
				|  |  | +    stream.eatWhile(/[\w.%]/);
 | 
	
		
			
				|  |  | +    return "number";
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  if (/[\w_]/.test(ch)) {
 | 
	
		
			
				|  |  | +    stream.eatWhile(/[\w\\\-_.]/);
 | 
	
		
			
				|  |  | +    return "variable";
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  return null;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const comment = (stream, state) => {
 | 
	
		
			
				|  |  | +  let ch;
 | 
	
		
			
				|  |  | +  while (ch = stream.next()) {
 | 
	
		
			
				|  |  | +    if (ch == "*" && stream.next() == ")") {
 | 
	
		
			
				|  |  | +      state.cur = normal;
 | 
	
		
			
				|  |  | +      return "comment";
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  state.cur = comment;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const bracketed = (level, style) => {
 | 
	
		
			
				|  |  | +  return (stream, state) => {
 | 
	
		
			
				|  |  | +    let curlev = null, ch;
 | 
	
		
			
				|  |  | +    while ((ch = stream.next()) != null) {
 | 
	
		
			
				|  |  | +      if (curlev == null) {
 | 
	
		
			
				|  |  | +        if (ch == "]") curlev = 0;
 | 
	
		
			
				|  |  | +      } else if (ch == "=") {
 | 
	
		
			
				|  |  | +        ++curlev;
 | 
	
		
			
				|  |  | +      } else if (ch == "]" && curlev == level) {
 | 
	
		
			
				|  |  | +        state.cur = normal;
 | 
	
		
			
				|  |  | +        break;
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        curlev = null;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return style;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const string = (quote) => {
 | 
	
		
			
				|  |  | +  return (stream, state) => {
 | 
	
		
			
				|  |  | +    let escaped = false, ch;
 | 
	
		
			
				|  |  | +    while ((ch = stream.next()) != null) {
 | 
	
		
			
				|  |  | +      if (ch == quote && !escaped) break;
 | 
	
		
			
				|  |  | +      escaped = !escaped && ch == "\\";
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if (!escaped)
 | 
	
		
			
				|  |  | +      state.cur = normal;
 | 
	
		
			
				|  |  | +    return "string";
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +export const matzoLang = {
 | 
	
		
			
				|  |  | +  startState: (basecol) => {
 | 
	
		
			
				|  |  | +    return {cur: normal};
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  token: (stream, state) => {
 | 
	
		
			
				|  |  | +    if (stream.eatSpace())
 | 
	
		
			
				|  |  | +      return null;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    let style = state.cur(stream, state);
 | 
	
		
			
				|  |  | +    const word = stream.current();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (style == "variable") {
 | 
	
		
			
				|  |  | +      if (keywords.test(word))
 | 
	
		
			
				|  |  | +        style = "keyword";
 | 
	
		
			
				|  |  | +      else if (builtins.test(word))
 | 
	
		
			
				|  |  | +        style = "builtin";
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return style;
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  languageData: {
 | 
	
		
			
				|  |  | +    commentTokens: {block: {open: "(\*", close: "\*)"}}
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +};
 |