Lsp base

// TODO: self shutdown when editor crashes
// To support the case that the editor starting a server crashes an editor should also pass 
// its process id to the server. This allows the server to monitor the editor process and to 
// shutdown itself if the editor process dies. The process id pass on the command line should 
// be the same as the one passed in the initialize parameters. The command line argument to use 
// is --clientProcessId.

use "immutable-json"
use "collections"
use "buffered"


type ReceivingMode is (ReceivingModeHeader | ReceivingModeContent)
primitive ReceivingModeHeader
primitive ReceivingModeContent

primitive InvalidJson
  fun string(): String val => "Invalid JSON"
primitive NoContentLength
  fun string(): String val => "No Content-Length header"
primitive InvalidContentLength
  fun string(): String val => "Invalid Content-Length header"
class val InvalidMessage
  let msg: String
  new val create(msg': String) =>
    msg = msg'
  fun string(): String val =>
    "Invalid Message: " + msg
type ParseError is (InvalidJson | NoContentLength | InvalidContentLength | InvalidMessage)

primitive NeedMore

interface Notifier
  be handle_message(msg: Message val)
  be handle_parse_error(parse_error: ParseError)

class ref BaseProtocol
  let buffer: Reader = Reader
  let notifier: Notifier tag

  var headers: Map[String, String] = Map[String, String]
  var receiving_mode: ReceivingMode = ReceivingModeHeader

  new ref create(notifier': Notifier tag) => 
    notifier = notifier'

  fun ref apply(data: Array[U8] iso) =>
    buffer.append(consume data)
    // parse until 
    var res = parse()
    while res isnt NeedMore do
      match res
      | let m: Message val => notifier.handle_message(m)
      | let pe: ParseError => notifier.handle_parse_error(pe)
      end
      // cleanup between messages/errors
      this.headers.clear()
      this.receiving_mode = ReceivingModeHeader
      res = parse()
    end


  fun ref parse(): (Message val | ParseError | NeedMore) =>
    match receiving_mode
    | ReceivingModeHeader => receive_headers()
    | ReceivingModeContent => receive_content()
    end

  fun ref receive_headers(): (Message val | ParseError | NeedMore) =>
    try
      // read as many headers as we can
      var msg = this.buffer.line()?
      while true do
        // End Of Headers
        if msg.size() == 0 then
          receiving_mode = ReceivingModeContent
          return receive_content()
        else
          let hdata = msg.split_by(": ", 2)
          this.headers.insert(hdata(0)?, hdata(1)?)
        end
        msg = this.buffer.line()?
      end
      NeedMore // massaging the return type
    else
      // need more data
      NeedMore
    end

  fun ref receive_content(): (Message val | ParseError | NeedMore) =>
    let cl_str =
      try
        headers("Content-Length")?
      else
        return NoContentLength
      end
    let content_length =
      try
        cl_str.usize()?
      else
        return InvalidContentLength
      end
    let content: Array[U8] val = 
      try
        this.buffer.block(content_length)?
      else
        return NeedMore
      end
    let message_json: JsonObject =
      try
        JsonDoc.>parse(String.from_array(content))?.data as JsonObject
      else
        // syntactically invalid json or not an object
        return InvalidJson
      end
    parse_message(message_json)


  fun ref parse_message(json: JsonObject): (Message val | ParseError) =>
    if json.data.contains("method") then
      let method: String =
        try
          match json.data("method")?
          | let s: String => s
          else
            return InvalidMessage("Invalid method. Not a string.")
          end
        else
          return InvalidMessage("Missing method") // shouldnt happen
        end
      // params are optional, but if present must be object or array
      let params: (JsonObject | JsonArray | None) =
        try
          match json.data("params")?
          | let obj: JsonObject => obj
          | let arr: JsonArray => arr
          else
            return InvalidMessage("Invalid request params")
          end
        else
          None
        end
      if json.data.contains("id") then
        // request
        let id: RequestId =
          try
            match json.data("id")?
            | let id: String => id
            | let id: I64 => id
            else
              return InvalidMessage("Invalid id. Expected string or integer")
            end
          else
            return InvalidMessage("Missing request id")
          end
        RequestMessage(id, method, params)
      else
        // notification
        Notification(method, params)
      end
    else
      // response
      let result: JsonType =
        try
          json.data("result")?
        end
      let id: (RequestId | None) =
        try
          match json.data("id")?
          | let id: String => id
          | let id: I64 => id
          else
            return InvalidMessage("Invalid id. Expected string or integer")
          end
        else
          None
        end
      let response_error: (ResponseError val | None) =
        try
          match json.data("error")?
          | let err: JsonObject =>
            // parse ResponseError object
            match parse_response_error(err)
            | let pe: ParseError =>
              return pe
            | let re: ResponseError => re
            end
          else
            return InvalidMessage("Invalid response error")
          end
        else
          None
        end
      ResponseMessage.create(id, result, response_error)
    end

  fun parse_response_error(json: JsonObject): (ResponseError val | ParseError) =>
      let code: I64 =
        try
          match json.data("code")?
          | let c: I64 => c
          else
            return InvalidMessage("Invalid ResponseError code. Not an integer.")
          end
        else
          return InvalidMessage("Missing ResponseError code")
        end
      let message: String =
        try
          match json.data("message")?
          | let m: String => m
          else
            return InvalidMessage("Invalid ResponseError message. Not a string.")
          end
        else
          return InvalidMessage("Missing ResponseError message")
        end
      let data: JsonType = try json.data("data")? end
      ResponseError(code, message, data)