Skip to content

Commit

Permalink
LSP: Handling the 'initialize' method (#519)
Browse files Browse the repository at this point in the history
* LSP: Handling the 'initialize' method

This adds a ton of code to the `json` std module to support json
encoding/decoding of the `initialize` request and response json rpc
messages as part of the LSP lifecycle. This can correctly receive an
`initialize` request and decode it to a data structure, and respond with
an `InitializeResult` object.

This code currently fails when it is sent the next message type (
`textDocument/didOpen`) which is not yet implemented.

* LSP: typecheck and emit diagnostics

Refactor the language server to be a bit more organized. Also, handle
the basic textDocument messages, for which the project is typechecked
and any errors are emitted as Diagnostics.
  • Loading branch information
kengorab authored Dec 17, 2024
1 parent 93454ad commit c629605
Show file tree
Hide file tree
Showing 10 changed files with 1,300 additions and 35 deletions.
54 changes: 39 additions & 15 deletions projects/compiler/src/typechecker.abra
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ enum TokenizeAndParseError {
export type ModuleLoader {
stdRoot: String
parsedModules: Map<String, ParsedModule> = {}
documentStore: Map<String, String> = {}
useDocumentStore: Bool = false
_invalidated: Map<String, Bool> = {}

func resolvePath(self, modulePath: String, relativeTo: String?): String {
if relativeTo |relativeTo| {
Expand All @@ -24,24 +27,45 @@ export type ModuleLoader {

func hasSeenModule(self, modulePathAbs: String): Bool = self.parsedModules.containsKey(modulePathAbs)

func invalidateModule(self, modulePathAbs: String) {
self._invalidated[modulePathAbs] = true
}

func _loadFileContents(self, modulePathAbs: String): String? {
if self.useDocumentStore {
val inMemContents = self.documentStore[modulePathAbs]
if inMemContents return inMemContents
}

match fs.readFile(modulePathAbs) {
Ok(contents) => Some(contents)
Err => None
}
}

func tokenizeAndParse(self, modulePath: String): Result<ParsedModule, TokenizeAndParseError> {
if self.parsedModules[modulePath] |m| return Ok(m)

val parsedModule = match fs.readFile(modulePath) {
Ok(contents) => {
val m = match Lexer.tokenize(contents) {
Ok(tokens) => {
val m = match Parser.parse(tokens) {
Ok(parsedModule) => parsedModule
Err(error) => return Err(TokenizeAndParseError.ParseError(error))
}
m
if self.parsedModules[modulePath] |m| {
val invalidated = self._invalidated[modulePath] ?: false
if !invalidated return Ok(m)
}

val parsedModule = if self._loadFileContents(modulePath) |contents| { //match fs.readFile(modulePath) {
// Ok(contents) => {
match Lexer.tokenize(contents) {
Ok(tokens) => {
val module = match Parser.parse(tokens) {
Ok(parsedModule) => parsedModule
Err(error) => return Err(TokenizeAndParseError.ParseError(error))
}
Err(error) => return Err(TokenizeAndParseError.LexerError(error))

self._invalidated[modulePath] = false

module
}
m
Err(error) => return Err(TokenizeAndParseError.LexerError(error))
}
Err(e) => return Err(TokenizeAndParseError.ReadFileError(modulePath))
} else {
return Err(TokenizeAndParseError.ReadFileError(modulePath))
}
self.parsedModules[modulePath] = parsedModule

Expand Down Expand Up @@ -827,7 +851,7 @@ type TypecheckerError {
}
}

enum TypecheckerErrorKind {
export enum TypecheckerErrorKind {
ReadFileError(path: String)
LexerError(inner: LexerError)
ParseError(inner: ParseError)
Expand Down
10 changes: 5 additions & 5 deletions projects/compiler/test/compiler/json.abra
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@ import JsonParser from "json"
println(JsonParser.parseString("[ 0 ]"))
/// Expect: Result.Ok(value: JsonValue.Array(items: [JsonValue.Array(items: [JsonValue.String(value: "hello")])]))
println(JsonParser.parseString("[[\"hello\"]]"))
/// Expect: Result.Ok(value: JsonValue.Array(items: [JsonValue.Object(obj: JsonObject(map: { foo: JsonValue.Number(value: Either.Left(left: 1)) }))]))
/// Expect: Result.Ok(value: JsonValue.Array(items: [JsonValue.Object(obj: JsonObject(_map: { foo: JsonValue.Number(value: Either.Left(left: 1)) }))]))
println(JsonParser.parseString("[{\"foo\": 1}]"))
})()

// Testing object values
(() => {
/// Expect: Result.Ok(value: JsonValue.Object(obj: JsonObject(map: {})))
/// Expect: Result.Ok(value: JsonValue.Object(obj: JsonObject(_map: {})))
println(JsonParser.parseString("{}"))
/// Expect: Result.Ok(value: JsonValue.Object(obj: JsonObject(map: { foo: JsonValue.Number(value: Either.Left(left: 1)) })))
/// Expect: Result.Ok(value: JsonValue.Object(obj: JsonObject(_map: { foo: JsonValue.Number(value: Either.Left(left: 1)) })))
println(JsonParser.parseString("{\"foo\": 1}"))
/// Expect: Result.Ok(value: JsonValue.Object(obj: JsonObject(map: { bar: JsonValue.Array(items: [JsonValue.String(value: "baz")]), foo: JsonValue.Number(value: Either.Left(left: 1)) })))
/// Expect: Result.Ok(value: JsonValue.Object(obj: JsonObject(_map: { bar: JsonValue.Array(items: [JsonValue.String(value: "baz")]), foo: JsonValue.Number(value: Either.Left(left: 1)) })))
println(JsonParser.parseString("{ \"foo\" : 1 , \"bar\": [\"baz\"]}"))
/// Expect: Result.Ok(value: "JsonValue.Object(obj: JsonObject(map: { fo\no: JsonValue.Number(value: Either.Left(left: 1)) }))")
/// Expect: Result.Ok(value: "JsonValue.Object(obj: JsonObject(_map: { fo\no: JsonValue.Number(value: Either.Left(left: 1)) }))")
println(JsonParser.parseString("{\"fo\\no\": 1}").map(v => v.toString().replaceAll("\n", "\\n")))
})()

Expand Down
Loading

0 comments on commit c629605

Please sign in to comment.