fbp-language 1.1.9 → 1.2.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/assets/fbp-logo.svg +3 -3
- package/extension.js +167 -0
- package/fbp-language-1.2.0.vsix +0 -0
- package/language-configuration.json +1 -1
- package/package.json +11 -62
- package/syntaxes/fbp.tmLanguage.json +28 -79
- package/themes/fbp-icon-theme.json +1 -1
- package/README.md +0 -253
- package/assets/fbp-logo.png +0 -0
- package/dist/cli.js +0 -160
- package/dist/compiler.js +0 -513
- package/dist/errors.js +0 -24
- package/themes/fbp-dark-color-theme.json +0 -89
- package/vscode-extension/assets/fbp-logo.svg +0 -8
- package/vscode-extension/language-configuration.json +0 -21
- package/vscode-extension/package.json +0 -46
- package/vscode-extension/syntaxes/fbp.tmLanguage.json +0 -49
- package/vscode-extension/themes/fbp-icon-theme.json +0 -11
package/dist/compiler.js
DELETED
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.FbpCompiler = void 0;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const errors_1 = require("./errors");
|
|
10
|
-
const discord_js_1 = require("discord.js");
|
|
11
|
-
const dotenv_1 = __importDefault(require("dotenv"));
|
|
12
|
-
function valueType(value) {
|
|
13
|
-
const v = value.trim();
|
|
14
|
-
if (/^"(?:[^"\\]|\\.)*"$/.test(v) || /^'(?:[^'\\]|\\.)*'$/.test(v))
|
|
15
|
-
return "texte";
|
|
16
|
-
if (/^-?\d+(?:\.\d+)?$/.test(v))
|
|
17
|
-
return "nombre";
|
|
18
|
-
if (v === "true" || v === "false")
|
|
19
|
-
return "booleen";
|
|
20
|
-
return "inconnu";
|
|
21
|
-
}
|
|
22
|
-
function splitArguments(raw) {
|
|
23
|
-
const result = [];
|
|
24
|
-
let current = "";
|
|
25
|
-
let quote = "";
|
|
26
|
-
let depth = 0;
|
|
27
|
-
for (const char of raw) {
|
|
28
|
-
if (quote) {
|
|
29
|
-
current += char;
|
|
30
|
-
if (char === quote)
|
|
31
|
-
quote = "";
|
|
32
|
-
}
|
|
33
|
-
else if (char === '"' || char === "'") {
|
|
34
|
-
quote = char;
|
|
35
|
-
current += char;
|
|
36
|
-
}
|
|
37
|
-
else if (char === "(") {
|
|
38
|
-
depth++;
|
|
39
|
-
current += char;
|
|
40
|
-
}
|
|
41
|
-
else if (char === ")") {
|
|
42
|
-
depth--;
|
|
43
|
-
current += char;
|
|
44
|
-
}
|
|
45
|
-
else if (char === "," && depth === 0) {
|
|
46
|
-
if (current.trim())
|
|
47
|
-
result.push(current.trim());
|
|
48
|
-
current = "";
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
current += char;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (current.trim())
|
|
55
|
-
result.push(current.trim());
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
function parseFile(file, source) {
|
|
59
|
-
const lines = source.split(/\r?\n/);
|
|
60
|
-
const root = [];
|
|
61
|
-
const stack = [];
|
|
62
|
-
const target = () => stack.length ? stack[stack.length - 1].body : root;
|
|
63
|
-
for (let index = 0; index < lines.length; index++) {
|
|
64
|
-
const raw = lines[index];
|
|
65
|
-
const line = raw.trim();
|
|
66
|
-
const lineNo = index + 1;
|
|
67
|
-
if (!line || line.startsWith("//") || line.startsWith("#"))
|
|
68
|
-
continue;
|
|
69
|
-
const importMatch = line.match(/^import\s*\{\s*([^}]+)\s*\}\s*de\s*(["'])(.+?)\2\s*$/);
|
|
70
|
-
if (importMatch) {
|
|
71
|
-
target().push({ kind: "import", names: importMatch[1].split(",").map(x => x.trim()), source: importMatch[3], line: lineNo });
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
const functionMatch = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*\(\s*\)\s*\{$/u);
|
|
75
|
-
if (functionMatch) {
|
|
76
|
-
const entry = { name: functionMatch[1], line: lineNo, body: [] };
|
|
77
|
-
target().push({ kind: "function", ...entry });
|
|
78
|
-
stack.push(entry);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (line === "}") {
|
|
82
|
-
if (!stack.length)
|
|
83
|
-
throw new errors_1.FbpError("Accolade fermante sans fonction correspondante.", file, lineNo);
|
|
84
|
-
stack.pop();
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
const assignment = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*=\s*(.+)$/u);
|
|
88
|
-
if (assignment) {
|
|
89
|
-
target().push({ kind: "assign", name: assignment[1], value: assignment[2].trim(), line: lineNo });
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const call = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*\((.*)\)\s*;?$/u);
|
|
93
|
-
if (call) {
|
|
94
|
-
target().push({ kind: "call", name: call[1], args: splitArguments(call[2]), line: lineNo });
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
throw new errors_1.FbpError(`Syntaxe non reconnue : « ${line} ». Vérifie les parenthèses, les accolades et le signe =.`, file, lineNo);
|
|
98
|
-
}
|
|
99
|
-
if (stack.length) {
|
|
100
|
-
const open = stack[stack.length - 1];
|
|
101
|
-
throw new errors_1.FbpError(`La fonction « ${open.name} » n'est pas fermée par }.`, file, open.line);
|
|
102
|
-
}
|
|
103
|
-
return root;
|
|
104
|
-
}
|
|
105
|
-
class FbpCompiler {
|
|
106
|
-
variables = new Map();
|
|
107
|
-
functions = new Map();
|
|
108
|
-
imports = new Map();
|
|
109
|
-
output;
|
|
110
|
-
discordClient;
|
|
111
|
-
discordCommands = new Map();
|
|
112
|
-
constructor(options = {}) {
|
|
113
|
-
this.output = options.onOutput ?? (value => console.log(value));
|
|
114
|
-
}
|
|
115
|
-
resolveDiscordValue(source, interaction, selectedUser, selectedMember, optionValues) {
|
|
116
|
-
const guild = interaction.guild;
|
|
117
|
-
const values = {
|
|
118
|
-
"discordServeur.nom": guild?.name ?? "Serveur indisponible",
|
|
119
|
-
"discordServeur.id": guild?.id ?? "Indisponible",
|
|
120
|
-
"discordServeur.creation": guild ? "<t:" + Math.floor(guild.createdTimestamp / 1000) + ":D>" : "Indisponible",
|
|
121
|
-
"discordMembres.nombre": guild ? String(guild.memberCount) : "Indisponible",
|
|
122
|
-
"discordProfil.nom": selectedMember?.displayName ?? selectedUser?.username ?? "Sélectionne un membre",
|
|
123
|
-
"discordProfil.id": selectedUser?.id ?? "Sélectionne un membre",
|
|
124
|
-
"discordProfil.avatar": selectedUser?.displayAvatarURL({ size: 256 }) ?? "Sélectionne un membre",
|
|
125
|
-
"discordRoles.nombre": guild ? String(guild.roles.cache.size) : "Indisponible",
|
|
126
|
-
"discordSalons.nombre": guild ? String(guild.channels.cache.size) : "Indisponible",
|
|
127
|
-
"discordStatistiques.membres": guild ? String(guild.memberCount) : "Indisponible",
|
|
128
|
-
"discordStatistiques.roles": guild ? String(guild.roles.cache.size) : "Indisponible",
|
|
129
|
-
"discordStatistiques.salons": guild ? String(guild.channels.cache.size) : "Indisponible",
|
|
130
|
-
"discordLatence": String(Math.max(0, Math.round(interaction.client.ws.ping))) + " ms",
|
|
131
|
-
"discordAvatar": selectedUser?.displayAvatarURL({ size: 256 }) ?? interaction.user.displayAvatarURL({ size: 256 })
|
|
132
|
-
};
|
|
133
|
-
const optionMatch = source.trim().match(/^(?:discordOption|option)\.([a-z0-9_-]{1,32})$/);
|
|
134
|
-
if (optionMatch)
|
|
135
|
-
return optionValues.get(optionMatch[1]) ?? "Option indisponible : " + optionMatch[1];
|
|
136
|
-
return source.trim() in values ? values[source.trim()] : "Source inconnue : " + source.trim();
|
|
137
|
-
}
|
|
138
|
-
runFile(filePath) {
|
|
139
|
-
const absolute = node_path_1.default.resolve(filePath);
|
|
140
|
-
if (!node_fs_1.default.existsSync(absolute))
|
|
141
|
-
throw new errors_1.FbpError(`Fichier introuvable : ${absolute}`);
|
|
142
|
-
if (!node_fs_1.default.statSync(absolute).isFile())
|
|
143
|
-
throw new errors_1.FbpError(`Le chemin indiqué n'est pas un fichier : ${absolute}`);
|
|
144
|
-
this.execute(parseFile(absolute, node_fs_1.default.readFileSync(absolute, "utf8")), node_path_1.default.dirname(absolute));
|
|
145
|
-
}
|
|
146
|
-
execute(statements, baseDir) {
|
|
147
|
-
for (const statement of statements) {
|
|
148
|
-
if (statement.kind === "import") {
|
|
149
|
-
const targetFile = node_path_1.default.resolve(baseDir, statement.source);
|
|
150
|
-
if (!node_fs_1.default.existsSync(targetFile))
|
|
151
|
-
throw new errors_1.FbpError(`Fichier importé introuvable : ${statement.source}`);
|
|
152
|
-
const nested = parseFile(targetFile, node_fs_1.default.readFileSync(targetFile, "utf8"));
|
|
153
|
-
for (const item of nested) {
|
|
154
|
-
if (item.kind === "function")
|
|
155
|
-
this.functions.set(item.name, item);
|
|
156
|
-
}
|
|
157
|
-
for (const name of statement.names) {
|
|
158
|
-
if (!this.functions.has(name))
|
|
159
|
-
throw new errors_1.FbpError(`La fonction « ${name} » n'existe pas dans ${statement.source}.`);
|
|
160
|
-
this.imports.set(name, targetFile);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else if (statement.kind === "function") {
|
|
164
|
-
if (this.functions.has(statement.name))
|
|
165
|
-
throw new errors_1.FbpError(`La fonction « ${statement.name} » est définie plusieurs fois.`, undefined, statement.line);
|
|
166
|
-
this.functions.set(statement.name, statement);
|
|
167
|
-
}
|
|
168
|
-
else if (statement.kind === "assign") {
|
|
169
|
-
const type = valueType(statement.value);
|
|
170
|
-
const previous = this.variables.get(statement.name);
|
|
171
|
-
if (previous && type !== "inconnu" && previous.type !== "inconnu" && previous.type !== type) {
|
|
172
|
-
throw new errors_1.FbpError(`Type incompatible pour « ${statement.name} » : attendu ${previous.type}, reçu ${type}.`, undefined, statement.line);
|
|
173
|
-
}
|
|
174
|
-
const value = this.evaluate(statement.value, statement.line);
|
|
175
|
-
this.variables.set(statement.name, { type: previous?.type ?? type, value });
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
this.call(statement.name, statement.args, statement.line, baseDir);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
evaluate(expression, line) {
|
|
183
|
-
const value = expression.trim();
|
|
184
|
-
if (value === "true")
|
|
185
|
-
return true;
|
|
186
|
-
if (value === "false")
|
|
187
|
-
return false;
|
|
188
|
-
if (/^-?\d+(?:\.\d+)?$/.test(value))
|
|
189
|
-
return Number(value);
|
|
190
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
191
|
-
return value.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\(["'\\])/g, "$1");
|
|
192
|
-
}
|
|
193
|
-
if (this.variables.has(value))
|
|
194
|
-
return this.variables.get(value).value;
|
|
195
|
-
throw new errors_1.FbpError(`Valeur inconnue « ${value} ». Utilise un texte, un nombre, true, false ou une variable déclarée.`, undefined, line);
|
|
196
|
-
}
|
|
197
|
-
call(name, args, line, baseDir) {
|
|
198
|
-
const values = args.map(arg => this.evaluate(arg, line));
|
|
199
|
-
if (name === "affiche") {
|
|
200
|
-
this.output(values.map(value => value === undefined ? "" : String(value)).join(" "));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (name === "discordConnexion") {
|
|
204
|
-
if (this.discordClient)
|
|
205
|
-
throw new errors_1.FbpError("La connexion Discord est déjà initialisée.", undefined, line);
|
|
206
|
-
dotenv_1.default.config();
|
|
207
|
-
const token = process.env.DISCORD_TOKEN;
|
|
208
|
-
if (!token)
|
|
209
|
-
throw new errors_1.FbpError("Token Discord manquant. Ajoute DISCORD_TOKEN dans le fichier .env.", undefined, line);
|
|
210
|
-
const client = new discord_js_1.Client({ intents: [discord_js_1.GatewayIntentBits.Guilds] });
|
|
211
|
-
this.discordClient = client;
|
|
212
|
-
client.once("ready", async () => {
|
|
213
|
-
try {
|
|
214
|
-
const commands = [...this.discordCommands.entries()].map(([commandName, response]) => {
|
|
215
|
-
const builder = new discord_js_1.SlashCommandBuilder()
|
|
216
|
-
.setName(commandName)
|
|
217
|
-
.setDescription(`Commande ${commandName} du bot FBP`);
|
|
218
|
-
if (response.type === "embed" && response.userOption) {
|
|
219
|
-
builder.addUserOption(option => option.setName(response.userOption.name)
|
|
220
|
-
.setDescription(response.userOption.description)
|
|
221
|
-
.setRequired(false));
|
|
222
|
-
}
|
|
223
|
-
for (const option of response.options ?? []) {
|
|
224
|
-
const add = {
|
|
225
|
-
texte: () => builder.addStringOption(item => {
|
|
226
|
-
item.setName(option.name).setDescription(option.description).setRequired(option.required);
|
|
227
|
-
for (const choice of option.choices ?? [])
|
|
228
|
-
if (typeof choice.value === "string")
|
|
229
|
-
item.addChoices({ name: choice.name, value: choice.value });
|
|
230
|
-
return item;
|
|
231
|
-
}),
|
|
232
|
-
nombre: () => builder.addNumberOption(item => {
|
|
233
|
-
item.setName(option.name).setDescription(option.description).setRequired(option.required);
|
|
234
|
-
if (option.minValue !== undefined)
|
|
235
|
-
item.setMinValue(option.minValue);
|
|
236
|
-
if (option.maxValue !== undefined)
|
|
237
|
-
item.setMaxValue(option.maxValue);
|
|
238
|
-
for (const choice of option.choices ?? [])
|
|
239
|
-
if (typeof choice.value === "number")
|
|
240
|
-
item.addChoices({ name: choice.name, value: choice.value });
|
|
241
|
-
return item;
|
|
242
|
-
}),
|
|
243
|
-
entier: () => builder.addIntegerOption(item => {
|
|
244
|
-
item.setName(option.name).setDescription(option.description).setRequired(option.required);
|
|
245
|
-
if (option.minValue !== undefined)
|
|
246
|
-
item.setMinValue(option.minValue);
|
|
247
|
-
if (option.maxValue !== undefined)
|
|
248
|
-
item.setMaxValue(option.maxValue);
|
|
249
|
-
for (const choice of option.choices ?? [])
|
|
250
|
-
if (typeof choice.value === "number")
|
|
251
|
-
item.addChoices({ name: choice.name, value: choice.value });
|
|
252
|
-
return item;
|
|
253
|
-
}),
|
|
254
|
-
booleen: () => builder.addBooleanOption(item => item.setName(option.name).setDescription(option.description).setRequired(option.required)),
|
|
255
|
-
salon: () => builder.addChannelOption(item => item.setName(option.name).setDescription(option.description).setRequired(option.required))
|
|
256
|
-
}[option.type];
|
|
257
|
-
add();
|
|
258
|
-
}
|
|
259
|
-
return builder.toJSON();
|
|
260
|
-
});
|
|
261
|
-
const manager = client.application?.commands;
|
|
262
|
-
if (!manager)
|
|
263
|
-
throw new Error("L'application Discord n'est pas disponible.");
|
|
264
|
-
const existing = await manager.fetch();
|
|
265
|
-
for (const command of commands) {
|
|
266
|
-
const registered = existing.find(item => item.name === command.name);
|
|
267
|
-
if (registered)
|
|
268
|
-
await manager.edit(registered.id, command);
|
|
269
|
-
else
|
|
270
|
-
await manager.create(command);
|
|
271
|
-
}
|
|
272
|
-
this.output("Connecté à Discord en tant que " + (client.user?.tag ?? "bot") + ".");
|
|
273
|
-
this.output("Commandes slash FBP enregistrées : " + commands.length + ".");
|
|
274
|
-
}
|
|
275
|
-
catch (error) {
|
|
276
|
-
this.output("Enregistrement des commandes slash impossible : " + (error instanceof Error ? error.message : String(error)));
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
client.on("interactionCreate", async (interaction) => {
|
|
280
|
-
if (!interaction.isChatInputCommand())
|
|
281
|
-
return;
|
|
282
|
-
const response = this.discordCommands.get(interaction.commandName);
|
|
283
|
-
if (response === undefined)
|
|
284
|
-
return;
|
|
285
|
-
try {
|
|
286
|
-
const optionValues = new Map();
|
|
287
|
-
for (const option of response.options ?? []) {
|
|
288
|
-
let value = null;
|
|
289
|
-
if (option.type === "texte")
|
|
290
|
-
value = interaction.options.getString(option.name);
|
|
291
|
-
else if (option.type === "nombre") {
|
|
292
|
-
const number = interaction.options.getNumber(option.name);
|
|
293
|
-
value = number === null ? null : String(number);
|
|
294
|
-
}
|
|
295
|
-
else if (option.type === "entier") {
|
|
296
|
-
const number = interaction.options.getInteger(option.name);
|
|
297
|
-
value = number === null ? null : String(number);
|
|
298
|
-
}
|
|
299
|
-
else if (option.type === "booleen") {
|
|
300
|
-
const boolean = interaction.options.getBoolean(option.name);
|
|
301
|
-
value = boolean === null ? null : String(boolean);
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
const channel = interaction.options.getChannel(option.name);
|
|
305
|
-
value = channel === null ? null : "#" + channel.name;
|
|
306
|
-
}
|
|
307
|
-
if (value === null && option.required) {
|
|
308
|
-
await interaction.reply({ content: "Option obligatoire indisponible : " + option.name, ephemeral: true });
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
if (value !== null)
|
|
312
|
-
optionValues.set(option.name, value);
|
|
313
|
-
}
|
|
314
|
-
if (response.type === "text") {
|
|
315
|
-
const content = response.content.replace(/\{\{([a-z0-9_-]{1,32})\}\}/g, (match, name) => optionValues.has(name) ? optionValues.get(name) : match);
|
|
316
|
-
await interaction.reply(content);
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
const embed = new discord_js_1.EmbedBuilder()
|
|
320
|
-
.setTitle(response.title)
|
|
321
|
-
.setDescription(response.description)
|
|
322
|
-
.setColor(Number.parseInt(response.color.slice(1), 16));
|
|
323
|
-
const guild = interaction.guild;
|
|
324
|
-
const selectedUser = response.userOption ? interaction.options.getUser(response.userOption.name) : null;
|
|
325
|
-
const selectedMember = selectedUser && guild ? await guild.members.fetch(selectedUser.id).catch(() => null) : null;
|
|
326
|
-
if (response.userOption && selectedUser)
|
|
327
|
-
optionValues.set(response.userOption.name, selectedMember?.displayName ?? selectedUser.username);
|
|
328
|
-
const fields = response.fields.map(field => ({
|
|
329
|
-
name: field.name,
|
|
330
|
-
value: field.dynamic ? this.resolveDiscordValue(field.value, interaction, selectedUser, selectedMember, optionValues) : field.value.replace(/\{\{([a-z0-9_-]{1,32})\}\}/g, (match, name) => optionValues.get(name) ?? match),
|
|
331
|
-
inline: false
|
|
332
|
-
}));
|
|
333
|
-
if (fields.length)
|
|
334
|
-
embed.addFields(fields);
|
|
335
|
-
await interaction.reply({ embeds: [embed] });
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
const message = "Une erreur est survenue pendant l'exécution de cette commande.";
|
|
340
|
-
this.output("Erreur Discord : " + (error instanceof Error ? error.message : String(error)));
|
|
341
|
-
if (!interaction.replied && !interaction.deferred) {
|
|
342
|
-
await interaction.reply({ content: message, ephemeral: true }).catch(() => undefined);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
void client.login(token).catch((error) => this.output("Connexion Discord impossible : " + (error instanceof Error ? error.message : String(error))));
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (name === "discordCommande") {
|
|
350
|
-
if (!this.discordClient)
|
|
351
|
-
throw new errors_1.FbpError("Appelle discordConnexion() avant de déclarer une commande.", undefined, line);
|
|
352
|
-
if (values.length !== 2 || typeof values[0] !== "string" || typeof values[1] !== "string") {
|
|
353
|
-
throw new errors_1.FbpError("Utilisation : discordCommande(\"ping\", \"Pong !\").", undefined, line);
|
|
354
|
-
}
|
|
355
|
-
const commandName = values[0];
|
|
356
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(commandName)) {
|
|
357
|
-
throw new errors_1.FbpError("Le nom d'une commande slash doit contenir 1 à 32 caractères : lettres minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
358
|
-
}
|
|
359
|
-
this.discordCommands.set(commandName, { type: "text", content: values[1], options: [] });
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
if (name === "discordCommandeEmbed") {
|
|
363
|
-
if (!this.discordClient)
|
|
364
|
-
throw new errors_1.FbpError("Appelle discordConnexion() avant de déclarer une commande.", undefined, line);
|
|
365
|
-
if (values.length !== 4 || values.some(value => typeof value !== "string")) {
|
|
366
|
-
throw new errors_1.FbpError("Utilisation : discordCommandeEmbed(nom, titre, description, couleur).", undefined, line);
|
|
367
|
-
}
|
|
368
|
-
const [commandName, title, description, color] = values;
|
|
369
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(commandName))
|
|
370
|
-
throw new errors_1.FbpError("Le nom d'une commande slash doit contenir 1 à 32 caractères : lettres minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
371
|
-
if (!/^#[0-9a-fA-F]{6}$/.test(color))
|
|
372
|
-
throw new errors_1.FbpError("La couleur d'un embed doit être au format hexadécimal, par exemple #5865F2.", undefined, line);
|
|
373
|
-
this.discordCommands.set(commandName, { type: "embed", title, description, color, fields: [], options: [] });
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
if (name === "discordChampDynamique") {
|
|
377
|
-
if (values.length !== 3 || values.some(value => typeof value !== "string")) {
|
|
378
|
-
throw new errors_1.FbpError("Utilisation : discordChampDynamique(nomCommande, nomChamp, source).", undefined, line);
|
|
379
|
-
}
|
|
380
|
-
const [commandName, fieldName, source] = values;
|
|
381
|
-
const command = this.discordCommands.get(commandName);
|
|
382
|
-
if (!command || command.type !== "embed")
|
|
383
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas un embed déclaré.", undefined, line);
|
|
384
|
-
if (command.fields.length >= 25)
|
|
385
|
-
throw new errors_1.FbpError("Un embed Discord ne peut pas contenir plus de 25 champs.", undefined, line);
|
|
386
|
-
if (!fieldName.trim() || !source.trim() || fieldName.length > 256 || source.length > 1024)
|
|
387
|
-
throw new errors_1.FbpError("Un champ dynamique doit avoir un nom et une source valides.", undefined, line);
|
|
388
|
-
command.fields.push({ name: fieldName, value: source, dynamic: true });
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (name === "discordChoixTexte" || name === "discordChoixEntier") {
|
|
392
|
-
if (values.length !== 4 || values.some(value => typeof value !== "string" && typeof value !== "number")) {
|
|
393
|
-
throw new errors_1.FbpError("Utilisation : discordChoixTexte(nomCommande, nomOption, nomChoix, valeur) ou discordChoixEntier(...).", undefined, line);
|
|
394
|
-
}
|
|
395
|
-
const [commandName, optionName, choiceName, choiceValue] = values;
|
|
396
|
-
if (typeof commandName !== "string" || typeof optionName !== "string" || typeof choiceName !== "string") {
|
|
397
|
-
throw new errors_1.FbpError("Le nom de la commande, de l'option et du choix doivent être du texte.", undefined, line);
|
|
398
|
-
}
|
|
399
|
-
const command = this.discordCommands.get(commandName);
|
|
400
|
-
if (!command)
|
|
401
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
402
|
-
const option = (command.options ?? []).find(item => item.name === optionName);
|
|
403
|
-
const expectedType = name === "discordChoixTexte" ? "texte" : "entier";
|
|
404
|
-
if (!option || option.type !== expectedType)
|
|
405
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » n'existe pas ou n'a pas le bon type.", undefined, line);
|
|
406
|
-
if (!choiceName.trim() || choiceName.length > 100)
|
|
407
|
-
throw new errors_1.FbpError("Le nom d'un choix doit contenir de 1 à 100 caractères.", undefined, line);
|
|
408
|
-
if (expectedType === "texte" && typeof choiceValue !== "string")
|
|
409
|
-
throw new errors_1.FbpError("La valeur d'un choix texte doit être du texte.", undefined, line);
|
|
410
|
-
if (expectedType === "entier" && (typeof choiceValue !== "number" || !Number.isInteger(choiceValue)))
|
|
411
|
-
throw new errors_1.FbpError("La valeur d'un choix entier doit être un nombre entier.", undefined, line);
|
|
412
|
-
if (option.minValue !== undefined || option.maxValue !== undefined)
|
|
413
|
-
throw new errors_1.FbpError("Une option avec des limites numériques ne peut pas recevoir de choix prédéfinis.", undefined, line);
|
|
414
|
-
const choices = option.choices ?? (option.choices = []);
|
|
415
|
-
if (choices.some(choice => choice.name === choiceName || choice.value === choiceValue))
|
|
416
|
-
throw new errors_1.FbpError("Ce nom ou cette valeur de choix existe déjà.", undefined, line);
|
|
417
|
-
if (choices.length >= 25)
|
|
418
|
-
throw new errors_1.FbpError("Une option Discord ne peut pas contenir plus de 25 choix.", undefined, line);
|
|
419
|
-
choices.push({ name: choiceName, value: choiceValue });
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (name === "discordOptionNombreLimites" || name === "discordOptionEntierLimites") {
|
|
423
|
-
if (values.length !== 4 || typeof values[0] !== "string" || typeof values[1] !== "string" || typeof values[2] !== "number" || typeof values[3] !== "number") {
|
|
424
|
-
throw new errors_1.FbpError("Utilisation : discordOptionNombreLimites(commande, option, minimum, maximum) ou discordOptionEntierLimites(...).", undefined, line);
|
|
425
|
-
}
|
|
426
|
-
const [commandName, optionName, minimum, maximum] = values;
|
|
427
|
-
const command = this.discordCommands.get(commandName);
|
|
428
|
-
if (!command)
|
|
429
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
430
|
-
const expectedType = name === "discordOptionNombreLimites" ? "nombre" : "entier";
|
|
431
|
-
const option = (command.options ?? []).find(item => item.name === optionName);
|
|
432
|
-
if (!option || option.type !== expectedType)
|
|
433
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » n'existe pas ou n'a pas le type numérique attendu.", undefined, line);
|
|
434
|
-
if (!Number.isFinite(minimum) || !Number.isFinite(maximum) || minimum > maximum || (expectedType === "entier" && (!Number.isInteger(minimum) || !Number.isInteger(maximum)))) {
|
|
435
|
-
throw new errors_1.FbpError("Les limites doivent être des nombres valides, minimum inférieur ou égal au maximum ; elles doivent être entières pour une option entière.", undefined, line);
|
|
436
|
-
}
|
|
437
|
-
if (option.choices?.length)
|
|
438
|
-
throw new errors_1.FbpError("Une option avec des choix prédéfinis ne peut pas recevoir de limites numériques.", undefined, line);
|
|
439
|
-
option.minValue = minimum;
|
|
440
|
-
option.maxValue = maximum;
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
if (name === "discordOptionTexte" || name === "discordOptionNombre" || name === "discordOptionEntier" || name === "discordOptionBooleen" || name === "discordOptionSalon") {
|
|
444
|
-
if (values.length < 3 || values.length > 4 || values.some(value => typeof value !== "string" && typeof value !== "boolean")) {
|
|
445
|
-
throw new errors_1.FbpError("Utilisation : discordOptionTexte/Nombre/Entier/Booleen/Salon(nomCommande, nomOption, description, obligatoire).", undefined, line);
|
|
446
|
-
}
|
|
447
|
-
const [commandName, optionName, description] = values;
|
|
448
|
-
const required = values.length === 4 ? values[3] : false;
|
|
449
|
-
if (typeof commandName !== "string" || typeof optionName !== "string" || typeof description !== "string" || typeof required !== "boolean") {
|
|
450
|
-
throw new errors_1.FbpError("Les trois premiers arguments doivent être du texte et le quatrième, facultatif, doit être true ou false.", undefined, line);
|
|
451
|
-
}
|
|
452
|
-
const command = this.discordCommands.get(commandName);
|
|
453
|
-
if (!command)
|
|
454
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
455
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(optionName))
|
|
456
|
-
throw new errors_1.FbpError("Le nom de l'option doit contenir 1 à 32 caractères minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
457
|
-
if (!description.trim() || description.length > 100)
|
|
458
|
-
throw new errors_1.FbpError("La description de l'option doit contenir 1 à 100 caractères.", undefined, line);
|
|
459
|
-
const options = command.options ?? (command.options = []);
|
|
460
|
-
if (options.some(option => option.name === optionName) || (command.type === "embed" && command.userOption?.name === optionName))
|
|
461
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » existe déjà pour cette commande.", undefined, line);
|
|
462
|
-
if (options.length + (command.type === "embed" && command.userOption ? 1 : 0) >= 25)
|
|
463
|
-
throw new errors_1.FbpError("Une commande slash Discord ne peut pas contenir plus de 25 options.", undefined, line);
|
|
464
|
-
const type = name === "discordOptionTexte" ? "texte" : name === "discordOptionNombre" ? "nombre" : name === "discordOptionEntier" ? "entier" : name === "discordOptionBooleen" ? "booleen" : "salon";
|
|
465
|
-
options.push({ name: optionName, description, type, required });
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
if (name === "discordOptionUtilisateur") {
|
|
469
|
-
if (values.length !== 3 || values.some(value => typeof value !== "string")) {
|
|
470
|
-
throw new errors_1.FbpError("Utilisation : discordOptionUtilisateur(nomCommande, nomOption, description).", undefined, line);
|
|
471
|
-
}
|
|
472
|
-
const [commandName, optionName, description] = values;
|
|
473
|
-
const command = this.discordCommands.get(commandName);
|
|
474
|
-
if (!command || command.type !== "embed")
|
|
475
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas un embed déclaré.", undefined, line);
|
|
476
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(optionName))
|
|
477
|
-
throw new errors_1.FbpError("Le nom de l'option doit contenir 1 à 32 caractères minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
478
|
-
if (!description.trim() || description.length > 100)
|
|
479
|
-
throw new errors_1.FbpError("La description de l'option doit contenir 1 à 100 caractères.", undefined, line);
|
|
480
|
-
const options = command.options ?? (command.options = []);
|
|
481
|
-
if (options.some(option => option.name === optionName) || command.userOption?.name === optionName)
|
|
482
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » existe déjà pour cette commande.", undefined, line);
|
|
483
|
-
if (options.length + 1 > 25)
|
|
484
|
-
throw new errors_1.FbpError("Une commande slash Discord ne peut pas contenir plus de 25 options.", undefined, line);
|
|
485
|
-
command.userOption = { name: optionName, description };
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
if (name === "discordChamp") {
|
|
489
|
-
if (values.length !== 3 || values.some(value => typeof value !== "string")) {
|
|
490
|
-
throw new errors_1.FbpError("Utilisation : discordChamp(nomCommande, nomChamp, valeur).", undefined, line);
|
|
491
|
-
}
|
|
492
|
-
const [commandName, fieldName, fieldValue] = values;
|
|
493
|
-
const command = this.discordCommands.get(commandName);
|
|
494
|
-
if (!command || command.type !== "embed")
|
|
495
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas un embed déclaré.", undefined, line);
|
|
496
|
-
if (command.fields.length >= 25)
|
|
497
|
-
throw new errors_1.FbpError("Un embed Discord ne peut pas contenir plus de 25 champs.", undefined, line);
|
|
498
|
-
if (!fieldName.trim() || !fieldValue.trim() || fieldName.length > 256 || fieldValue.length > 1024)
|
|
499
|
-
throw new errors_1.FbpError("Un champ doit avoir un nom (1 à 256 caractères) et une valeur (1 à 1024 caractères).", undefined, line);
|
|
500
|
-
command.fields.push({ name: fieldName, value: fieldValue });
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
if (name === "ping") {
|
|
504
|
-
this.output("FBP fonctionne. Cette commande ne vérifie pas à elle seule une connexion Discord.");
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
const fn = this.functions.get(name);
|
|
508
|
-
if (!fn)
|
|
509
|
-
throw new errors_1.FbpError(`Fonction « ${name} » inconnue. Vérifie son nom ou son import.`, undefined, line);
|
|
510
|
-
this.execute(fn.body, this.imports.has(name) ? node_path_1.default.dirname(this.imports.get(name)) : baseDir);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
exports.FbpCompiler = FbpCompiler;
|
package/dist/errors.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.FbpError = void 0;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
class FbpError extends Error {
|
|
9
|
-
file;
|
|
10
|
-
line;
|
|
11
|
-
constructor(message, file, line) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.file = file;
|
|
14
|
-
this.line = line;
|
|
15
|
-
this.name = "FbpError";
|
|
16
|
-
}
|
|
17
|
-
format() {
|
|
18
|
-
const location = this.file
|
|
19
|
-
? `${node_path_1.default.relative(process.cwd(), this.file)}${this.line ? `:${this.line}` : ""}: `
|
|
20
|
-
: "";
|
|
21
|
-
return `Erreur FBP : ${location}${this.message}`;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
exports.FbpError = FbpError;
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "FBP Colors",
|
|
3
|
-
"type": "dark",
|
|
4
|
-
"tokenColors": [
|
|
5
|
-
{
|
|
6
|
-
"scope": [
|
|
7
|
-
"keyword.control.fbp"
|
|
8
|
-
],
|
|
9
|
-
"settings": {
|
|
10
|
-
"foreground": "#C792EA",
|
|
11
|
-
"fontStyle": "bold"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"scope": [
|
|
16
|
-
"entity.name.function.fbp"
|
|
17
|
-
],
|
|
18
|
-
"settings": {
|
|
19
|
-
"foreground": "#82AAFF",
|
|
20
|
-
"fontStyle": "bold"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"scope": [
|
|
25
|
-
"support.function.fbp"
|
|
26
|
-
],
|
|
27
|
-
"settings": {
|
|
28
|
-
"foreground": "#89DDFF"
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"scope": [
|
|
33
|
-
"string.quoted.double.fbp"
|
|
34
|
-
],
|
|
35
|
-
"settings": {
|
|
36
|
-
"foreground": "#C3E88D"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"scope": [
|
|
41
|
-
"constant.numeric.fbp"
|
|
42
|
-
],
|
|
43
|
-
"settings": {
|
|
44
|
-
"foreground": "#F78C6C"
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"scope": [
|
|
49
|
-
"constant.language.boolean.fbp"
|
|
50
|
-
],
|
|
51
|
-
"settings": {
|
|
52
|
-
"foreground": "#FF9CAC",
|
|
53
|
-
"fontStyle": "bold"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"scope": [
|
|
58
|
-
"variable.other.assignment.fbp"
|
|
59
|
-
],
|
|
60
|
-
"settings": {
|
|
61
|
-
"foreground": "#F4B860"
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"scope": [
|
|
66
|
-
"keyword.operator.fbp"
|
|
67
|
-
],
|
|
68
|
-
"settings": {
|
|
69
|
-
"foreground": "#89DDFF"
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"scope": [
|
|
74
|
-
"punctuation.section.fbp"
|
|
75
|
-
],
|
|
76
|
-
"settings": {
|
|
77
|
-
"foreground": "#AAB3C5"
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"scope": [
|
|
82
|
-
"constant.character.escape.fbp"
|
|
83
|
-
],
|
|
84
|
-
"settings": {
|
|
85
|
-
"foreground": "#FFCB6B"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 128">
|
|
2
|
-
<title>FBP file icon</title>
|
|
3
|
-
<g fill="#718DB5" font-family="Arial, Helvetica, sans-serif" font-size="88" font-weight="600" text-anchor="middle">
|
|
4
|
-
<text x="48" y="94">{</text>
|
|
5
|
-
<text x="128" y="94">B</text>
|
|
6
|
-
<text x="208" y="94">}</text>
|
|
7
|
-
</g>
|
|
8
|
-
</svg>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"brackets": [
|
|
3
|
-
["{", "}"],
|
|
4
|
-
["(", ")"],
|
|
5
|
-
["[", "]"]
|
|
6
|
-
],
|
|
7
|
-
"autoClosingPairs": [
|
|
8
|
-
["{", "}"],
|
|
9
|
-
["(", ")"],
|
|
10
|
-
["[", "]"],
|
|
11
|
-
["\"", "\""],
|
|
12
|
-
["'", "'"]
|
|
13
|
-
],
|
|
14
|
-
"surroundingPairs": [
|
|
15
|
-
["{", "}"],
|
|
16
|
-
["(", ")"],
|
|
17
|
-
["[", "]"],
|
|
18
|
-
["\"", "\""],
|
|
19
|
-
["'", "'"]
|
|
20
|
-
]
|
|
21
|
-
}
|