ZedScripts 0.0.2__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.
- ZedScripts/__about__.py +1 -0
- ZedScripts/__init__.py +38 -0
- ZedScripts/enums/Diagnostic.py +165 -0
- ZedScripts/enums/SyntaxErrorType.py +7 -0
- ZedScripts/environment/__init__.py +33 -0
- ZedScripts/environment/config.py +82 -0
- ZedScripts/environment/document.py +374 -0
- ZedScripts/environment/mod.py +62 -0
- ZedScripts/environment/version.py +202 -0
- ZedScripts/environment/workspace.py +320 -0
- ZedScripts/locale/en.json +38 -0
- ZedScripts/main.py +7 -0
- ZedScripts/providers/actions.py +13 -0
- ZedScripts/providers/capabilities.py +107 -0
- ZedScripts/providers/configuration.py +26 -0
- ZedScripts/providers/debug.py +59 -0
- ZedScripts/providers/diagnostics.py +101 -0
- ZedScripts/providers/hover.py +37 -0
- ZedScripts/providers/http.py +49 -0
- ZedScripts/providers/locale.py +80 -0
- ZedScripts/providers/notifications.py +45 -0
- ZedScripts/providers/semantic_tokens.py +189 -0
- ZedScripts/scripts/__init__.py +160 -0
- ZedScripts/scripts/block.py +547 -0
- ZedScripts/scripts/dataset.py +355 -0
- ZedScripts/scripts/value.py +706 -0
- ZedScripts/scripts/version.py +75 -0
- ZedScripts/server.py +288 -0
- ZedScripts/structure/ast.py +143 -0
- ZedScripts/structure/lexer.py +482 -0
- ZedScripts/structure/parser.py +243 -0
- ZedScripts/utils.py +85 -0
- zedscripts-0.0.2.dist-info/METADATA +105 -0
- zedscripts-0.0.2.dist-info/RECORD +37 -0
- zedscripts-0.0.2.dist-info/WHEEL +5 -0
- zedscripts-0.0.2.dist-info/licenses/LICENSE +21 -0
- zedscripts-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Self
|
|
2
|
+
if TYPE_CHECKING:
|
|
3
|
+
from ..environment.config import ReleaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GameVersion:
|
|
7
|
+
_cache: dict[str, 'GameVersion'] = {}
|
|
8
|
+
def __init__(self, version: str):
|
|
9
|
+
self.version = version
|
|
10
|
+
split_version = version.split(".")
|
|
11
|
+
assert len(split_version) >= 3, "Version must have major, minor, and patch components"
|
|
12
|
+
self.major = int(split_version[0])
|
|
13
|
+
self.minor = int(split_version[1])
|
|
14
|
+
self.patch = int(split_version[2])
|
|
15
|
+
|
|
16
|
+
# cache
|
|
17
|
+
GameVersion._cache[version] = self
|
|
18
|
+
|
|
19
|
+
def __hash__(self):
|
|
20
|
+
return hash((self.major, self.minor, self.patch))
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
return self.version
|
|
24
|
+
|
|
25
|
+
def to_data_version(self, build: int) -> "DataVersion":
|
|
26
|
+
return DataVersion(f"{self.major}.{self.minor}.{self.patch}.{build}")
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def find_or_make(version: str) -> "GameVersion":
|
|
30
|
+
return GameVersion._cache.get(version, GameVersion(version))
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def from_values(major: int, minor: int, patch: int) -> "GameVersion":
|
|
34
|
+
return GameVersion.find_or_make(f"{major}.{minor}.{patch}")
|
|
35
|
+
|
|
36
|
+
def __ge__(self, other):
|
|
37
|
+
if not isinstance(other, GameVersion): return NotImplemented
|
|
38
|
+
return (self.major, self.minor, self.patch) >= (other.major, other.minor, other.patch)
|
|
39
|
+
|
|
40
|
+
def __gt__(self, other):
|
|
41
|
+
if not isinstance(other, GameVersion): return NotImplemented
|
|
42
|
+
return (self.major, self.minor, self.patch) > (other.major, other.minor, other.patch)
|
|
43
|
+
|
|
44
|
+
def __lt__(self, other):
|
|
45
|
+
if not isinstance(other, GameVersion): return NotImplemented
|
|
46
|
+
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
|
47
|
+
|
|
48
|
+
def __le__(self, other):
|
|
49
|
+
if not isinstance(other, GameVersion): return NotImplemented
|
|
50
|
+
return (self.major, self.minor, self.patch) <= (other.major, other.minor, other.patch)
|
|
51
|
+
|
|
52
|
+
def __eq__(self, other):
|
|
53
|
+
if not isinstance(other, GameVersion): return NotImplemented
|
|
54
|
+
return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
|
|
55
|
+
|
|
56
|
+
class DataVersion(GameVersion):
|
|
57
|
+
_data_cache: dict[str, 'DataVersion'] = {}
|
|
58
|
+
def __init__(self, version: str):
|
|
59
|
+
super().__init__(version)
|
|
60
|
+
split_version = version.split(".")
|
|
61
|
+
assert len(split_version) >= 4, "Data version must have major, minor, patch, and build components"
|
|
62
|
+
self.build = int(split_version[3])
|
|
63
|
+
|
|
64
|
+
# cache
|
|
65
|
+
DataVersion._data_cache[version] = self
|
|
66
|
+
|
|
67
|
+
def __hash__(self):
|
|
68
|
+
return hash((self.major, self.minor, self.patch, self.build))
|
|
69
|
+
|
|
70
|
+
def to_game_version(self) -> GameVersion:
|
|
71
|
+
return GameVersion.find_or_make(f"{self.major}.{self.minor}.{self.patch}")
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def find_or_make(version: str) -> "DataVersion":
|
|
75
|
+
return DataVersion._data_cache.get(version, DataVersion(version))
|
ZedScripts/server.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from lsprotocol import types
|
|
6
|
+
from pygls.lsp.server import LanguageServer
|
|
7
|
+
from pygls.protocol.language_server import LanguageServerProtocol
|
|
8
|
+
|
|
9
|
+
from .__about__ import __version__
|
|
10
|
+
from .utils import uri_to_path
|
|
11
|
+
from .environment import WorkspaceType
|
|
12
|
+
from .environment.workspace import Workspace
|
|
13
|
+
from .environment.document import Document
|
|
14
|
+
from .providers.diagnostics import DiagnosticReport
|
|
15
|
+
from .providers.semantic_tokens import get_tokens
|
|
16
|
+
from .providers.locale import zedlocalizer
|
|
17
|
+
from .providers import capabilities
|
|
18
|
+
from .providers.notifications import ZedNotification, NotificationParams
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# for future implementation of custom protocol features
|
|
23
|
+
class ZedLanguageProtocole(LanguageServerProtocol): ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ZedServer(LanguageServer):
|
|
28
|
+
instance: 'ZedServer | None' = None
|
|
29
|
+
def __init__(self):
|
|
30
|
+
super().__init__(name="zedserver", version=__version__, protocol_cls=ZedLanguageProtocole)
|
|
31
|
+
self.documents: dict[Path, Document] = {}
|
|
32
|
+
zedlocalizer.load_locale_files()
|
|
33
|
+
ZedServer.instance = self
|
|
34
|
+
|
|
35
|
+
def send_notification(self, method: ZedNotification, params: NotificationParams | None = None) -> None:
|
|
36
|
+
logging.debug("Sending notification: %s with params: %s", method, params)
|
|
37
|
+
self.protocol.notify(
|
|
38
|
+
method,
|
|
39
|
+
params
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# def send_request(self, method: str, )
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def wait_for_debug_client(self) -> None:
|
|
46
|
+
import time
|
|
47
|
+
from .providers.debug import wait_for_port_available, force_close_port
|
|
48
|
+
try:
|
|
49
|
+
import debugpy
|
|
50
|
+
except ImportError:
|
|
51
|
+
logging.error("debugpy is not installed. Debugging will not be available.")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
debug_port = 5678
|
|
55
|
+
debug_host = "localhost"
|
|
56
|
+
|
|
57
|
+
# ensure the debug port is available
|
|
58
|
+
if not wait_for_port_available(debug_port, debug_host):
|
|
59
|
+
logging.error(f"Debug port {debug_port} is still in use after waiting. Attempting to bind anyway...")
|
|
60
|
+
|
|
61
|
+
# probably a bad idea to do that
|
|
62
|
+
# but realistically, we should only be the ones using that port
|
|
63
|
+
force_close_port(debug_port)
|
|
64
|
+
time.sleep(0.2)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
debugpy.listen((debug_host, debug_port))
|
|
68
|
+
logging.debug(f"Waiting for debug client to attach on {debug_host}:{debug_port}...")
|
|
69
|
+
debugpy.wait_for_client() # blocks until the "Python: Attach" launch config connects
|
|
70
|
+
logging.debug("Debug client attached.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def startup(self) -> None:
|
|
74
|
+
logging.info(f"ZedServer {__version__} starting up.")
|
|
75
|
+
|
|
76
|
+
logging.debug("ZEDSCRIPTS_DEBUG_WAIT=%s", os.environ.get("ZEDSCRIPTS_DEBUG_WAIT"))
|
|
77
|
+
if os.environ.get("ZEDSCRIPTS_DEBUG_WAIT") == "1":
|
|
78
|
+
self.wait_for_debug_client()
|
|
79
|
+
|
|
80
|
+
self.start_io()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def on_document_changed(self, path: Path, text: str) -> None:
|
|
85
|
+
# skip if it's a configuration file
|
|
86
|
+
if Workspace.update_configuration_file(path, text):
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# first check if the file is a zedscripts file
|
|
90
|
+
document = Workspace.find_or_make(path)
|
|
91
|
+
if document is None:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
logging.debug("Document changed: %s\n%s", path, text)
|
|
95
|
+
|
|
96
|
+
# update it
|
|
97
|
+
document.on_document_changed(text)
|
|
98
|
+
|
|
99
|
+
def on_document_deleted(self, path: Path) -> None:
|
|
100
|
+
Document.delete(path)
|
|
101
|
+
|
|
102
|
+
def on_document_renamed(self, old_file: Path, new_file: Path) -> None:
|
|
103
|
+
Document.rename(old_file, new_file)
|
|
104
|
+
|
|
105
|
+
def on_did_change_watched_files(self, params: types.DidChangeWatchedFilesParams) -> None:
|
|
106
|
+
# we are only interested in handling the global configuration file changes
|
|
107
|
+
for change in params.changes:
|
|
108
|
+
path = uri_to_path(change.uri)
|
|
109
|
+
if not Workspace.is_global_configuration_file(path):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
logging.debug(f"Global configuration file changed. ({change.type})")
|
|
113
|
+
|
|
114
|
+
# update the global configuration
|
|
115
|
+
Workspace.update_global_configuration()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def on_document_diagnostics(self, path: Path, previous_result_id: str | None) -> DiagnosticReport | None:
|
|
119
|
+
document = Workspace.find_or_make(path)
|
|
120
|
+
if document is None:
|
|
121
|
+
return None
|
|
122
|
+
return document.on_document_diagnostics(previous_result_id)
|
|
123
|
+
|
|
124
|
+
def on_semantic_tokens(self, path: Path) -> types.SemanticTokens | None:
|
|
125
|
+
document = Document.find(path)
|
|
126
|
+
if document is None:
|
|
127
|
+
return None
|
|
128
|
+
return document.on_semantic_tokens()
|
|
129
|
+
|
|
130
|
+
def on_document_hover(self, path: Path, position: types.Position) -> types.Hover | None:
|
|
131
|
+
document = Document.find(path)
|
|
132
|
+
if document is None:
|
|
133
|
+
return None
|
|
134
|
+
return document.on_hover(position)
|
|
135
|
+
|
|
136
|
+
def on_linked_editing_range(self, path: Path, position: types.Position) -> types.LinkedEditingRanges | None:
|
|
137
|
+
document = Document.find(path)
|
|
138
|
+
if document is None:
|
|
139
|
+
return None
|
|
140
|
+
return document.on_linked_editing_range(position)
|
|
141
|
+
|
|
142
|
+
def on_definition(self, path: Path, position: types.Position) -> types.DefinitionResult:
|
|
143
|
+
document = Document.find(path)
|
|
144
|
+
if document is None:
|
|
145
|
+
return None
|
|
146
|
+
return document.on_definition(position)
|
|
147
|
+
|
|
148
|
+
zedserver = ZedServer()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
## EVENT HANDLERS
|
|
153
|
+
|
|
154
|
+
@zedserver.feature(types.INITIALIZE)
|
|
155
|
+
def initialize(server: ZedServer, params: types.InitializeParams):
|
|
156
|
+
"""
|
|
157
|
+
In here we handle the initialization of the server. For that we provide
|
|
158
|
+
a set of rules and capabilities that the server supports.
|
|
159
|
+
|
|
160
|
+
### Diagnostics
|
|
161
|
+
Our diagnostics depend on other files so we prefer to diagnostic
|
|
162
|
+
the whole workspace. They could take a while for large workspaces
|
|
163
|
+
(i.e. the game files) so we want to provide feedback when it takes
|
|
164
|
+
a while.
|
|
165
|
+
"""
|
|
166
|
+
# this will store what the server can currently do
|
|
167
|
+
capabilities.register_client_capabilities(params)
|
|
168
|
+
|
|
169
|
+
# from the above, we determine what the server capabilities should be
|
|
170
|
+
return types.InitializeResult(
|
|
171
|
+
capabilities=capabilities.get_server_capabilities(),
|
|
172
|
+
server_info=types.ServerInfo(
|
|
173
|
+
name="ZedScripts Language Server",
|
|
174
|
+
version=__version__,
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@zedserver.feature(types.INITIALIZED)
|
|
180
|
+
def initialized(server: ZedServer, params: types.InitializedParams) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Called after the client sends the 'initialized' notification.
|
|
183
|
+
At this point, the client is ready to receive notifications.
|
|
184
|
+
This is the proper place to load workspaces and send progress notifications.
|
|
185
|
+
"""
|
|
186
|
+
# load the global dataset
|
|
187
|
+
Workspace.global_dataset.load()
|
|
188
|
+
|
|
189
|
+
logging.debug("Server workspace folders")
|
|
190
|
+
logging.debug(server.workspace.folders)
|
|
191
|
+
for workspace_folder in server.workspace.folders.values():
|
|
192
|
+
ws = Workspace(uri_to_path(workspace_folder.uri), WorkspaceType.PROJECT)
|
|
193
|
+
ws.load()
|
|
194
|
+
Workspace.load_libraries()
|
|
195
|
+
server.send_notification(
|
|
196
|
+
ZedNotification.LOADING_DOCUMENTS_DONE
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@zedserver.feature(types.TEXT_DOCUMENT_DID_OPEN)
|
|
201
|
+
def did_open(server: ZedServer, params: types.DidOpenTextDocumentParams) -> None:
|
|
202
|
+
server.on_document_changed(uri_to_path(params.text_document.uri), params.text_document.text)
|
|
203
|
+
|
|
204
|
+
@zedserver.feature(types.TEXT_DOCUMENT_DID_CHANGE)
|
|
205
|
+
def did_change(server: ZedServer, params: types.DidChangeTextDocumentParams) -> None:
|
|
206
|
+
document = server.workspace.get_text_document(params.text_document.uri)
|
|
207
|
+
server.on_document_changed(uri_to_path(document.uri), str.join("", document.lines))
|
|
208
|
+
|
|
209
|
+
@zedserver.feature(types.WORKSPACE_DID_DELETE_FILES)
|
|
210
|
+
def workspace_did_delete_files(server: ZedServer, params: types.DeleteFilesParams) -> None:
|
|
211
|
+
for file in params.files:
|
|
212
|
+
path = uri_to_path(file.uri)
|
|
213
|
+
server.on_document_deleted(path)
|
|
214
|
+
|
|
215
|
+
@zedserver.feature(types.WORKSPACE_DID_RENAME_FILES)
|
|
216
|
+
def workspace_did_rename_files(server: ZedServer, params: types.RenameFilesParams) -> None:
|
|
217
|
+
for file in params.files:
|
|
218
|
+
old_path = uri_to_path(file.old_uri)
|
|
219
|
+
new_path = uri_to_path(file.new_uri)
|
|
220
|
+
server.on_document_renamed(old_path, new_path)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@zedserver.feature(types.TEXT_DOCUMENT_DIAGNOSTIC)
|
|
225
|
+
def diagnostic(server: ZedServer, params: types.DocumentDiagnosticParams) -> DiagnosticReport | None:
|
|
226
|
+
path = uri_to_path(params.text_document.uri)
|
|
227
|
+
return server.on_document_diagnostics(path, params.previous_result_id)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# sadly I'm not sure that implementation works as expected because the client constantly
|
|
232
|
+
# asks again and again for workspace diagnostics
|
|
233
|
+
# it also doesn't ask for diagnostics of non-opened documents
|
|
234
|
+
#
|
|
235
|
+
# # also see when implementing the configuration file:
|
|
236
|
+
# # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#diagnostic_refresh
|
|
237
|
+
# @zedserver.feature(types.WORKSPACE_DIAGNOSTIC)
|
|
238
|
+
# def workspace_diagnostic(server: ZedServer, params: types.WorkspaceDiagnosticParams):
|
|
239
|
+
# """
|
|
240
|
+
# Provides diagnostics for all documents in the workspace.
|
|
241
|
+
# This is called when the client requests workspace-wide diagnostics.
|
|
242
|
+
# """
|
|
243
|
+
# logging.info("Workspace diagnostic requested.")
|
|
244
|
+
|
|
245
|
+
# # map of previous result IDs by document URI
|
|
246
|
+
# previous_result_ids = {item.uri: item.value for item in params.previous_result_ids}
|
|
247
|
+
# logging.debug(pformat(previous_result_ids))
|
|
248
|
+
|
|
249
|
+
# items: list[types.WorkspaceDocumentDiagnosticReport] = []
|
|
250
|
+
# for document in Document.get_documents():
|
|
251
|
+
# uri = document.get_uri()
|
|
252
|
+
# previous_result_id = previous_result_ids.get(uri)
|
|
253
|
+
# items.append(document.get_lsp_workspace_diagnostics(previous_result_id))
|
|
254
|
+
|
|
255
|
+
# return types.WorkspaceDiagnosticReport(items=items)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
token_types, token_modifiers = get_tokens()
|
|
259
|
+
@zedserver.feature(
|
|
260
|
+
types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
|
|
261
|
+
types.SemanticTokensLegend(
|
|
262
|
+
token_types=token_types,
|
|
263
|
+
token_modifiers=token_modifiers,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
def semantic_tokens(server: ZedServer, params: types.SemanticTokensParams):
|
|
267
|
+
path = uri_to_path(params.text_document.uri)
|
|
268
|
+
return server.on_semantic_tokens(path)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@zedserver.feature(types.TEXT_DOCUMENT_HOVER)
|
|
272
|
+
def hover(server: ZedServer, params: types.HoverParams) -> types.Hover | None:
|
|
273
|
+
path = uri_to_path(params.text_document.uri)
|
|
274
|
+
return server.on_document_hover(path, params.position)
|
|
275
|
+
|
|
276
|
+
@zedserver.feature(types.TEXT_DOCUMENT_LINKED_EDITING_RANGE)
|
|
277
|
+
def linked_editing_range(server: ZedServer, params: types.LinkedEditingRangeParams) -> types.LinkedEditingRanges | None:
|
|
278
|
+
path = uri_to_path(params.text_document.uri)
|
|
279
|
+
return server.on_linked_editing_range(path, params.position)
|
|
280
|
+
|
|
281
|
+
@zedserver.feature(types.TEXT_DOCUMENT_DEFINITION)
|
|
282
|
+
def definition(server: ZedServer, params: types.DefinitionParams) -> types.DefinitionResult:
|
|
283
|
+
path = uri_to_path(params.text_document.uri)
|
|
284
|
+
return server.on_definition(path, params.position)
|
|
285
|
+
|
|
286
|
+
@zedserver.feature(types.WORKSPACE_DID_CHANGE_WATCHED_FILES)
|
|
287
|
+
def did_change_watched_files(server: ZedServer, params: types.DidChangeWatchedFilesParams) -> None:
|
|
288
|
+
return server.on_did_change_watched_files(params)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional
|
|
2
|
+
|
|
3
|
+
from .lexer import Token, TokenCollection
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .lexer import TextPosition
|
|
7
|
+
|
|
8
|
+
class BlockBody:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.children: list[Node] = []
|
|
12
|
+
"""Child nodes."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Node:
|
|
16
|
+
"""Base class for Zedscript AST nodes."""
|
|
17
|
+
def __init__(self, *, comments: Optional[list[Token]] = None) -> None:
|
|
18
|
+
super().__init__()
|
|
19
|
+
if comments is None:
|
|
20
|
+
comments = []
|
|
21
|
+
self.comments: list[Token] = comments
|
|
22
|
+
"""
|
|
23
|
+
Comments for the node.
|
|
24
|
+
For blocks, all comments before the opening bracket are included.
|
|
25
|
+
For values, all comments in the range of the value are included.
|
|
26
|
+
A TokenCollection is not used because the tokens might not be contiguous.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BlockNode(Node, BlockBody):
|
|
31
|
+
"""
|
|
32
|
+
Zedscript block.
|
|
33
|
+
"""
|
|
34
|
+
def __init__(
|
|
35
|
+
self, type: TokenCollection | None, id: TokenCollection | None,
|
|
36
|
+
open_bracket: Token, close_bracket: Token | None,
|
|
37
|
+
*, comments: Optional[list[Token]] = None
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__(comments=comments)
|
|
40
|
+
self.type: TokenCollection | None = type
|
|
41
|
+
"""
|
|
42
|
+
Tokens representing the type of the block.
|
|
43
|
+
This never has any whitespace, but a collection is used because comments can appear anywhere.
|
|
44
|
+
This will never be None in well formed Zedscript.
|
|
45
|
+
"""
|
|
46
|
+
self.id: TokenCollection | None = id
|
|
47
|
+
"""
|
|
48
|
+
Tokens representing the id of the block.
|
|
49
|
+
This never has any whitespace, but a collection is used because comments can appear anywhere.
|
|
50
|
+
"""
|
|
51
|
+
self.open_bracket: Token = open_bracket
|
|
52
|
+
"""
|
|
53
|
+
Opening bracket token. (``{``)
|
|
54
|
+
"""
|
|
55
|
+
self.close_bracket: Token | None = close_bracket
|
|
56
|
+
"""
|
|
57
|
+
Closing bracket token. (``}``)
|
|
58
|
+
This will never be None in well formed Zedscript.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def tokens(self) -> TokenCollection:
|
|
63
|
+
result = TokenCollection()
|
|
64
|
+
if self.type is not None:
|
|
65
|
+
result.extend(self.type)
|
|
66
|
+
if self.id is not None:
|
|
67
|
+
result.extend(self.id)
|
|
68
|
+
result.append(self.open_bracket)
|
|
69
|
+
if self.close_bracket is not None:
|
|
70
|
+
result.append(self.close_bracket)
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
def well_formed(self) -> bool:
|
|
74
|
+
return self.type is not None and self.close_bracket is not None
|
|
75
|
+
|
|
76
|
+
def contains_position(self, text_position: 'TextPosition') -> bool:
|
|
77
|
+
return self.tokens.to_range() == text_position
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ValueNode(Node):
|
|
81
|
+
"""
|
|
82
|
+
Zedscript value.
|
|
83
|
+
"""
|
|
84
|
+
def __init__(self, tokens: TokenCollection, comma: Token, *, comments: Optional[list[Token]] = None) -> None:
|
|
85
|
+
super().__init__(comments=comments)
|
|
86
|
+
self.tokens: TokenCollection = tokens
|
|
87
|
+
"""Tokens comprising the value."""
|
|
88
|
+
self.comma: Token = comma
|
|
89
|
+
"""
|
|
90
|
+
Comma token following the value.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def is_key_value(self) -> bool:
|
|
94
|
+
return "=" in str(self.tokens)
|
|
95
|
+
|
|
96
|
+
def value(self) -> TokenCollection:
|
|
97
|
+
if not self.is_key_value():
|
|
98
|
+
return self.tokens.strip()
|
|
99
|
+
value = TokenCollection()
|
|
100
|
+
|
|
101
|
+
# for token in reversed(self.tokens):
|
|
102
|
+
found_equal = False
|
|
103
|
+
for token in self.tokens:
|
|
104
|
+
if not found_equal:
|
|
105
|
+
if token.text == "=":
|
|
106
|
+
found_equal = True
|
|
107
|
+
continue
|
|
108
|
+
# if token.text == "=":
|
|
109
|
+
# found_equal = True
|
|
110
|
+
# break
|
|
111
|
+
value.append(token)
|
|
112
|
+
|
|
113
|
+
# since we looped backwards, they've been inserted backwards
|
|
114
|
+
# value.reverse()
|
|
115
|
+
return value.strip()
|
|
116
|
+
|
|
117
|
+
def key(self) -> TokenCollection:
|
|
118
|
+
assert self.is_key_value()
|
|
119
|
+
key = TokenCollection()
|
|
120
|
+
|
|
121
|
+
for token in self.tokens:
|
|
122
|
+
if token.text == "=":
|
|
123
|
+
break
|
|
124
|
+
key.append(token)
|
|
125
|
+
|
|
126
|
+
return key.strip()
|
|
127
|
+
|
|
128
|
+
def equals(self) -> Token:
|
|
129
|
+
assert self.is_key_value()
|
|
130
|
+
for token in self.tokens:
|
|
131
|
+
if token.text == "=":
|
|
132
|
+
return token
|
|
133
|
+
raise RuntimeError("Couldn't find equals token")
|
|
134
|
+
|
|
135
|
+
def contains_position(self, text_position: 'TextPosition') -> bool:
|
|
136
|
+
return self.tokens.to_range() == text_position
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Chunk(BlockBody):
|
|
140
|
+
"""
|
|
141
|
+
Root of a Zedscript chunk.
|
|
142
|
+
A chunk is essentially a block body, but we use a separate class to avoid ambiguity.
|
|
143
|
+
"""
|