jaclang 0.0.6__py3-none-any.whl → 0.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/__init__.py +2 -1
- jaclang/cli/__jac_gen__/__init__.py +0 -0
- jaclang/cli/__jac_gen__/cli.py +175 -0
- jaclang/cli/__jac_gen__/cmds.py +132 -0
- jaclang/cli/cmds.jac +3 -0
- jaclang/cli/impl/__jac_gen__/__init__.py +0 -0
- jaclang/cli/impl/__jac_gen__/cli_impl.py +16 -0
- jaclang/cli/impl/__jac_gen__/cmds_impl.py +26 -0
- jaclang/cli/impl/cmds_impl.jac +17 -3
- jaclang/core/__jac_gen__/__init__.py +0 -0
- jaclang/core/__jac_gen__/primitives.py +567 -0
- jaclang/core/impl/__jac_gen__/__init__.py +0 -0
- jaclang/core/impl/__jac_gen__/arch_impl.py +24 -0
- jaclang/core/impl/__jac_gen__/element_impl.py +26 -0
- jaclang/core/impl/__jac_gen__/exec_ctx_impl.py +12 -0
- jaclang/core/impl/__jac_gen__/memory_impl.py +14 -0
- jaclang/core/impl/element_impl.jac +2 -2
- jaclang/core/primitives.jac +1 -0
- jaclang/jac/absyntree.py +65 -42
- jaclang/jac/constant.py +4 -0
- jaclang/jac/importer.py +18 -60
- jaclang/jac/langserve.py +26 -0
- jaclang/jac/lexer.py +9 -1
- jaclang/jac/parser.py +135 -123
- jaclang/jac/passes/blue/ast_build_pass.py +410 -353
- jaclang/jac/passes/blue/blue_pygen_pass.py +15 -0
- jaclang/jac/passes/blue/decl_def_match_pass.py +33 -21
- jaclang/jac/passes/blue/import_pass.py +1 -1
- jaclang/jac/passes/blue/pyout_pass.py +47 -12
- jaclang/jac/passes/blue/sym_tab_build_pass.py +38 -127
- jaclang/jac/passes/blue/tests/test_ast_build_pass.py +2 -2
- jaclang/jac/passes/blue/tests/test_blue_pygen_pass.py +9 -30
- jaclang/jac/passes/blue/tests/test_decl_def_match_pass.py +13 -13
- jaclang/jac/passes/blue/tests/test_sym_tab_build_pass.py +6 -4
- jaclang/jac/passes/ir_pass.py +1 -1
- jaclang/jac/passes/purple/__jac_gen__/__init__.py +0 -0
- jaclang/jac/passes/purple/__jac_gen__/analyze_pass.py +37 -0
- jaclang/jac/passes/purple/__jac_gen__/purple_pygen_pass.py +305 -0
- jaclang/jac/passes/purple/impl/__jac_gen__/__init__.py +0 -0
- jaclang/jac/passes/purple/impl/__jac_gen__/purple_pygen_pass_impl.py +23 -0
- jaclang/jac/symtable.py +12 -4
- jaclang/jac/tests/fixtures/__jac_gen__/__init__.py +0 -0
- jaclang/jac/tests/fixtures/__jac_gen__/hello_world.py +16 -0
- jaclang/jac/tests/fixtures/fam.jac +7 -8
- jaclang/jac/transform.py +4 -3
- jaclang/jac/transpiler.py +13 -9
- jaclang/utils/fstring_parser.py +2 -2
- jaclang/utils/helpers.py +41 -0
- jaclang/utils/test.py +30 -0
- jaclang/vendor/__init__.py +1 -0
- jaclang/vendor/pygls/__init__.py +25 -0
- jaclang/vendor/pygls/capabilities.py +502 -0
- jaclang/vendor/pygls/client.py +176 -0
- jaclang/vendor/pygls/constants.py +26 -0
- jaclang/vendor/pygls/exceptions.py +220 -0
- jaclang/vendor/pygls/feature_manager.py +241 -0
- jaclang/vendor/pygls/lsp/__init__.py +139 -0
- jaclang/vendor/pygls/lsp/client.py +2224 -0
- jaclang/vendor/pygls/lsprotocol/__init__.py +2 -0
- jaclang/vendor/pygls/lsprotocol/_hooks.py +1233 -0
- jaclang/vendor/pygls/lsprotocol/converters.py +17 -0
- jaclang/vendor/pygls/lsprotocol/types.py +12820 -0
- jaclang/vendor/pygls/lsprotocol/validators.py +47 -0
- jaclang/vendor/pygls/progress.py +79 -0
- jaclang/vendor/pygls/protocol.py +1184 -0
- jaclang/vendor/pygls/server.py +620 -0
- jaclang/vendor/pygls/uris.py +184 -0
- jaclang/vendor/pygls/workspace/__init__.py +81 -0
- jaclang/vendor/pygls/workspace/position.py +204 -0
- jaclang/vendor/pygls/workspace/text_document.py +234 -0
- jaclang/vendor/pygls/workspace/workspace.py +311 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/METADATA +1 -1
- jaclang-0.0.8.dist-info/RECORD +118 -0
- jaclang/core/jaclang.jac +0 -62
- jaclang-0.0.6.dist-info/RECORD +0 -76
- /jaclang/{utils → vendor}/sly/__init__.py +0 -0
- /jaclang/{utils → vendor}/sly/docparse.py +0 -0
- /jaclang/{utils → vendor}/sly/lex.py +0 -0
- /jaclang/{utils → vendor}/sly/yacc.py +0 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/WHEEL +0 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/entry_points.txt +0 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
############################################################################
|
|
2
|
+
# Copyright(c) Open Law Library. All rights reserved. #
|
|
3
|
+
# See ThirdPartyNotices.txt in the project root for additional notices. #
|
|
4
|
+
# #
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License") #
|
|
6
|
+
# you may not use this file except in compliance with the License. #
|
|
7
|
+
# You may obtain a copy of the License at #
|
|
8
|
+
# #
|
|
9
|
+
# http: // www.apache.org/licenses/LICENSE-2.0 #
|
|
10
|
+
# #
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software #
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
|
14
|
+
# See the License for the specific language governing permissions and #
|
|
15
|
+
# limitations under the License. #
|
|
16
|
+
############################################################################
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
from threading import Event
|
|
21
|
+
from typing import Any
|
|
22
|
+
from typing import Callable
|
|
23
|
+
from typing import List
|
|
24
|
+
from typing import Optional
|
|
25
|
+
from typing import Type
|
|
26
|
+
from typing import Union
|
|
27
|
+
|
|
28
|
+
from cattrs import Converter
|
|
29
|
+
|
|
30
|
+
from jaclang.vendor.pygls.exceptions import PyglsError, JsonRpcException
|
|
31
|
+
from jaclang.vendor.pygls.protocol import JsonRPCProtocol, default_converter
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def aio_readline(stop_event, reader, message_handler):
|
|
38
|
+
CONTENT_LENGTH_PATTERN = re.compile(rb"^Content-Length: (\d+)\r\n$")
|
|
39
|
+
|
|
40
|
+
# Initialize message buffer
|
|
41
|
+
message = []
|
|
42
|
+
content_length = 0
|
|
43
|
+
|
|
44
|
+
while not stop_event.is_set():
|
|
45
|
+
# Read a header line
|
|
46
|
+
header = await reader.readline()
|
|
47
|
+
if not header:
|
|
48
|
+
break
|
|
49
|
+
message.append(header)
|
|
50
|
+
|
|
51
|
+
# Extract content length if possible
|
|
52
|
+
if not content_length:
|
|
53
|
+
match = CONTENT_LENGTH_PATTERN.fullmatch(header)
|
|
54
|
+
if match:
|
|
55
|
+
content_length = int(match.group(1))
|
|
56
|
+
logger.debug("Content length: %s", content_length)
|
|
57
|
+
|
|
58
|
+
# Check if all headers have been read (as indicated by an empty line \r\n)
|
|
59
|
+
if content_length and not header.strip():
|
|
60
|
+
# Read body
|
|
61
|
+
body = await reader.readexactly(content_length)
|
|
62
|
+
if not body:
|
|
63
|
+
break
|
|
64
|
+
message.append(body)
|
|
65
|
+
|
|
66
|
+
# Pass message to protocol
|
|
67
|
+
message_handler(b"".join(message))
|
|
68
|
+
|
|
69
|
+
# Reset the buffer
|
|
70
|
+
message = []
|
|
71
|
+
content_length = 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class JsonRPCClient:
|
|
75
|
+
"""Base JSON-RPC client."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
protocol_cls: Type[JsonRPCProtocol] = JsonRPCProtocol,
|
|
80
|
+
converter_factory: Callable[[], Converter] = default_converter,
|
|
81
|
+
):
|
|
82
|
+
# Strictly speaking `JsonRPCProtocol` wants a `LanguageServer`, not a
|
|
83
|
+
# `JsonRPCClient`. However there similar enough for our purposes, which is
|
|
84
|
+
# that this client will mostly be used in testing contexts.
|
|
85
|
+
self.protocol = protocol_cls(self, converter_factory()) # type: ignore
|
|
86
|
+
|
|
87
|
+
self._server: Optional[asyncio.subprocess.Process] = None
|
|
88
|
+
self._stop_event = Event()
|
|
89
|
+
self._async_tasks: List[asyncio.Task] = []
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def stopped(self) -> bool:
|
|
93
|
+
"""Return ``True`` if the client has been stopped."""
|
|
94
|
+
return self._stop_event.is_set()
|
|
95
|
+
|
|
96
|
+
def feature(
|
|
97
|
+
self,
|
|
98
|
+
feature_name: str,
|
|
99
|
+
options: Optional[Any] = None,
|
|
100
|
+
):
|
|
101
|
+
"""Decorator used to register LSP features.
|
|
102
|
+
|
|
103
|
+
Example
|
|
104
|
+
-------
|
|
105
|
+
::
|
|
106
|
+
|
|
107
|
+
import logging
|
|
108
|
+
from jaclang.vendor.pygls.client import JsonRPCClient
|
|
109
|
+
|
|
110
|
+
ls = JsonRPCClient()
|
|
111
|
+
|
|
112
|
+
@ls.feature('window/logMessage')
|
|
113
|
+
def completions(ls, params):
|
|
114
|
+
logging.info("%s", params.message)
|
|
115
|
+
"""
|
|
116
|
+
return self.protocol.fm.feature(feature_name, options)
|
|
117
|
+
|
|
118
|
+
async def start_io(self, cmd: str, *args, **kwargs):
|
|
119
|
+
"""Start the given server and communicate with it over stdio."""
|
|
120
|
+
|
|
121
|
+
logger.debug("Starting server process: %s", " ".join([cmd, *args]))
|
|
122
|
+
server = await asyncio.create_subprocess_exec(
|
|
123
|
+
cmd,
|
|
124
|
+
*args,
|
|
125
|
+
stdout=asyncio.subprocess.PIPE,
|
|
126
|
+
stdin=asyncio.subprocess.PIPE,
|
|
127
|
+
stderr=asyncio.subprocess.PIPE,
|
|
128
|
+
**kwargs,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
self.protocol.connection_made(server.stdin) # type: ignore
|
|
132
|
+
connection = asyncio.create_task(
|
|
133
|
+
aio_readline(self._stop_event, server.stdout, self.protocol.data_received)
|
|
134
|
+
)
|
|
135
|
+
notify_exit = asyncio.create_task(self._server_exit())
|
|
136
|
+
|
|
137
|
+
self._server = server
|
|
138
|
+
self._async_tasks.extend([connection, notify_exit])
|
|
139
|
+
|
|
140
|
+
async def _server_exit(self):
|
|
141
|
+
if self._server is not None:
|
|
142
|
+
await self._server.wait()
|
|
143
|
+
logger.debug(
|
|
144
|
+
"Server process %s exited with return code: %s",
|
|
145
|
+
self._server.pid,
|
|
146
|
+
self._server.returncode,
|
|
147
|
+
)
|
|
148
|
+
await self.server_exit(self._server)
|
|
149
|
+
self._stop_event.set()
|
|
150
|
+
|
|
151
|
+
async def server_exit(self, server: asyncio.subprocess.Process):
|
|
152
|
+
"""Called when the server process exits."""
|
|
153
|
+
|
|
154
|
+
def _report_server_error(
|
|
155
|
+
self, error: Exception, source: Union[PyglsError, JsonRpcException]
|
|
156
|
+
):
|
|
157
|
+
try:
|
|
158
|
+
self.report_server_error(error, source)
|
|
159
|
+
except Exception:
|
|
160
|
+
logger.error("Unable to report error", exc_info=True)
|
|
161
|
+
|
|
162
|
+
def report_server_error(
|
|
163
|
+
self, error: Exception, source: Union[PyglsError, JsonRpcException]
|
|
164
|
+
):
|
|
165
|
+
"""Called when the server does something unexpected e.g. respond with malformed
|
|
166
|
+
JSON."""
|
|
167
|
+
|
|
168
|
+
async def stop(self):
|
|
169
|
+
self._stop_event.set()
|
|
170
|
+
|
|
171
|
+
if self._server is not None and self._server.returncode is None:
|
|
172
|
+
logger.debug("Terminating server process: %s", self._server.pid)
|
|
173
|
+
self._server.terminate()
|
|
174
|
+
|
|
175
|
+
if len(self._async_tasks) > 0:
|
|
176
|
+
await asyncio.gather(*self._async_tasks)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
############################################################################
|
|
2
|
+
# Copyright(c) Open Law Library. All rights reserved. #
|
|
3
|
+
# See ThirdPartyNotices.txt in the project root for additional notices. #
|
|
4
|
+
# #
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License") #
|
|
6
|
+
# you may not use this file except in compliance with the License. #
|
|
7
|
+
# You may obtain a copy of the License at #
|
|
8
|
+
# #
|
|
9
|
+
# http: // www.apache.org/licenses/LICENSE-2.0 #
|
|
10
|
+
# #
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software #
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
|
14
|
+
# See the License for the specific language governing permissions and #
|
|
15
|
+
# limitations under the License. #
|
|
16
|
+
############################################################################
|
|
17
|
+
|
|
18
|
+
# Dynamically assigned attributes
|
|
19
|
+
ATTR_EXECUTE_IN_THREAD = "execute_in_thread"
|
|
20
|
+
ATTR_COMMAND_TYPE = "command"
|
|
21
|
+
ATTR_FEATURE_TYPE = "feature"
|
|
22
|
+
ATTR_REGISTERED_NAME = "reg_name"
|
|
23
|
+
ATTR_REGISTERED_TYPE = "reg_type"
|
|
24
|
+
|
|
25
|
+
# Parameters
|
|
26
|
+
PARAM_LS = "ls"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
############################################################################
|
|
2
|
+
# Original work Copyright 2018 Palantir Technologies, Inc. #
|
|
3
|
+
# Original work licensed under the MIT License. #
|
|
4
|
+
# See ThirdPartyNotices.txt in the project root for license information. #
|
|
5
|
+
# All modifications Copyright (c) Open Law Library. All rights reserved. #
|
|
6
|
+
# #
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License") #
|
|
8
|
+
# you may not use this file except in compliance with the License. #
|
|
9
|
+
# You may obtain a copy of the License at #
|
|
10
|
+
# #
|
|
11
|
+
# http: // www.apache.org/licenses/LICENSE-2.0 #
|
|
12
|
+
# #
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software #
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
|
16
|
+
# See the License for the specific language governing permissions and #
|
|
17
|
+
# limitations under the License. #
|
|
18
|
+
############################################################################
|
|
19
|
+
import traceback
|
|
20
|
+
from typing import Set
|
|
21
|
+
from typing import Type
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class JsonRpcException(Exception):
|
|
25
|
+
"""A class used as a base class for json rpc exceptions."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, message=None, code=None, data=None):
|
|
28
|
+
message = message or getattr(self.__class__, "MESSAGE")
|
|
29
|
+
super().__init__(message)
|
|
30
|
+
self.message = message
|
|
31
|
+
self.code = code or getattr(self.__class__, "CODE")
|
|
32
|
+
self.data = data
|
|
33
|
+
|
|
34
|
+
def __eq__(self, other):
|
|
35
|
+
return (
|
|
36
|
+
isinstance(other, self.__class__)
|
|
37
|
+
and self.code == other.code
|
|
38
|
+
and self.message == other.message
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def __hash__(self):
|
|
42
|
+
return hash((self.code, self.message))
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def from_error(error):
|
|
46
|
+
for exc_class in _EXCEPTIONS:
|
|
47
|
+
if exc_class.supports_code(error.code):
|
|
48
|
+
return exc_class(
|
|
49
|
+
code=error.code, message=error.message, data=error.data
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return JsonRpcException(code=error.code, message=error.message, data=error.data)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def supports_code(cls, code):
|
|
56
|
+
# Defaults to UnknownErrorCode
|
|
57
|
+
return getattr(cls, "CODE", -32001) == code
|
|
58
|
+
|
|
59
|
+
def to_dict(self):
|
|
60
|
+
exception_dict = {
|
|
61
|
+
"code": self.code,
|
|
62
|
+
"message": self.message,
|
|
63
|
+
}
|
|
64
|
+
if self.data is not None:
|
|
65
|
+
exception_dict["data"] = str(self.data)
|
|
66
|
+
return exception_dict
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class JsonRpcInternalError(JsonRpcException):
|
|
70
|
+
CODE = -32603
|
|
71
|
+
MESSAGE = "Internal Error"
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def of(cls, exc_info):
|
|
75
|
+
exc_type, exc_value, exc_tb = exc_info
|
|
76
|
+
return cls(
|
|
77
|
+
message="".join(
|
|
78
|
+
traceback.format_exception_only(exc_type, exc_value)
|
|
79
|
+
).strip(),
|
|
80
|
+
data={"traceback": traceback.format_tb(exc_tb)},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class JsonRpcInvalidParams(JsonRpcException):
|
|
85
|
+
CODE = -32602
|
|
86
|
+
MESSAGE = "Invalid Params"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class JsonRpcInvalidRequest(JsonRpcException):
|
|
90
|
+
CODE = -32600
|
|
91
|
+
MESSAGE = "Invalid Request"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class JsonRpcMethodNotFound(JsonRpcException):
|
|
95
|
+
CODE = -32601
|
|
96
|
+
MESSAGE = "Method Not Found"
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def of(cls, method):
|
|
100
|
+
return cls(message=cls.MESSAGE + ": " + method)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class JsonRpcParseError(JsonRpcException):
|
|
104
|
+
CODE = -32700
|
|
105
|
+
MESSAGE = "Parse Error"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class JsonRpcRequestCancelled(JsonRpcException):
|
|
109
|
+
CODE = -32800
|
|
110
|
+
MESSAGE = "Request Cancelled"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class JsonRpcContentModified(JsonRpcException):
|
|
114
|
+
CODE = -32801
|
|
115
|
+
MESSAGE = "Content Modified"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class JsonRpcServerNotInitialized(JsonRpcException):
|
|
119
|
+
CODE = -32002
|
|
120
|
+
MESSAGE = "ServerNotInitialized"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class JsonRpcUnknownErrorCode(JsonRpcException):
|
|
124
|
+
CODE = -32001
|
|
125
|
+
MESSAGE = "UnknownErrorCode"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class JsonRpcReservedErrorRangeStart(JsonRpcException):
|
|
129
|
+
CODE = -32099
|
|
130
|
+
MESSAGE = "jsonrpcReservedErrorRangeStart"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class JsonRpcReservedErrorRangeEnd(JsonRpcException):
|
|
134
|
+
CODE = -32000
|
|
135
|
+
MESSAGE = "jsonrpcReservedErrorRangeEnd"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class LspReservedErrorRangeStart(JsonRpcException):
|
|
139
|
+
CODE = -32899
|
|
140
|
+
MESSAGE = "lspReservedErrorRangeStart"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class LspReservedErrorRangeEnd(JsonRpcException):
|
|
144
|
+
CODE = -32800
|
|
145
|
+
MESSAGE = "lspReservedErrorRangeEnd"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class JsonRpcServerError(JsonRpcException):
|
|
149
|
+
def __init__(self, message, code, data=None):
|
|
150
|
+
if not _is_server_error_code(code):
|
|
151
|
+
raise ValueError("Error code should be in range -32099 - -32000")
|
|
152
|
+
super().__init__(message=message, code=code, data=data)
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def supports_code(cls, code):
|
|
156
|
+
return _is_server_error_code(code)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _is_server_error_code(code):
|
|
160
|
+
return -32099 <= code <= -32000
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_EXCEPTIONS: Set[Type[JsonRpcException]] = {
|
|
164
|
+
JsonRpcInternalError,
|
|
165
|
+
JsonRpcInvalidParams,
|
|
166
|
+
JsonRpcInvalidRequest,
|
|
167
|
+
JsonRpcMethodNotFound,
|
|
168
|
+
JsonRpcParseError,
|
|
169
|
+
JsonRpcRequestCancelled,
|
|
170
|
+
JsonRpcServerError,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class PyglsError(Exception):
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class CommandAlreadyRegisteredError(PyglsError):
|
|
179
|
+
def __init__(self, command_name):
|
|
180
|
+
self.command_name = command_name
|
|
181
|
+
|
|
182
|
+
def __repr__(self):
|
|
183
|
+
return f'Command "{self.command_name}" is already registered.'
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class FeatureAlreadyRegisteredError(PyglsError):
|
|
187
|
+
def __init__(self, feature_name):
|
|
188
|
+
self.feature_name = feature_name
|
|
189
|
+
|
|
190
|
+
def __repr__(self):
|
|
191
|
+
return f'Feature "{self.feature_name}" is already registered.'
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class FeatureRequestError(PyglsError):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class FeatureNotificationError(PyglsError):
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class MethodTypeNotRegisteredError(PyglsError):
|
|
203
|
+
def __init__(self, name):
|
|
204
|
+
self.name = name
|
|
205
|
+
|
|
206
|
+
def __repr__(self):
|
|
207
|
+
return f'"{self.name}" is not added to `pygls.lsp.LSP_METHODS_MAP`.'
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class ThreadDecoratorError(PyglsError):
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class ValidationError(PyglsError):
|
|
215
|
+
def __init__(self, errors=None):
|
|
216
|
+
self.errors = errors or []
|
|
217
|
+
|
|
218
|
+
def __repr__(self):
|
|
219
|
+
opt_errs = "\n-".join([e for e in self.errors])
|
|
220
|
+
return f"Missing options: {opt_errs}"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
############################################################################
|
|
2
|
+
# Copyright(c) Open Law Library. All rights reserved. #
|
|
3
|
+
# See ThirdPartyNotices.txt in the project root for additional notices. #
|
|
4
|
+
# #
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License") #
|
|
6
|
+
# you may not use this file except in compliance with the License. #
|
|
7
|
+
# You may obtain a copy of the License at #
|
|
8
|
+
# #
|
|
9
|
+
# http: // www.apache.org/licenses/LICENSE-2.0 #
|
|
10
|
+
# #
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software #
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
|
14
|
+
# See the License for the specific language governing permissions and #
|
|
15
|
+
# limitations under the License. #
|
|
16
|
+
############################################################################
|
|
17
|
+
import asyncio
|
|
18
|
+
import functools
|
|
19
|
+
import inspect
|
|
20
|
+
import itertools
|
|
21
|
+
import logging
|
|
22
|
+
from typing import Any, Callable, Dict, Optional, get_type_hints
|
|
23
|
+
|
|
24
|
+
from jaclang.vendor.pygls.constants import (
|
|
25
|
+
ATTR_COMMAND_TYPE,
|
|
26
|
+
ATTR_EXECUTE_IN_THREAD,
|
|
27
|
+
ATTR_FEATURE_TYPE,
|
|
28
|
+
ATTR_REGISTERED_NAME,
|
|
29
|
+
ATTR_REGISTERED_TYPE,
|
|
30
|
+
PARAM_LS,
|
|
31
|
+
)
|
|
32
|
+
from jaclang.vendor.pygls.exceptions import (
|
|
33
|
+
CommandAlreadyRegisteredError,
|
|
34
|
+
FeatureAlreadyRegisteredError,
|
|
35
|
+
ThreadDecoratorError,
|
|
36
|
+
ValidationError,
|
|
37
|
+
)
|
|
38
|
+
from jaclang.vendor.pygls.lsp import get_method_options_type, is_instance
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def assign_help_attrs(f, reg_name, reg_type):
|
|
44
|
+
setattr(f, ATTR_REGISTERED_NAME, reg_name)
|
|
45
|
+
setattr(f, ATTR_REGISTERED_TYPE, reg_type)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def assign_thread_attr(f):
|
|
49
|
+
setattr(f, ATTR_EXECUTE_IN_THREAD, True)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_help_attrs(f):
|
|
53
|
+
return getattr(f, ATTR_REGISTERED_NAME, None), getattr(
|
|
54
|
+
f, ATTR_REGISTERED_TYPE, None
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def has_ls_param_or_annotation(f, annotation):
|
|
59
|
+
"""Returns true if callable has first parameter named `ls` or type of
|
|
60
|
+
annotation"""
|
|
61
|
+
try:
|
|
62
|
+
sig = inspect.signature(f)
|
|
63
|
+
first_p = next(itertools.islice(sig.parameters.values(), 0, 1))
|
|
64
|
+
return first_p.name == PARAM_LS or get_type_hints(f)[first_p.name] == annotation
|
|
65
|
+
except Exception:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def is_thread_function(f):
|
|
70
|
+
return getattr(f, ATTR_EXECUTE_IN_THREAD, False)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def wrap_with_server(f, server):
|
|
74
|
+
"""Returns a new callable/coroutine with server as first argument."""
|
|
75
|
+
if not has_ls_param_or_annotation(f, type(server)):
|
|
76
|
+
return f
|
|
77
|
+
|
|
78
|
+
if asyncio.iscoroutinefunction(f):
|
|
79
|
+
|
|
80
|
+
async def wrapped(*args, **kwargs):
|
|
81
|
+
return await f(server, *args, **kwargs)
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
wrapped = functools.partial(f, server)
|
|
85
|
+
if is_thread_function(f):
|
|
86
|
+
assign_thread_attr(wrapped)
|
|
87
|
+
|
|
88
|
+
return wrapped
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class FeatureManager:
|
|
92
|
+
"""A class for managing server features.
|
|
93
|
+
|
|
94
|
+
Attributes:
|
|
95
|
+
_builtin_features(dict): Predefined set of lsp methods
|
|
96
|
+
_feature_options(dict): Registered feature's options
|
|
97
|
+
_features(dict): Registered features
|
|
98
|
+
_commands(dict): Registered commands
|
|
99
|
+
server(LanguageServer): Reference to the language server
|
|
100
|
+
If passed, server will be passed to registered
|
|
101
|
+
features/commands with first parameter:
|
|
102
|
+
1. ls - parameter naming convention
|
|
103
|
+
2. name: LanguageServer - add typings
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, server=None):
|
|
107
|
+
self._builtin_features = {}
|
|
108
|
+
self._feature_options = {}
|
|
109
|
+
self._features = {}
|
|
110
|
+
self._commands = {}
|
|
111
|
+
self.server = server
|
|
112
|
+
|
|
113
|
+
def add_builtin_feature(self, feature_name: str, func: Callable) -> None:
|
|
114
|
+
"""Registers builtin (predefined) feature."""
|
|
115
|
+
self._builtin_features[feature_name] = func
|
|
116
|
+
logger.info("Registered builtin feature %s", feature_name)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def builtin_features(self) -> Dict:
|
|
120
|
+
"""Returns server builtin features."""
|
|
121
|
+
return self._builtin_features
|
|
122
|
+
|
|
123
|
+
def command(self, command_name: str) -> Callable:
|
|
124
|
+
"""Decorator used to register custom commands.
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
@ls.command('myCustomCommand')
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def decorator(f):
|
|
131
|
+
# Validate
|
|
132
|
+
if command_name is None or command_name.strip() == "":
|
|
133
|
+
logger.error("Missing command name.")
|
|
134
|
+
raise ValidationError("Command name is required.")
|
|
135
|
+
|
|
136
|
+
# Check if not already registered
|
|
137
|
+
if command_name in self._commands:
|
|
138
|
+
logger.error('Command "%s" is already registered.', command_name)
|
|
139
|
+
raise CommandAlreadyRegisteredError(command_name)
|
|
140
|
+
|
|
141
|
+
assign_help_attrs(f, command_name, ATTR_COMMAND_TYPE)
|
|
142
|
+
|
|
143
|
+
wrapped = wrap_with_server(f, self.server)
|
|
144
|
+
# Assign help attributes for thread decorator
|
|
145
|
+
assign_help_attrs(wrapped, command_name, ATTR_COMMAND_TYPE)
|
|
146
|
+
|
|
147
|
+
self._commands[command_name] = wrapped
|
|
148
|
+
|
|
149
|
+
logger.info('Command "%s" is successfully registered.', command_name)
|
|
150
|
+
|
|
151
|
+
return f
|
|
152
|
+
|
|
153
|
+
return decorator
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def commands(self) -> Dict:
|
|
157
|
+
"""Returns registered custom commands."""
|
|
158
|
+
return self._commands
|
|
159
|
+
|
|
160
|
+
def feature(
|
|
161
|
+
self,
|
|
162
|
+
feature_name: str,
|
|
163
|
+
options: Optional[Any] = None,
|
|
164
|
+
) -> Callable:
|
|
165
|
+
"""Decorator used to register LSP features.
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
@ls.feature('textDocument/completion', CompletionItems(trigger_characters=['.']))
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def decorator(f):
|
|
172
|
+
# Validate
|
|
173
|
+
if feature_name is None or feature_name.strip() == "":
|
|
174
|
+
logger.error("Missing feature name.")
|
|
175
|
+
raise ValidationError("Feature name is required.")
|
|
176
|
+
|
|
177
|
+
# Add feature if not exists
|
|
178
|
+
if feature_name in self._features:
|
|
179
|
+
logger.error('Feature "%s" is already registered.', feature_name)
|
|
180
|
+
raise FeatureAlreadyRegisteredError(feature_name)
|
|
181
|
+
|
|
182
|
+
assign_help_attrs(f, feature_name, ATTR_FEATURE_TYPE)
|
|
183
|
+
|
|
184
|
+
wrapped = wrap_with_server(f, self.server)
|
|
185
|
+
# Assign help attributes for thread decorator
|
|
186
|
+
assign_help_attrs(wrapped, feature_name, ATTR_FEATURE_TYPE)
|
|
187
|
+
|
|
188
|
+
self._features[feature_name] = wrapped
|
|
189
|
+
|
|
190
|
+
if options:
|
|
191
|
+
options_type = get_method_options_type(feature_name)
|
|
192
|
+
if options_type and not is_instance(options, options_type):
|
|
193
|
+
raise TypeError(
|
|
194
|
+
(
|
|
195
|
+
f'Options of method "{feature_name}"'
|
|
196
|
+
f" should be instance of type {options_type}"
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
self._feature_options[feature_name] = options
|
|
200
|
+
|
|
201
|
+
logger.info('Registered "%s" with options "%s"', feature_name, options)
|
|
202
|
+
|
|
203
|
+
return f
|
|
204
|
+
|
|
205
|
+
return decorator
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def feature_options(self) -> Dict:
|
|
209
|
+
"""Returns feature options for registered features."""
|
|
210
|
+
return self._feature_options
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def features(self) -> Dict:
|
|
214
|
+
"""Returns registered features"""
|
|
215
|
+
return self._features
|
|
216
|
+
|
|
217
|
+
def thread(self) -> Callable:
|
|
218
|
+
"""Decorator that mark function to execute it in a thread."""
|
|
219
|
+
|
|
220
|
+
def decorator(f):
|
|
221
|
+
if asyncio.iscoroutinefunction(f):
|
|
222
|
+
raise ThreadDecoratorError(
|
|
223
|
+
f'Thread decorator cannot be used with async functions "{f.__name__}"'
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Allow any decorator order
|
|
227
|
+
try:
|
|
228
|
+
reg_name = getattr(f, ATTR_REGISTERED_NAME)
|
|
229
|
+
reg_type = getattr(f, ATTR_REGISTERED_TYPE)
|
|
230
|
+
|
|
231
|
+
if reg_type is ATTR_FEATURE_TYPE:
|
|
232
|
+
assign_thread_attr(self.features[reg_name])
|
|
233
|
+
elif reg_type is ATTR_COMMAND_TYPE:
|
|
234
|
+
assign_thread_attr(self.commands[reg_name])
|
|
235
|
+
|
|
236
|
+
except AttributeError:
|
|
237
|
+
assign_thread_attr(f)
|
|
238
|
+
|
|
239
|
+
return f
|
|
240
|
+
|
|
241
|
+
return decorator
|