forked from secondlife/lsl-definitions
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathslua.py
More file actions
255 lines (227 loc) · 10.1 KB
/
Copy pathslua.py
File metadata and controls
255 lines (227 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
"""Generators for SLua typechecker definition files (Luau LSP and Selene)."""
from __future__ import annotations
import io
import yaml
from lsl_definitions.generators.base import register
from lsl_definitions.lsl import LSLDefinitions
from lsl_definitions.slua import (
SLuaClassDeclaration,
SLuaDefinitions,
SLuaFunction,
SLuaModule,
SLuaParameter,
SLuaProperty,
)
from lsl_definitions.utils import Deprecated, remove_nones
@register("slua_lsp_defs")
def gen_luau_lsp_defs(definitions: LSLDefinitions, slua_definitions: SLuaDefinitions) -> str:
"""Generate SLua definitions for Luau Language Server"""
ll_module = slua_definitions.modules["ll"]
llcompat_module = slua_definitions.modules["llcompat"]
defs = io.StringIO()
defs.write("""-- Second Life SLua (Server Lua) definitions file for luau-lsp.
-- This file is auto-generated by https://github.com/secondlife/lsl-definitions.
----------------------------------
---------- LSL LUAU DEFS ---------
----------------------------------
""")
# 1. Luau builtins unneeded. Luau-lsp already know about these
# 2. SLua base classes. These only depend on Luau builtins
classes = list(slua_definitions.base_classes.values()) + list(slua_definitions.classes.values())
classes.sort(key=lambda x: x.name)
for class_ in (class_ for class_ in classes if class_.name[0].islower()):
class_.write_luau_def(defs)
defs.write("\n")
for alias in slua_definitions.type_aliases.values():
defs.write(alias.to_luau_def())
defs.write("\n")
defs.write("\n")
# 3. SLua standard library. Depends on base classes
for class_ in (class_ for class_ in classes if class_.name[0].isupper()):
class_.write_luau_def(defs)
defs.write("\n")
for func in slua_definitions.functions.values():
if func.private or func.local_only:
continue
if not func.typechecker_flags.fully_defined:
defs.write("-- ")
func.write_luau_global_def(defs)
for module in sorted(slua_definitions.modules.values(), key=lambda x: x.name):
if module.name in {"ll", "llcompat"}:
continue
if module.name == "string":
defs.write(
"--[[ commented out to avoid shadowing magic type functions find, format, gmatch, and match\n"
)
module.write_luau_def(defs)
if module.name == "string":
defs.write("--]]\n")
for var in slua_definitions.global_variables.values():
defs.write("declare ")
defs.write(var.to_luau_def())
defs.write("\n")
ll_module.write_luau_def(defs)
llcompat_module.write_luau_def(defs)
for const in sorted(slua_definitions.global_constants.values(), key=lambda x: x.name):
if const.private:
continue
defs.write("declare ")
defs.write(const.to_luau_def())
defs.write("\n")
return defs.getvalue()
@register("slua_selene")
def gen_selene_yml(definitions: LSLDefinitions, slua_definitions: SLuaDefinitions) -> str:
"""Generate SLua standard library for Selene linter
https://kampfkarren.github.io/selene/usage/std.html
"""
classes = {**slua_definitions.base_classes, **slua_definitions.classes}
file = io.StringIO()
file.write("""# Second Life SLua (Server Lua) standard library definition file for selene.
# This file is auto-generated by https://github.com/secondlife/lsl-definitions.
""")
selene = {
"base": "luau",
"name": "Second Life SLua language support",
"lua_versions": ["luau", "lua51"],
"globals": {},
"structs": {},
}
def selene_type(type_str: str, default="any") -> str | dict | None:
if type_str.endswith("?"):
type_str = type_str[:-1]
# GLTF-clearable params are encoded as ``<base> | ""``. Selene args can't
# be unions, so collapse to the base type if selene accepts a bare string
# there; otherwise emit a display-only hint mentioning both forms.
clear_sentinel = ' | ""'
if type_str.endswith(clear_sentinel):
type_str = type_str[: -len(clear_sentinel)]
type_map = {
"boolean": "bool",
"boolean | number": "bool",
"number": "number",
"string": "string",
"string | uuid": "string",
"(string | uuid)": "string",
"buffer": {"display": "buffer"},
"uuid": {"display": "uuid"},
"vector": {"display": "vector"},
"quaternion": {"display": "quaternion"},
"list": "table",
"thread": {"display": "thread"},
}
if type_str in type_map:
return type_map[type_str]
if type_str in slua_definitions.type_aliases:
return slua_definitions.type_aliases[type_str].selene_type
if (type_str.startswith("{") and type_str.endswith("}")) or type_str.startswith("list |"):
return "table"
if "|" in type_str:
return default
if "->" in type_str:
return "function"
if "..." in type_str:
return "..."
return default
def selene_property(prop: SLuaProperty) -> dict:
if prop.type in classes:
return remove_nones(struct=prop.type, description=prop.comment or None)
else:
return remove_nones(
property=prop.modifiable,
type=selene_type(prop.type, default=None),
description=prop.comment or None,
)
def selene_param(param: SLuaParameter) -> dict:
optional = param.type.endswith("?") if param.optional is None else param.optional
return remove_nones(
type=param.selene_type or selene_type(param.type),
required=None if not optional and param.optional is None else not optional,
observes=param.observes,
)
def selene_deprecated(deprecated: Deprecated | None, comment: str) -> dict | None:
if deprecated is None:
return None
messages: list[str] = []
if deprecated.use:
messages.append(f"Use {deprecated.use!r} instead.")
if deprecated.reason:
messages.append(deprecated.reason)
if not messages:
messages.append(comment)
return remove_nones(message=" ".join(messages), replace=deprecated.selene_replace)
def selene_function(func: SLuaFunction, method=False) -> dict:
if func.slua_removed:
return remove_nones(removed=True, description=f"{func.name} is removed in SLua.")
parameters = func.parameters[1:] if method else func.parameters
return remove_nones(
method=method or None,
deprecated=selene_deprecated(func.deprecated, func.comment),
args=[selene_param(a) for a in parameters],
must_use=func.must_use or None,
description=func.comment or None,
)
def selene_class(class_: SLuaClassDeclaration) -> dict:
fields = {}
for method in class_.methods.values():
fields[method.name] = selene_function(method, method=True)
for func in class_.functions.values():
fields[func.name] = selene_function(func, method=False)
for prop in class_.properties.values():
fields[prop.name] = selene_property(prop)
return fields
def selene_module(module: SLuaModule) -> dict:
globals = {}
if module.callable:
globals[module.name] = selene_function(module.callable)
globals.update(
{
f"{module.name}.{const.name}": selene_property(const)
# for func in sorted(self.functions, key=lambda x: x.name)
for const in module.constants.values()
if not const.private
}
)
globals.update(
{
f"{module.name}.{func.name}": selene_function(func)
# for func in sorted(self.functions, key=lambda x: x.name)
for func in module.functions.values()
if not func.private and not func.local_only
}
)
return globals
# Duplicate quaternion module as rotation. The callable aspect of quaternion
# prevents us from being able to de-duplicate this with structs.
slua_definitions.modules["rotation"] = SLuaModule(
name="rotation",
comment=slua_definitions.modules["quaternion"].comment,
callable=slua_definitions.modules["quaternion"].callable,
constants=slua_definitions.modules["quaternion"].constants,
functions=slua_definitions.modules["quaternion"].functions,
)
for const in slua_definitions.global_variables.values():
if not const.private and const.name != "rotation":
selene["globals"][const.name] = selene_property(const)
for const in sorted(slua_definitions.global_constants.values(), key=lambda x: x.name):
if not const.private:
selene["globals"][const.name] = selene_property(const)
for func in sorted(slua_definitions.functions.values(), key=lambda x: x.name):
if not func.private and not func.local_only:
selene["globals"][func.name] = selene_function(func)
for module in sorted(slua_definitions.modules.values(), key=lambda x: x.name):
if module.name not in {"ll", "llcompat"}:
selene["globals"].update(selene_module(module))
selene["globals"].update(selene_module(slua_definitions.modules["ll"]))
selene["globals"].update(selene_module(slua_definitions.modules["llcompat"]))
for class_ in classes.values():
selene["structs"][class_.name] = selene_class(class_)
# Fix up LLEvents argument types
event_names = [m for m in slua_definitions.type_aliases.values() if m.name == "LLEventName"][
0
].selene_type
for method_name in ["on", "once", "off", "handlers"]:
selene["structs"]["LLEvents"][method_name]["args"][0]["type"] = event_names
noalias_dumper = yaml.dumper.SafeDumper
noalias_dumper.ignore_aliases = lambda self, data: True
yaml.dump(selene, file, sort_keys=False, Dumper=noalias_dumper)
return file.getvalue()