diff --git a/blueprintcompiler/decompiler.py b/blueprintcompiler/decompiler.py index cd663862d9a2d627921fce7718152ff602e90519..ca5a23e6f06c0c547063d3a7f8b9f8e7ecd1ce14 100644 --- a/blueprintcompiler/decompiler.py +++ b/blueprintcompiler/decompiler.py @@ -22,7 +22,7 @@ from enum import Enum import typing as T from dataclasses import dataclass -from .xml_reader import Element, parse +from .xml_reader import Element, parse, parse_string from .gir import * from .utils import Colors @@ -190,6 +190,13 @@ def decompile(data): return ctx.result +def decompile_string(data): + ctx = DecompileCtx() + + xml = parse_string(data) + _decompile_element(ctx, None, xml) + + return ctx.result def canon(string: str) -> str: diff --git a/blueprintcompiler/lsp.py b/blueprintcompiler/lsp.py index bbe4c1d2df360c694e25fb056d5a5c2b95f7cb06..18e5fd6d2f64df119a1cb908124f2d5762ccd185 100644 --- a/blueprintcompiler/lsp.py +++ b/blueprintcompiler/lsp.py @@ -24,7 +24,7 @@ import json, sys, traceback from .completions import complete from .errors import PrintableError, CompileError, MultipleErrors from .lsp_utils import * -from . import tokenizer, parser, utils, xml_reader +from . import tokenizer, parser, utils, xml_reader, decompiler def command(json_method): @@ -46,6 +46,9 @@ class OpenFile: def apply_changes(self, changes): for change in changes: + if isinstance(change, str): + self.text = change + continue start = utils.pos_to_idx(change["range"]["start"]["line"], change["range"]["start"]["character"], self.text) end = utils.pos_to_idx(change["range"]["end"]["line"], change["range"]["end"]["character"], self.text) self.text = self.text[:start] + change["text"] + self.text[end:] @@ -136,6 +139,16 @@ class LanguageServer: self.logfile.write("\n") self.logfile.flush() + def _send_error(self, id, code, message, data=None): + self._send({ + "id": id, + "error": { + "code": code, + "message": message, + "data": data, + }, + }) + def _send_response(self, id, result): self._send({ "id": id, @@ -153,7 +166,7 @@ class LanguageServer: def initialize(self, id, params): from . import main - self.client_capabilities = params.get("capabilities") + self.client_capabilities = params.get("capabilities", {}) self._send_response(id, { "capabilities": { "textDocumentSync": { @@ -224,6 +237,53 @@ class LanguageServer: completions = complete(open_file.ast, open_file.tokens, idx) self._send_response(id, [completion.to_json(True) for completion in completions]) + @command("x-blueprintcompiler/compile") + def compile(self, id, params): + text = params.get("text") + xml = None + diagnostics = [] + + try: + tokens = tokenizer.tokenize(text) + ast, errors, warnings = parser.parse(tokens) + xml = ast.generate() + diagnostics += warnings + if errors is not None: + diagnostics += errors.errors + diagnostics += ast.errors + except MultipleErrors as e: + diagnostics += e.errors + except CompileError as e: + self._send_error(id, ErrorCode.RequestFailed, e.message) + return + except: + self._log(traceback.format_exc()) + self._send_error(id, ErrorCode.RequestFailed, "Invalid input") + return + + info = [] + for diagnostic in diagnostics: + line_num, col_num = utils.idx_to_pos(diagnostic.start + 1, text) + info.append({ "line": line_num, "col": col_num, "message": diagnostic.message }) + + self._send_response(id, { "xml": xml, "info": info }) + + @command("x-blueprintcompiler/decompile") + def decompile(self, id, params): + text = params.get("text") + blp = None + + try: + blp = decompiler.decompile_string(text) + except decompiler.UnsupportedError as e: + self._send_error(id, ErrorCode.RequestFailed, e.message) + return + except: + self._log(traceback.format_exc()) + self._send_error(id, ErrorCode.RequestFailed, "Invalid input") + return + + self._send_response(id, { "blp": blp }) @command("textDocument/semanticTokens/full") def semantic_tokens(self, id, params): @@ -233,7 +293,6 @@ class LanguageServer: "data": open_file.calc_semantic_tokens(), }) - @command("textDocument/codeAction") def code_actions(self, id, params): open_file = self._open_files[params["textDocument"]["uri"]] @@ -264,11 +323,30 @@ class LanguageServer: def _send_file_updates(self, open_file: OpenFile): + self._send_file_diagnostics(open_file) + self._send_file_compiled(open_file) + + def _send_file_diagnostics(self, open_file: OpenFile): self._send_notification("textDocument/publishDiagnostics", { "uri": open_file.uri, "diagnostics": [self._create_diagnostic(open_file.text, open_file.uri, err) for err in open_file.diagnostics], }) + def _send_file_compiled(self, open_file: OpenFile): + if (self.client_capabilities.get("textDocument", {}).get("x-blueprintcompiler/publishCompiled") is None): + return + + xml = None + try: + xml = open_file.ast.generate() + except: + self._log(traceback.format_exc()) + return + self._send_notification("textDocument/x-blueprintcompiler/publishCompiled", { + "uri": open_file.uri, + "xml": xml + }) + def _create_diagnostic(self, text, uri, err): message = err.message diff --git a/blueprintcompiler/lsp_utils.py b/blueprintcompiler/lsp_utils.py index 5664ab602b8665938963c6bdcc9bdec873390de9..9f1fd778c37c5da9ff4350850a17895794eaf199 100644 --- a/blueprintcompiler/lsp_utils.py +++ b/blueprintcompiler/lsp_utils.py @@ -65,6 +65,8 @@ class CompletionItemKind(enum.IntEnum): Operator = 24 TypeParameter = 25 +class ErrorCode(enum.IntEnum): + RequestFailed = -32803 @dataclass class Completion: diff --git a/blueprintcompiler/xml_reader.py b/blueprintcompiler/xml_reader.py index 24ae5ffdc698439d11fc2ed19cb0f8a1727490fb..a3d53d54b101294fb7b17e448d7bc17800c56604 100644 --- a/blueprintcompiler/xml_reader.py +++ b/blueprintcompiler/xml_reader.py @@ -83,3 +83,8 @@ def parse(filename): parser.setContentHandler(handler) parser.parse(filename) return handler.root + +def parse_string(xml): + handler = Handler() + parser = sax.parseString(xml, handler) + return handler.root