
require:
   codemirror as CodeMirror

provide:
   CodeMirror


CodeMirror.defineMode{"earlgrey"} with {config, parserConfig} ->

   re = {
      id = R```
         start
         in "a-zA-Z$_"
         * [in "a-zA-Z$_0-9" or ["-", in "a-zA-Z$_0-9"]]
      ```

      numr = R```
         start
         {+d}, in "rR"
         {+ in"A-Za-z0-9_"}
         ?[".", {+ in "A-Za-z0-9_"}]
      ```
      num = R```
         start
         {+ in "0-9_"}
         ?[".", {+d}]
         ?[in "eE", {? in "+-", + in "0-9_"}]
      ```

      lowprio = R```
         ;; +" "
         "=" or "=>" or "->" or "%" or ":"
         \ or "with" or "where" or "each"
         +" " or end
      ```

      lowprio1 = R```
         "=" or "=>" or "->" or "%" or ":"
         +" " or end
      ```
      lowprio2 = R```
         "with" or "where" or "each"
         +" " or end
      ```

      op = R```
         start
         + in "+\\-*/~^<>=%&|?!@#.:"
      ```
      op2_open = R```
         start
         in "([{"
      ```
      comma = R```
         start, ","
      ```
      op2_close = R```
         start
         in "}])"
      ```
      op3 = R```
         start
         "with" or "where" or "when"
           \ or "and" or "not" or "or"
           \ or "in" or "mod"
           \ or "each" or "as"
           \ or "of" or "is"
         [+ in "+\\-*/~^<>=%&|?!@#.:"] or boundary
      ```

      op4 = R```
         start
         "`", {+ in "A-Za-z0-9_$"}, "`"
      ```
   }

   {
      startState{} = {
         mode = .code
         previous = null
         sol = true
         nest = {{"", 0, 0, false}}
      }

      token{stream, state} =

         if stream.sol{}:
            if stream.match{R"===$"}:
               return "sep"
            else:
               state.sol = true
               index = state.nest.length - 1
               match state.nest[index]:
                  {o, a, _, _} ->
                     state.nest[index] = {o, a, stream.indentation{}, false}
                  else ->
                     pass
         else:
            state.sol = false

         spaces = stream.eatSpace{}

         result = match state.mode:

            .code ->
               match stream.peek{}:
                  d and in {"\"", "'", "`"} ->
                     stream.next{}
                     state.mode = .string
                     state.strtype =
                        match d:
                           "\"" -> "string"
                           "'"  -> "interpolated"
                           "`"  -> "quote"
                     state.finisher = d
                     state.strtype
                  when stream.match{R";;"} ->
                     stream.skipToEnd{}
                     state.previous = .comment
                     "comment"
                  ;; when stream.match{"___"} ->
                  ;;    state.previous = .id
                  ;;    "ellipsis"
                  when stream.match{R"\.[A-Za-z0-9_$\\-]+"} ->
                     state.previous = .id
                     "string"
                  when stream.match{R"@[A-Za-z0-9$\\-]+"} ->
                     state.previous = .id
                     "attribute"
                  when stream.match{R"#[A-Za-z0-9$\\-]+"} ->
                     state.previous = .id
                     "tag"
                  when stream.match{R"pass|else|return|break|continue|match"} ->
                     state.previous = .id
                     "keyword"
                  when stream.match{R"true|false|null|undefined|this"} ->
                     state.previous = .id
                     "special"
                  when stream.match{re.lowprio1} ->
                     state.previous = .lowprio
                     "operator"
                  when stream.match{re.lowprio2} ->
                     state.previous = .lowprio
                     "wordop"
                  when stream.match{re.op}
                       \ or stream.match{re.op4} ->
                     state.previous = .op
                     "operator"
                  when stream.match{re.op3} ->
                     state.previous = .op
                     "wordop"
                  match when stream.match{re.id} ->
                     match when R"lowprio|comma"? state.previous or state.sol ->
                        when stream.match{R" +(with|where|when|and|not|or|in|mod|each|as|of|is)( |$)", false} ->
                           state.previous = .id
                           "variable"
                        when stream.match{R" *:| +[A-Za-z0-9$\[\{\(\"]", false} ->
                           state.previous = .id
                           "keyword"
                        when stream.match{R" +[+\-*/~^<>=%&|?!@#.:']+[A-Za-z0-9$\[\{\(]", false} ->
                           state.previous = .id
                           "keyword"
                        else ->
                           state.previous = .id
                           "variable"
                     else ->
                        state.previous = .id
                        "variable"
                  when stream.match{re.numr} or stream.match{re.num} ->
                     state.previous = .id
                     "number"
                  when stream.match{re.op2_open, false} ->
                     opchar = stream.match{re.op2_open}[0]
                     state.previous = .lowprio
                     state.nest.push with
                        if stream.eol{}:
                           then: {
                              opchar
                              n
                              n + config.indentUnit
                              true
                           } where n = state.nest[state.nest.length - 1][2]
                           else: {
                              opchar
                              n
                              n + 1
                              true
                           } where n = stream.column{}
                     "open"
                  when stream.match{re.comma} ->
                     state.previous = .comma
                     "punctuation"
                  when stream.match{re.op2_close} ->
                     state.previous = .punct
                     if state.nest.length > 1:
                        state.nest.pop{}
                     "close"
                  else ->
                     stream.next{}
                     null

            .string ->
               stream.match{rx} where
                  sf = state.finisher
                  rx = new RegExp{"(\\\\" + sf + "|[^" + sf + "])+"}
               match stream.eat{state.finisher}:
                  undefined? ->
                     state.strtype
                  else ->
                     state.mode = .code
                     state.previous = .id
                     state.strtype

         result

      indent{state, line} =

         {_, outr, inner, first} = state.nest[index] where
            index = state.nest.length - 1

         add =
            if [not first] and [state.previous == .lowprio or state.previous == .op]:
               then: config.indentUnit
               else: 0

         match line:
            R`start, in "]}"`? ->
               outr + add
            else ->
               inner + add

      blankLine{state} =
         state.sol = true
         index = state.nest.length - 1
         match state.nest[index]:
            {o, a, _, _} ->
               state.nest[index] = {o, a, 0, false}
            else ->
               pass
         null

      lineComment = ";;"

      electricInput = R`start, *" ", in "]})", end`
   }



