cluaupp 1.4.1 → 1.5.0
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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/editors/vscode/README.md +17 -0
- package/editors/vscode/extension.js +140 -21
- package/editors/vscode/icons/flare.svg +5 -0
- package/editors/vscode/language-configuration.json +21 -0
- package/editors/vscode/package.json +61 -7
- package/editors/vscode/snippets/flare.json +27 -0
- package/editors/vscode/syntaxes/flare.tmLanguage.json +75 -0
- package/examples/game/.vscode/extensions.json +3 -1
- package/examples/game/.vscode/settings.json +2 -1
- package/examples/game/include/clpp/libs/roster.clh +6 -0
- package/examples/game/src/ReplicatedStorage/Shared/Net/Net.flare +5 -0
- package/examples/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
- package/generated/cli.js +2 -2
- package/generated/flare/index.js +15 -3
- package/generated/flare/language.js +561 -0
- package/generated/flare/parse.js +7 -6
- package/generated/intellisense.js +20 -2
- package/generated/lsp.js +118 -7
- package/include/clpp/libs/roster.clh +6 -0
- package/package.json +1 -1
- package/runtime/Roster/init.luau +6 -0
- package/src/cli.ts +2 -2
- package/src/flare/index.ts +20 -1
- package/src/flare/language.ts +708 -0
- package/src/flare/parse.ts +6 -6
- package/src/intellisense.ts +19 -2
- package/src/lsp.ts +151 -8
- package/templates/game/.vscode/extensions.json +3 -1
- package/templates/game/.vscode/settings.json +2 -1
- package/templates/game/include/clpp/libs/roster.clh +6 -0
- package/templates/game/src/ReplicatedStorage/Shared/Net/Net.flare +5 -0
- package/templates/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
package/src/flare/parse.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FlareField, FlareQuery, FlarePacket, FlareSchema, FlareSide, FlareType } from "./types.js";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const FLARE_TYPE_ALIASES: Record<string, FlareType> = {
|
|
4
4
|
u8: "u8",
|
|
5
5
|
u16: "u16",
|
|
6
6
|
u32: "u32",
|
|
@@ -28,14 +28,14 @@ const TYPES: Record<string, FlareType> = {
|
|
|
28
28
|
Player: "Player",
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
export const FLARE_IDENT = "[A-Za-z_][A-Za-z0-9_]*";
|
|
32
32
|
|
|
33
33
|
function fail(file: string, line: number, message: string): never {
|
|
34
34
|
throw new Error(`cluaupp flare: ${file}:${line}: ${message}`);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function parseType(raw: string, file: string, line: number): FlareType {
|
|
38
|
-
const mapped =
|
|
38
|
+
const mapped = FLARE_TYPE_ALIASES[raw];
|
|
39
39
|
if (!mapped) {
|
|
40
40
|
fail(file, line, `unknown type '${raw}'`);
|
|
41
41
|
}
|
|
@@ -53,7 +53,7 @@ function parseFields(list: string, file: string, line: number): FlareField[] {
|
|
|
53
53
|
if (!piece) {
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
const match = piece.match(new RegExp(`^(${
|
|
56
|
+
const match = piece.match(new RegExp(`^(${FLARE_IDENT})\\s+(${FLARE_IDENT})$`));
|
|
57
57
|
if (!match) {
|
|
58
58
|
fail(file, line, `expected 'Type name' in '${piece}'`);
|
|
59
59
|
}
|
|
@@ -101,7 +101,7 @@ export function parseFlare(source: string, filePath: string, defaultName: string
|
|
|
101
101
|
|
|
102
102
|
const packet = line.match(
|
|
103
103
|
new RegExp(
|
|
104
|
-
`^packet\\s+(${
|
|
104
|
+
`^packet\\s+(${FLARE_IDENT})\\s*\\((.*)\\)\\s+from\\s+(Client|Server)(?:\\s+(unreliable|reliable))?\\s*$`,
|
|
105
105
|
"i",
|
|
106
106
|
),
|
|
107
107
|
);
|
|
@@ -121,7 +121,7 @@ export function parseFlare(source: string, filePath: string, defaultName: string
|
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
const query = line.match(new RegExp(`^query\\s+(${
|
|
124
|
+
const query = line.match(new RegExp(`^query\\s+(${FLARE_IDENT})\\s*\\((.*)\\)\\s*->\\s*(${FLARE_IDENT})\\s*$`, "i"));
|
|
125
125
|
if (query) {
|
|
126
126
|
if (queryId > 255) {
|
|
127
127
|
fail(file, lineNo, "too many queries (max 255)");
|
package/src/intellisense.ts
CHANGED
|
@@ -82,6 +82,7 @@ function writeVscode(root: string): void {
|
|
|
82
82
|
"*.server.clpp": "clpp",
|
|
83
83
|
"*.client.clpp": "clpp",
|
|
84
84
|
"*.plugin.clpp": "clpp",
|
|
85
|
+
"*.flare": "flare",
|
|
85
86
|
};
|
|
86
87
|
writeJson(settingsPath, {
|
|
87
88
|
...current,
|
|
@@ -92,7 +93,7 @@ function writeVscode(root: string): void {
|
|
|
92
93
|
const extensions = readJsonObject(extensionsPath);
|
|
93
94
|
writeJson(extensionsPath, {
|
|
94
95
|
...extensions,
|
|
95
|
-
recommendations: uniqueStrings(extensions.recommendations),
|
|
96
|
+
recommendations: uniqueStrings(extensions.recommendations, ["kartzdev.cluaupp-flare"]),
|
|
96
97
|
unwantedRecommendations: uniqueStrings(extensions.unwantedRecommendations, ["ms-vscode.cpptools"]),
|
|
97
98
|
});
|
|
98
99
|
}
|
|
@@ -112,7 +113,7 @@ export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
|
|
|
112
113
|
if (!fs.existsSync(from)) {
|
|
113
114
|
return [];
|
|
114
115
|
}
|
|
115
|
-
const id = `kartzdev.cluaupp-
|
|
116
|
+
const id = `kartzdev.cluaupp-flare-${pkg.version}`;
|
|
116
117
|
const homes = [path.join(os.homedir(), ".cursor", "extensions"), path.join(os.homedir(), ".vscode", "extensions")];
|
|
117
118
|
const installed: string[] = [];
|
|
118
119
|
for (const home of homes) {
|
|
@@ -123,6 +124,16 @@ export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
|
|
|
123
124
|
const dest = path.join(home, id);
|
|
124
125
|
copyDir(from, dest);
|
|
125
126
|
fs.writeFileSync(path.join(dest, "cluaupp.root"), path.resolve(cluauppRoot), "utf8");
|
|
127
|
+
const manifestPath = path.join(dest, "package.json");
|
|
128
|
+
if (fs.existsSync(manifestPath)) {
|
|
129
|
+
try {
|
|
130
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as Record<string, unknown>;
|
|
131
|
+
manifest.version = pkg.version;
|
|
132
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`, "utf8");
|
|
133
|
+
} catch {
|
|
134
|
+
// keep the copied manifest
|
|
135
|
+
}
|
|
136
|
+
}
|
|
126
137
|
installed.push(dest);
|
|
127
138
|
}
|
|
128
139
|
return installed;
|
|
@@ -130,6 +141,12 @@ export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
|
|
|
130
141
|
|
|
131
142
|
export async function installEditorSupport() {
|
|
132
143
|
const local = installEditorExtension();
|
|
144
|
+
if (local.length > 0) {
|
|
145
|
+
console.log("cluaupp: Flare IntelliSense installed");
|
|
146
|
+
for (const dest of local) {
|
|
147
|
+
console.log(" ", dest);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
133
150
|
if (hasClpp()) {
|
|
134
151
|
const result = spawnSync("clpp", ["install"], { encoding: "utf8", windowsHide: true });
|
|
135
152
|
if (result.status === 0) {
|
package/src/lsp.ts
CHANGED
|
@@ -1,30 +1,173 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
CompletionItemKind,
|
|
3
|
+
DiagnosticSeverity,
|
|
4
|
+
MarkupKind,
|
|
5
|
+
ParameterInformation,
|
|
3
6
|
ProposedFeatures,
|
|
7
|
+
SignatureInformation,
|
|
8
|
+
SymbolKind,
|
|
4
9
|
TextDocuments,
|
|
5
10
|
TextDocumentSyncKind,
|
|
11
|
+
createConnection,
|
|
12
|
+
type CompletionItem,
|
|
13
|
+
type Diagnostic,
|
|
14
|
+
type DocumentSymbol,
|
|
15
|
+
type Hover,
|
|
16
|
+
type SignatureHelp,
|
|
6
17
|
} from "vscode-languageserver/node";
|
|
7
18
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
19
|
+
import {
|
|
20
|
+
completionsAt,
|
|
21
|
+
hoverAt,
|
|
22
|
+
isFlareDocument,
|
|
23
|
+
signatureAt,
|
|
24
|
+
symbolsIn,
|
|
25
|
+
diagnoseFlare,
|
|
26
|
+
type FlareCompletionKind,
|
|
27
|
+
type FlareSeverity,
|
|
28
|
+
} from "./flare/language.js";
|
|
8
29
|
import { pkg } from "./package-info.js";
|
|
9
30
|
|
|
31
|
+
const KIND: Record<FlareCompletionKind, CompletionItemKind> = {
|
|
32
|
+
keyword: CompletionItemKind.Keyword,
|
|
33
|
+
type: CompletionItemKind.TypeParameter,
|
|
34
|
+
snippet: CompletionItemKind.Snippet,
|
|
35
|
+
value: CompletionItemKind.EnumMember,
|
|
36
|
+
field: CompletionItemKind.Field,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const SEVERITY: Record<FlareSeverity, DiagnosticSeverity> = {
|
|
40
|
+
error: DiagnosticSeverity.Error,
|
|
41
|
+
warning: DiagnosticSeverity.Warning,
|
|
42
|
+
hint: DiagnosticSeverity.Hint,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const SYMBOL: Record<"packet" | "query" | "option", SymbolKind> = {
|
|
46
|
+
packet: SymbolKind.Event,
|
|
47
|
+
query: SymbolKind.Method,
|
|
48
|
+
option: SymbolKind.Namespace,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function flareDiagnostics(document: TextDocument): Diagnostic[] {
|
|
52
|
+
return diagnoseFlare(document.getText(), document.uri).map((item) => ({
|
|
53
|
+
range: {
|
|
54
|
+
start: { line: item.line, character: item.start },
|
|
55
|
+
end: { line: item.line, character: Math.max(item.end, item.start + 1) },
|
|
56
|
+
},
|
|
57
|
+
message: item.message,
|
|
58
|
+
severity: SEVERITY[item.severity],
|
|
59
|
+
source: "flare",
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
10
63
|
export function start(_options: { projectRoot?: string } = {}): void {
|
|
11
64
|
const connection = createConnection(ProposedFeatures.all);
|
|
12
65
|
const documents = new TextDocuments(TextDocument);
|
|
13
66
|
|
|
14
67
|
connection.onInitialize(() => ({
|
|
15
68
|
capabilities: {
|
|
16
|
-
textDocumentSync: TextDocumentSyncKind.
|
|
69
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
70
|
+
completionProvider: {
|
|
71
|
+
triggerCharacters: [" ", "(", ",", ">", "="],
|
|
72
|
+
resolveProvider: false,
|
|
73
|
+
},
|
|
74
|
+
hoverProvider: true,
|
|
75
|
+
documentSymbolProvider: true,
|
|
76
|
+
signatureHelpProvider: {
|
|
77
|
+
triggerCharacters: ["(", ","],
|
|
78
|
+
},
|
|
17
79
|
},
|
|
18
|
-
serverInfo: { name: "cluaupp", version: pkg.version },
|
|
80
|
+
serverInfo: { name: "cluaupp-flare", version: pkg.version },
|
|
19
81
|
}));
|
|
20
82
|
|
|
21
|
-
const
|
|
22
|
-
|
|
83
|
+
const publish = (document: TextDocument) => {
|
|
84
|
+
if (!isFlareDocument(document.uri)) {
|
|
85
|
+
connection.sendDiagnostics({ uri: document.uri, diagnostics: [] });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
connection.sendDiagnostics({ uri: document.uri, diagnostics: flareDiagnostics(document) });
|
|
23
89
|
};
|
|
24
90
|
|
|
25
|
-
documents.onDidOpen((event) =>
|
|
26
|
-
documents.onDidChangeContent((event) =>
|
|
27
|
-
documents.onDidClose((event) =>
|
|
91
|
+
documents.onDidOpen((event) => publish(event.document));
|
|
92
|
+
documents.onDidChangeContent((event) => publish(event.document));
|
|
93
|
+
documents.onDidClose((event) => {
|
|
94
|
+
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
connection.onCompletion((params): CompletionItem[] => {
|
|
98
|
+
const document = documents.get(params.textDocument.uri);
|
|
99
|
+
if (!document || !isFlareDocument(document.uri)) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return completionsAt(document.getText(), params.position.line, params.position.character).map((item) => ({
|
|
103
|
+
label: item.label,
|
|
104
|
+
kind: KIND[item.kind],
|
|
105
|
+
detail: item.detail,
|
|
106
|
+
documentation: { kind: MarkupKind.Markdown, value: item.documentation },
|
|
107
|
+
insertText: item.insertText,
|
|
108
|
+
insertTextFormat: item.snippet ? 2 : 1,
|
|
109
|
+
sortText: item.sortText,
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
connection.onHover((params): Hover | null => {
|
|
114
|
+
const document = documents.get(params.textDocument.uri);
|
|
115
|
+
if (!document || !isFlareDocument(document.uri)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const hover = hoverAt(document.getText(), params.position.line, params.position.character);
|
|
119
|
+
if (!hover) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
contents: { kind: MarkupKind.Markdown, value: hover.contents },
|
|
124
|
+
range: {
|
|
125
|
+
start: { line: params.position.line, character: hover.start },
|
|
126
|
+
end: { line: params.position.line, character: hover.end },
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
connection.onDocumentSymbol((params): DocumentSymbol[] => {
|
|
132
|
+
const document = documents.get(params.textDocument.uri);
|
|
133
|
+
if (!document || !isFlareDocument(document.uri)) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
return symbolsIn(document.getText()).map((symbol) => ({
|
|
137
|
+
name: symbol.name,
|
|
138
|
+
detail: symbol.detail,
|
|
139
|
+
kind: SYMBOL[symbol.kind],
|
|
140
|
+
range: {
|
|
141
|
+
start: { line: symbol.line, character: 0 },
|
|
142
|
+
end: { line: symbol.line, character: Math.max(symbol.end, 1) },
|
|
143
|
+
},
|
|
144
|
+
selectionRange: {
|
|
145
|
+
start: { line: symbol.line, character: symbol.start },
|
|
146
|
+
end: { line: symbol.line, character: Math.max(symbol.end, symbol.start + 1) },
|
|
147
|
+
},
|
|
148
|
+
}));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
connection.onSignatureHelp((params): SignatureHelp | null => {
|
|
152
|
+
const document = documents.get(params.textDocument.uri);
|
|
153
|
+
if (!document || !isFlareDocument(document.uri)) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const signature = signatureAt(document.getText(), params.position.line, params.position.character);
|
|
157
|
+
if (!signature) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const info = SignatureInformation.create(
|
|
161
|
+
signature.label,
|
|
162
|
+
signature.documentation,
|
|
163
|
+
...signature.parameters.map((parameter) => ParameterInformation.create(parameter.label, parameter.documentation)),
|
|
164
|
+
);
|
|
165
|
+
return {
|
|
166
|
+
signatures: [info],
|
|
167
|
+
activeSignature: 0,
|
|
168
|
+
activeParameter: signature.activeParameter,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
28
171
|
|
|
29
172
|
documents.listen(connection);
|
|
30
173
|
connection.listen();
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
// Table = u16 count + key/value pairs. Array = u16 len + values. Methods also work as Roster::X(rawTable).
|
|
6
6
|
|
|
7
7
|
struct Roster {
|
|
8
|
+
// Construct with `new Roster()` / `new Roster(table)` — emits Luau Roster.new.
|
|
9
|
+
// `New` is the CL++ name because `new` is a keyword.
|
|
8
10
|
static Roster New();
|
|
11
|
+
static Roster New(auto initial);
|
|
9
12
|
|
|
10
13
|
int Size();
|
|
11
14
|
int Length();
|
|
@@ -13,6 +16,7 @@ struct Roster {
|
|
|
13
16
|
auto GetData();
|
|
14
17
|
|
|
15
18
|
auto At(int index);
|
|
19
|
+
// Array value search (1-based index). Dictionary keys: Has / Get / Set / Remove.
|
|
16
20
|
int Find(auto value);
|
|
17
21
|
int Find(auto value, int initPos);
|
|
18
22
|
int Find(auto value, int initPos, int maxPos);
|
|
@@ -23,6 +27,7 @@ struct Roster {
|
|
|
23
27
|
auto Shift();
|
|
24
28
|
int Unshift(auto value);
|
|
25
29
|
auto RemoveAt(int index);
|
|
30
|
+
Roster Remove(auto key);
|
|
26
31
|
|
|
27
32
|
Roster Map(func mapper);
|
|
28
33
|
Roster Filter(func predicate);
|
|
@@ -74,6 +79,7 @@ struct Roster {
|
|
|
74
79
|
static auto Shift(auto data);
|
|
75
80
|
static int Unshift(auto data, auto value);
|
|
76
81
|
static auto RemoveAt(auto data, int index);
|
|
82
|
+
static Roster Remove(auto data, auto key);
|
|
77
83
|
static Roster Map(auto data, func mapper);
|
|
78
84
|
static Roster Filter(auto data, func predicate);
|
|
79
85
|
static auto Reduce(auto data, func reducer, auto initial);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
#include "../../ReplicatedStorage/Shared/Constants/Datas/TemplateData.clh"
|
|
5
5
|
|
|
6
6
|
void init() {
|
|
7
|
-
PlayerTemplate playerData;
|
|
7
|
+
PlayerTemplate playerData = PlayerTemplate();
|
|
8
8
|
Keep.Server.Init(DataServiceOptions {
|
|
9
9
|
.Template = playerData,
|
|
10
10
|
.StoreName = "PlayerData",
|