diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index e84cd98d..00000000 --- a/tools/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tools - -None of those tool is in a usable state right now: -- `gendoc.buzz`: extracts docblock from buzz code and generate markdown documentation from it -- `lsp.buzz`: language server protocol for buzz \ No newline at end of file diff --git a/tools/gendoc.buzz b/tools/gendoc.buzz deleted file mode 100644 index 22891366..00000000 --- a/tools/gendoc.buzz +++ /dev/null @@ -1,208 +0,0 @@ -import "std"; -import "debug"; -import "serialize"; -import "io"; -import "fs"; -import "errors"; -import "buffer"; - -object Declaration { - str declarationType, - str? name = null, - str typeDef, - str? docblock, - - {str: Declaration} subDeclarations, - - static fun init( - str declarationType, - str typeDef, - str? docblock, - {str: Declaration}? subDeclarations - ) > Declaration -> Declaration{ - declarationType = declarationType, - typeDef = typeDef, - docblock = docblock, - subDeclarations = subDeclarations ?? {}, - } - - | Split docblock by @ instructions - static fun handleDocblock(str docblock) > {str: str} { - const pat atPattern = $"@([^@ ]+)(\s+[^\n@]+)?"; - {str: str} out = {}; - - foreach (str line in docblock.split("\\n")) { - if (atPattern.match(line) -> at) { - if (at.len() > 2) { - out[at[1].trim()] = at[2]; - } else { - out[at[1].trim()] = ""; - } - } else { - out["unspecified"] = (out["unspecified"] ?? "") + line; - } - } - - return out; - } - - fun toMarkdown(str? heading, Buffer out) > void !> WriteWhileReadingError { - const pat atParamPattern = $"([^\s]+)\s+(.+)"; - - if (this.docblock == null) { - return; - } - - {str: str} processed = Declaration.handleDocblock(this.docblock ?? ""); - - | Ignore things marked with @private - if (processed["private"] != null) { - return; - } - - out.write("\n{heading ?? "###"} `{this.typeDef} {this.name ?? ""}`\n"); - - out.write(processed["unspecified"] ?? ""); - - foreach (str instruction, str content in processed) { - if (instruction == "param") { - [str]? param = atParamPattern.match(content); - - out.write("\n- "); - if (param != null) { - out.write("**`{param![1]}`:** {param![2]}\n"); - } else { - out.write("{content}\n"); - } - } else if (instruction == "return") { - out.write("\n\n**Returns:** {content}"); - } - } - } -} - -fun extractDeclarations([Boxed] statements) > [Declaration] !> JsonParseError { - [Declaration] declarations = []; - - foreach (Boxed statement in statements) { - {str: Boxed} statementMap = statement.mapValue(); - str nodeType = statement.q(["node"]).stringValue(); - - if ( - nodeType == "Function" - or nodeType == "VarDeclaration" - or nodeType == "FunDeclaration" - or nodeType == "Enum" - or nodeType == "ObjectDeclaration" - ) { - Declaration declaration = Declaration.init( - declarationType: nodeType, - typeDef: statement.q(["type_def"]).stringValue(), - docblock: statement.q(["docblock"]).string(), - ); - - if (nodeType == "VarDeclaration") { - declaration.name = statement.q(["name"]).string(); - } - - if (nodeType == "ObjectDeclaration") { - {str: Boxed} props = statement.q(["members"]).mapValue(); - foreach (str name, Boxed member in props) { - str typeDef = member.q(["type_def"]).stringValue(); - str displayName = "{typeDef} {name}"; - if (typeDef.indexOf("fun") == 0) { - displayName = typeDef; - } - - declaration.subDeclarations[name] = - Declaration.init( - declarationType: "member", - typeDef: displayName, - docblock: member.mapValue()["docblock"]?.string(), - ); - } - } - - declarations.append(declaration); - } - } - - return declarations; -} - -fun getDeclarations(str path) > [Declaration] - !> FileSystemError, - UnexpectedError, - ReadWriteError, - CompileError, - JsonParseError, - WriteWhileReadingError { - File file = File.open(path, mode: FileMode.read); - - str source = ast( - file.readAll(), - scriptName: path.sub(0, len: path.indexOf(".buzz")!) - ); - file.close(); - - Boxed? root = null; - - try { - root = jsonDecode(source); - } catch (JsonParseError error) { - print("Could not decode ast of for {path}"); - - return []; - } - - | Root must be a function - assert(root?.q(["node"]).string() ?? "" == "Function", message: "Expected root node to be a function"); - - return extractDeclarations(root?.q(["body", "statements"]).listValue() ?? []); -} - -fun genMarkdownDoc([Declaration] declarations, Buffer out) > void !> WriteWhileReadingError { - foreach (Declaration decl in declarations) { - decl.toMarkdown("###", out: out); - } -} - -fun main([str] args) > void - !> FileSystemError, - ReadWriteError, - InvalidArgumentError, - UnexpectedError, - CompileError, - JsonParseError, - WriteWhileReadingError -{ - {str: str} markdownDoc = {}; - foreach (str file in list("src/lib")) { - if (file.endsWith(".buzz")) { - print("Generating doc for {file}..."); - [Declaration] declarations = getDeclarations("src/lib/{file}"); - - if (declarations.len() > 0) { - Buffer out = Buffer.init(); - - genMarkdownDoc(declarations, out: out); - markdownDoc[file] = out.toString(); - } - } - } - - File mdFile = File.open("{currentDirectory()}/doc/std.md", mode: FileMode.write); - - mdFile.write("# Buzz std lib\n## Table of contents\n"); - foreach (str lib, str libDoc in markdownDoc) { - const str libName = lib.sub(0, len: lib.indexOf(".buzz")!); - mdFile.write("\n- [{libName}](#{libName.replace(" ", with: "-")})"); - } - - foreach (str lib, str libDoc in markdownDoc) { - const str libName = lib.sub(0, len: lib.indexOf(".buzz")!); - mdFile.write("\n## {libName}\n{libDoc}"); - } - - mdFile.close(); -} \ No newline at end of file diff --git a/tools/lsp.buzz b/tools/lsp.buzz deleted file mode 100755 index 26a1b4af..00000000 --- a/tools/lsp.buzz +++ /dev/null @@ -1,1140 +0,0 @@ -#!/usr/bin/env buzz - -import "std"; -import "io"; -import "serialize"; -import "os" as os; -import "debug" as debug; -import "errors"; -import "buffer"; - -object LspState { - | TODO: Maybe bad to keep this in memory - {str: str} sources, - {str: Boxed} ast, -} - -enum(int) SymbolKind { - File = 1, - Module = 2, - Namespace = 3, - Package = 4, - Class = 5, - Method = 6, - Property = 7, - Field = 8, - Constructor = 9, - Enum = 10, - Interface = 11, - KFunction = 12, - Variable = 13, - Constant = 14, - String = 15, - Number = 16, - Boolean = 17, - Array = 18, - Object = 19, - Key = 20, - Null = 21, - EnumMember = 22, - Struct = 23, - Event = 24, - Operator = 25, - TypeParameter = 26, -} - -enum LSPErrorType { - ReadError, - WriteError, - ParseError, -} - -object LSPError { - LSPErrorType errorType, - str message, - - static fun init(LSPErrorType errorType) > LSPError { - str message = "Error occured"; - if (errorType == LSPErrorType.ReadError) { - message = "Error while reading request"; - } else if (errorType == LSPErrorType.WriteError) { - message = "Error while writing response"; - } else if (errorType == LSPErrorType.ParseError) { - message = "Error while parsing buzz script"; - } - - return LSPError{ - errorType = errorType, - message = message - }; - } -} - -fun readContentLength() > int? !> LSPError { - while (true) { - try { - str? header = stdin.readLine(); - - if (header == null) { - throw LSPError{ - errorType = LSPErrorType.ReadError, - message = "Could not parse request header", - }; - } - - | Consume \r\n - stdin.read(2); - | Strip \r - header = header!.sub(0, len: header!.len() - 1); - const int? colon = header!.indexOf(": "); - - if (colon == null) { - throw LSPError{ - errorType = LSPErrorType.ReadError, - message = "Could not parse request header", - }; - } - - const str name = header!.sub(0, len: colon!); - const str value = header!.sub(colon! + 2); - - if (name == "Content-Length") { - return parseInt(value); - } - } catch { - throw LSPError{ - errorType = LSPErrorType.ReadError, - message = "Could not parse request header", - }; - } - } - - return null; -} - -fun respond(str? strId, int? numId, Boxed result) > void !> LSPError - { - try { - const Boxed response = Boxed.init( - { - , - "jsonrpc": "2.0", - "id": if (strId != null) (strId as? any) else (numId as? any), - "result": result - } - ); - - const str stringResponse = jsonEncode(response); - - stderr.write("Content-Length: {stringResponse.len()}\r\n\r\n{stringResponse}"); - stdout.write("Content-Length: {stringResponse.len()}\r\n\r\n{stringResponse}"); - } catch { - throw LSPError.init(LSPErrorType.WriteError); - } -} - -fun withinNode(Boxed node, str uri, int line, int column) > bool { - const str script = node.q(["location", "script"]).stringValue(); - const int startLine = node.q(["location", "start_line"]).integer() ?? -1; - const int startColumn = node.q(["location", "start_column"]).integer() ?? -1; - const int endLine = node.q(["location", "end_line"]).integer() ?? -1; - const int endColumn = node.q(["location", "end_column"]).integer() ?? -1; - - const bool result = uri.endsWith(script) - and line >= startLine - and line <= endLine - and (line != startLine or (column - 1) >= startColumn) - and (line != endLine or (column - 1) >= endColumn) - and (endLine != startLine or ((column - 1) >= startColumn and (column - 1) <= endColumn)); - - stderr.write("is {uri}:{line + 1}:{column} within {node.q(["node"]).stringValue()} {script}:{startLine + 1}:{startColumn} - {endLine + 1}:{endColumn} -> {result}\n") catch void; - - return result; -} - -| Go down the ast tree to find the smallest node under `line`:`column` -fun findNodeUnder([Boxed] trail, Boxed root, str uri, int line, int column) > void { - if (!withinNode(root, uri: uri, line: line, column: column)) { - return; - } - - trail.append(root); - - const str node = root.q(["node"]).string() ?? "-"; - - if (node == "Function") { - const Boxed body = root.q(["body"]); - - if (withinNode(body, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: body, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Enum") { - const [Boxed] cases = root.q(["cases"]).listValue(); - - foreach (int i, Boxed case in cases) { - if (withinNode(case, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: case, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "VarDeclaration") { - const Boxed value = root.q(["value"]); - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "FunDeclaration") { - const Boxed function = root.q(["function"]); - - if (withinNode(function, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: function, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ObjectDeclaration") { - const {str: Boxed} methods = root.q(["methods"]).mapValue(); - const {str: Boxed} members = root.q(["members"]).mapValue(); - - foreach (str name, Boxed method in methods) { - if (withinNode(method, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: method, uri: uri, line: line, column: column); - - return; - } - } - - foreach (str name, Boxed member in members) { - if (withinNode(member, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: member, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Binary") { - const Boxed left = root.q(["left"]); - const Boxed right = root.q(["right"]); - - if (withinNode(left, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: left, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(right, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: right, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Unary") { - const Boxed left = root.q(["left"]); - - if (withinNode(left, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: left, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Subscript") { - const Boxed subscripted = root.q(["subscripted"]); - const Boxed index = root.q(["index"]); - const Boxed value = root.q(["value"]); - - if (withinNode(subscripted, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: subscripted, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(index, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: index, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Unwrap") { - const Boxed unwrapped = root.q(["unwrapped"]); - - if (withinNode(unwrapped, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: unwrapped, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ForceUnwrap") { - const Boxed unwrapped = root.q(["unwrapped"]); - - if (withinNode(unwrapped, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: unwrapped, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Is") { - const Boxed left = root.q(["left"]); - - if (withinNode(left, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: left, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Expression") { - const Boxed expression = root.q(["expression"]); - - if (withinNode(expression, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: expression, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "NamedVariable") { - const Boxed value = root.q(["value"]); - - if (value.map() != null and withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Number") { - return; - } - - if (node == "String") { - const [Boxed] elements = root.q(["elements"]).listValue(); - - foreach (int i, Boxed element in elements) { - if (withinNode(element, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: element, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "StringLiteral") { - return; - } - - if (node == "Pattern") { - return; - } - - if (node == "Boolean") { - return; - } - - if (node == "Null") { - return; - } - - if (node == "List") { - const [Boxed] items = root.q(["items"]).listValue(); - - foreach (int i, Boxed item in items) { - if (withinNode(item, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: item, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Map") { - const {str: Boxed} map = root.q(["items"]).mapValue(); - - foreach (str key, Boxed value in map) { - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Dot") { - const Boxed callee = root.q(["callee"]); - const Boxed? value = root.mapValue()["value"]; - const Boxed? call = root.mapValue()["call"]; - - if (withinNode(callee, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: callee, uri: uri, line: line, column: column); - - return; - } - - if (value != null and withinNode(value!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value!, uri: uri, line: line, column: column); - - return; - } - - if (call != null and withinNode(call!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: call!, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ObjectInit") { - const Boxed objectRef = root.q(["object"]); - const [Boxed] properties = root.q(["properties"]).listValue(); - - if (withinNode(objectRef, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: objectRef, root: objectRef, uri: uri, line: line, column: column); - - return; - } - - foreach (int i, Boxed property in properties) { - if (withinNode(property, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: property, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Throw") { - const Boxed errorValue = root.q(["error_value"]); - - if (withinNode(errorValue, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: errorValue, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Break") { - return; - } - - if (node == "Continue") { - return; - } - - if (node == "Call") { - const Boxed? callee = root.mapValue()["callee"]; - const [Boxed] arguments = root.q(["arguments"]).listValue(); - - if (callee != null and withinNode(callee!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: callee!, uri: uri, line: line, column: column); - - return; - } - - foreach (int i, Boxed argument in arguments) { - const Boxed argNode = argument.q(["value"]); - if (withinNode(argNode, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: argNode, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "AsyncCall") { - const Boxed call = root.q(["call"]); - - if (withinNode(call, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: call, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Resume") { - const Boxed fiber = root.q(["fiber"]); - - if (withinNode(fiber, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: fiber, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Resolve") { - const Boxed fiber = root.q(["fiber"]); - - if (withinNode(fiber, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: fiber, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Yield") { - const Boxed expression = root.q(["expression"]); - - if (withinNode(expression, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: expression, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "If") { - const Boxed condition = root.q(["condition"]); - const Boxed body = root.q(["body"]); - const Boxed? elseBranch = root.mapValue()["else"]; - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(body, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: body, uri: uri, line: line, column: column); - - return; - } - - if (elseBranch != null and withinNode(elseBranch!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: elseBranch!, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Block") { - const [Boxed] statements = root.q(["statements"]).listValue(); - - foreach (int i, Boxed statement in statements) { - if (withinNode(statement, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: statement, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Return") { - const Boxed value = root.q(["value"]); - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "For") { - const [Boxed] initDeclarations = root.q(["init_declarations"]).listValue(); - const Boxed condition = root.q(["condition"]); - const [Boxed] postLoops = root.q(["postLoop"]).listValue(); - const Boxed body = root.q(["body"]); - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - foreach (int i, Boxed initDeclaration in initDeclarations) { - if (withinNode(initDeclaration, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: initDeclaration, uri: uri, line: line, column: column); - - return; - } - } - - foreach (int i, Boxed postLoop in postLoops) { - if (withinNode(postLoop, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: postLoop, uri: uri, line: line, column: column); - - return; - } - } - - if (withinNode(body, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: body, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ForEach") { - const Boxed? key = root.mapValue()["key"]; - const Boxed value = root.q(["value"]); - const Boxed iterable = root.q(["iterable"]); - const Boxed block = root.q(["block"]); - - if (key != null and withinNode(key!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: key!, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(iterable, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: iterable, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(block, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: block, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "DoUntil") { - const Boxed condition = root.q(["condition"]); - const Boxed block = root.q(["block"]); - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(block, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: block, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "While") { - const Boxed condition = root.q(["condition"]); - const Boxed block = root.q(["block"]); - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(block, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: block, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Export") { - return; - } - - if (node == "Import") { - return; - } -} - -| Search symbol with a list of statements without going into a deeper scope -fun findSymbolInScope([Boxed] statements, str symbol) > Boxed? { - foreach (int i, Boxed statement in statements) { - const str statementType = statement.q(["node"]).stringValue(); - - if (statementType == "VarDeclaration") { - if (symbol == statement.q(["name"]).stringValue()) { - return statement; - } - } else if (statementType == "FunDeclaration") { - if (symbol == statement.q(["function", "name"]).stringValue()) { - return statement; - } - } else if (statementType == "ObjectDeclaration") { - if (statement.q(["type_def"]).stringValue().endsWith(symbol)) { - return statement; - } - } - } - - return null; -} - -| Climp up ast tree from the leaf to find its declaration -fun findSymbol([Boxed] trail, str symbol) > Boxed? { - for (int i = trail.len() - 1; i >= 0; i = i - 1) { - Boxed node = trail[i]; - const str nodeType = node.q(["node"]).stringValue(); - - if (nodeType == "Block") { - Boxed? declaration = findSymbolInScope(node.q(["statements"]).listValue(), symbol: symbol); - - if (declaration != null) { - return declaration; - } - } else if (nodeType == "Function") { - Boxed? declaration = findSymbolInScope(node.q(["body", "statements"]).listValue(), symbol: symbol); - - if (declaration != null) { - return declaration; - } - } - } - - return null; -} - -| Climp up ast tree from the leaf to find its declaration -fun findDeclaration([Boxed] trail) > Boxed? !> LSPError { - assert(trail.len() >= 2, message: "Trail should not be empty"); - - try { - stderr.write("Searching in trail: "); - foreach (int i, Boxed e in trail) { - stderr.write("{e.q(["node"]).stringValue()} > "); - } - stderr.write("\n"); - - Boxed leaf = trail[trail.len() - 1]; - str leafType = leaf.q(["node"]).stringValue(); - - if (leafType == "NamedVariable") { - const str symbol = leaf.q(["identifier"]).stringValue(); - const bool global = leaf.q(["slot_type"]).stringValue().endsWith("Global"); - - if (global) { - | Its a global we can search from the root directly - return findSymbol([trail[0]], symbol: symbol); - } else { - return findSymbol(trail.sub(0, len: trail.len() - 1), symbol: symbol); - } - } else if (leafType == "Function") { - - } else if (leafType == "Dot") { - stderr.write("TODO Dot\n"); - } else { - stderr.write("Can't investigate a {leafType}\n"); - } - } catch { - throw LSPError.init(LSPErrorType.WriteError); - } - - return null; -} - -fun getAst(LspState state, str filename) > Boxed !> LSPError { - if (!filename.startsWith("file://")) { - throw LSPError.init(LSPErrorType.ParseError); - } - - | Dismiss "file://" prefix - filename = filename.sub(7); - - str? source = state.sources[filename]; - if (source == null) { - try { - File file = File.open(filename, mode: FileMode.read); - - source = file.readAll(); - state.sources[filename] = source!; - - file.close(); - } catch { - throw LSPError.init(LSPErrorType.ParseError); - } - } - - if (state.ast[filename] == null) { - try { - stderr.write("Getting ast for {filename}\n"); - - str astString = debug.ast(source!, scriptName: filename); - - state.ast[filename] = jsonDecode(astString); - } catch { - throw LSPError.init(LSPErrorType.ParseError); - } - } - - return state.ast[filename]!; -} - -fun getSymbolsAt(Boxed node, [Boxed] declarations) > void !> CircularReference, NotSerializable { - const str ndType = node.q(["node"]).stringValue(); - - if (ndType == "Function") { - getSymbolsAt(node.q(["body"]), declarations: declarations); - - return; - } - - if (ndType == "Enum") { - declarations.append(Boxed.init( - { - , - "name": node.q(["type_def"]).stringValue().sub(5), - "kind": SymbolKind.Enum.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - return; - } - - if (ndType == "VarDeclaration") { - declarations.append(Boxed.init( - { - , - "name": node.q(["name"]).stringValue(), - "kind": SymbolKind.Variable.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - return; - } - - if (ndType == "FunDeclaration") { - pat namePattern = $"fun (.+)\("; - str name = namePattern.match(node.q(["type_def"]).stringValue())![1]; - - declarations.append(Boxed.init( - { - , - "name": name, - "kind": SymbolKind.KFunction.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - return; - } - - if (ndType == "ObjectDeclaration") { - pat namePattern = $"(object) (.+)"; - [str] match = namePattern.match(node.q(["type_def"]).stringValue())!; - SymbolKind kind = SymbolKind.Object; - str name = match[2]; - - declarations.append(Boxed.init( - { - , - "name": name, - "kind": kind.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - foreach (str methodName, Boxed method in node.q(["methods"]).mapValue()) { - declarations.append(Boxed.init( - { - , - "name": methodName, - "kind": SymbolKind.Method.value, - "range": locationToRange(method.q(["location"])).data, - "selectionRange": locationToRange(method.q(["location"])).data, - } - )); - } - - foreach (str propertyName, Boxed def in node.q(["members"]).mapValue()) { - declarations.append(Boxed.init( - { - , - "name": propertyName, - "kind": SymbolKind.Property.value, - | TODO: add property location - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - } - - return; - } - - if (ndType == "Expression") { - getSymbolsAt(node.q(["expression"]), declarations: declarations); - - return; - } - - if (ndType == "If") { - getSymbolsAt(node.q(["condition"]), declarations: declarations); - getSymbolsAt(node.q(["body"]), declarations: declarations); - getSymbolsAt(node.q(["else"]), declarations: declarations); - - return; - } - - if (ndType == "Block") { - foreach (int i, Boxed statement in node.q(["statements"]).listValue()) { - getSymbolsAt(statement, declarations: declarations); - } - } - - if (ndType == "For") { - getSymbolsAt(node.q(["body"]), declarations: declarations); - - return; - } - - if (ndType == "ForEach") { - getSymbolsAt(node.q(["block"]), declarations: declarations); - - return; - } - - if (ndType == "DoUntil") { - getSymbolsAt(node.q(["block"]), declarations: declarations); - - return; - } - - if (ndType == "While") { - getSymbolsAt(node.q(["block"]), declarations: declarations); - - return; - } -} - -fun getSymbols(LspState state, {str: Boxed} request) > Boxed !> CircularReference, NotSerializable { - const Boxed ast = getAst(state, filename: request["params"]?.q(["textDocument", "uri"]).stringValue() ?? "") catch Boxed{}; - - [Boxed] declarations = []; - - getSymbolsAt(ast, declarations: declarations); - - return Boxed.init(declarations); -} - -fun locationToRange(Boxed location) > Boxed !> CircularReference, NotSerializable { - const int startLine = location.q(["start_line"]).integer() ?? -1; - const int startColumn = location.q(["start_column"]).integer() ?? -1; - const int endLine = location.q(["end_line"]).integer() ?? -1; - const int endColumn = location.q(["end_column"]).integer() ?? -1; - - return Boxed.init( - { - , - "start": { - , - "line": startLine, - "character": startColumn, - }, - "end": { - , - "line": endLine, - "character": endColumn, - } - } - ); -} - -fun gotoDefinition(LspState state, {str: Boxed} request) > Boxed !> LSPError, CircularReference, NotSerializable { - const {str: Boxed} textDocument = request["params"]?.q(["textDocument"]).mapValue() ?? {}; - str uri = textDocument["uri"]?.stringValue() ?? ""; - const {str: Boxed} position = request["params"]?.q(["position"]).mapValue() ?? {}; - const int line = position["line"]?.integerValue() ?? 0; - const int column = position["character"]?.integerValue() ?? 0; - - Boxed ast = getAst(state, filename: uri); - - | Skip entry point - [Boxed] roots = ast.q(["body", "statements"]).list() ?? []; - foreach (int i, Boxed root in roots) { - [Boxed] trail = [ast]; - - findNodeUnder(trail, root: root, uri: uri, line: line, column: column); - - if (trail.len() > 1) { - Boxed? declaration = findDeclaration(trail); - - if (declaration != null) { - return Boxed.init( - { - , - "uri": uri, - "range": locationToRange(declaration!.q(["location"])).data, - } - ); - } - - break; - } - } - - return Boxed{}; -} - -fun stop(LspState state, {str: Boxed} request) > Boxed { - os.exit(0); - - return Boxed{}; -} - -| Possible commands: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#languageFeatures -const {str: Function(LspState state, {str: Boxed} request) > Boxed} handlers = { - "exit": stop, - "shutdown": stop, - "initialize": fun (LspState state, {str: Boxed} request) - -> Boxed.init( - { - , - "capabilities": { - , - "declarationProvider": true, - "definitionProvider": true, - "documentSymbolProvider": true, - }, - "serverInfo": { - , - "name": "buzz-lsp", - "version": "0.0.1", - }, - }, - ) catch Boxed{}, - | There's not really a difference between declaration and definition in buzz - "textDocument/declaration": gotoDefinition, - "textDocument/definition": gotoDefinition, - "textDocument/documentSymbol": getSymbols, -}; - -fun main([str] args) > void !> any { - LspState state = LspState{ - sources = {}, - ast = {}, - }; - - while (true) { - int? contentLength = readContentLength(); - - if (contentLength == null or contentLength! <= 0) { - throw "Request is empty"; - } - - str? requestRaw = stdin.read(contentLength ?? 0) catch null; - - stderr.write("Request is: `{requestRaw!}`\n") catch void; - - if (requestRaw == null) { - throw "Could not read request"; - } - - {str: Boxed} request = (jsonDecode(requestRaw!) catch Boxed{}).mapValue(); - const str? method = request["method"]?.string(); - - stderr.write("Method is: `{method ?? "none"}`\n") catch void; - - Boxed result = Boxed{}; - if (method != null and handlers[method!] != null) { - result = handlers[method!]!(state, request: request); - } - - respond( - strId: request["id"]?.string(), - numId: request["id"]?.integer(), - result: result, - ); - } -} - -test "documentSymbol" { - LspState state = LspState{ - sources = {}, - ast = {}, - }; - - {str: Boxed} request = jsonDecode( - `\{"jsonrpc":"2.0","id":163,"method":"textDocument/definition","params":\{"textDocument":\{"uri":"file:///Users/giann/git/buzz/src/lib/serialize.buzz"},"position":\{"line":300,"character":22}}}` - ).mapValue(); - - print(jsonEncode(handlers["textDocument/definition"]!(state, request: request))); -} \ No newline at end of file