fbp-language 0.0.0-stage → 1.0.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/README.md CHANGED
@@ -1,3 +1,104 @@
1
- # Temporary Holding Version
1
+ # FBP — French Bot Programming
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ FBP est un langage interprété par Node.js. Les fichiers source utilisent l'extension `.fbp`. Le paquet fournit le moteur et la commande `fbp`.
4
+
5
+ ## Prérequis
6
+
7
+ - Node.js 20 ou plus récent
8
+ - npm
9
+
10
+ ## Installation depuis npm
11
+
12
+ Quand le paquet sera publié sur npm :
13
+
14
+ ```bash
15
+ npm install fbp-language
16
+ ```
17
+
18
+ Puis, dans le `package.json` de ton projet :
19
+
20
+ ```json
21
+ {
22
+ "scripts": {
23
+ "start": "fbp run lancer"
24
+ },
25
+ "dependencies": {
26
+ "fbp-language": "^1.0.0"
27
+ }
28
+ }
29
+ ```
30
+
31
+ Lance le script avec `npm start`. npm rend la commande `fbp` disponible dans les scripts du projet.
32
+
33
+ ## Créer un projet FBP
34
+
35
+ Crée un fichier `commandes.fbp` à la racine de ton projet. Malgré son extension, ce fichier est actuellement du JSON :
36
+
37
+ ```json
38
+ {
39
+ "lancer": "scripts/main.fbp"
40
+ }
41
+ ```
42
+
43
+ Crée ensuite le fichier `scripts/main.fbp` :
44
+
45
+ ```fbp
46
+ nom = "Mon projet"
47
+
48
+ lance() {
49
+ affiche("FBP fonctionne !")
50
+ affiche("Projet :", nom)
51
+ }
52
+
53
+ lance()
54
+ ```
55
+
56
+ Les chemins déclarés dans `commandes.fbp` sont relatifs à la racine du projet.
57
+
58
+ ## Commandes CLI
59
+
60
+ ```bash
61
+ fbp help
62
+ fbp version
63
+ fbp info
64
+ fbp ping
65
+ fbp run lancer
66
+ fbp run-file scripts/main.fbp
67
+ ```
68
+
69
+ - `fbp run <nom>` exécute la commande correspondante dans le fichier `commandes.fbp` du dossier courant.
70
+ - `fbp run-file <fichier>` exécute directement un fichier FBP.
71
+ - `fbp ping` vérifie que le CLI répond ; il ne se connecte pas à Discord.
72
+
73
+ ## Syntaxe actuelle
74
+
75
+ ```fbp
76
+ nom = "Gab"
77
+ niveau = 15
78
+ actif = true
79
+
80
+ lance() {
81
+ affiche("Bonjour", nom)
82
+ }
83
+
84
+ lance()
85
+ ```
86
+
87
+ - Types reconnus : texte, nombre et booléen.
88
+ - Les fonctions ne prennent pas encore de paramètres.
89
+ - `affiche(...)` écrit dans le terminal.
90
+ - `import { fonction } de "./fichier.fbp"` importe des fonctions.
91
+ - Les commentaires commencent par `//` ou `#`.
92
+
93
+ ## Limites actuelles
94
+
95
+ FBP est encore un interpréteur de base. Les paramètres de fonction, les conditions, les boucles, les valeurs de retour et les expressions arithmétiques ne sont pas encore implémentés. La connexion à Discord n'est pas incluse dans le moteur.
96
+
97
+ ## Développement du dépôt
98
+
99
+ ```bash
100
+ npm install
101
+ npm test
102
+ ```
103
+
104
+ Les fichiers d'essai internes se trouvent dans `tests/fixtures`. Ils ne font pas partie du paquet publié.
package/dist/cli.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const compiler_1 = require("./compiler");
10
+ const errors_1 = require("./errors");
11
+ const VERSION = "1.0.0";
12
+ function help() {
13
+ console.log(`FBP — French Bot Programming
14
+
15
+ Utilisation :
16
+ fbp help Affiche cette aide
17
+ fbp version Affiche la version de FBP
18
+ fbp info Affiche les informations du moteur
19
+ fbp ping Vérifie que le CLI répond
20
+ fbp run <commande> Exécute une commande de commandes.fbp
21
+ fbp run-file <fichier> Exécute directement un fichier .fbp`);
22
+ }
23
+ function ping() {
24
+ console.log("FBP : opérationnel");
25
+ console.log(`Version : ${VERSION}`);
26
+ console.log(`Node.js : ${process.version}`);
27
+ console.log("Bot : aucune connexion externe n'est configurée par le CLI seul.");
28
+ }
29
+ function runCommand(name) {
30
+ const configPath = node_path_1.default.resolve("commandes.fbp");
31
+ if (!node_fs_1.default.existsSync(configPath)) {
32
+ throw new errors_1.FbpError("Fichier commandes.fbp introuvable à la racine du projet. Crée ce fichier pour définir tes commandes.");
33
+ }
34
+ let commands;
35
+ try {
36
+ commands = JSON.parse(node_fs_1.default.readFileSync(configPath, "utf8"));
37
+ }
38
+ catch {
39
+ throw new errors_1.FbpError("commandes.fbp doit contenir un objet JSON valide associant chaque commande à un fichier .fbp.");
40
+ }
41
+ if (!commands || typeof commands !== "object" || Array.isArray(commands)) {
42
+ throw new errors_1.FbpError("commandes.fbp doit être un objet de commandes.");
43
+ }
44
+ const target = commands[name];
45
+ if (typeof target !== "string" || !target.trim()) {
46
+ const available = Object.keys(commands);
47
+ throw new errors_1.FbpError(`Commande « ${name} » inconnue.${available.length ? ` Commandes disponibles : ${available.join(", ")}.` : " Aucune commande n'est définie."}`);
48
+ }
49
+ new compiler_1.FbpCompiler().runFile(node_path_1.default.resolve(node_path_1.default.dirname(configPath), target));
50
+ }
51
+ function main() {
52
+ const [command, ...args] = process.argv.slice(2);
53
+ try {
54
+ switch (command) {
55
+ case "help":
56
+ case "--help":
57
+ case "-h":
58
+ case undefined:
59
+ help();
60
+ break;
61
+ case "version":
62
+ case "--version":
63
+ case "-v":
64
+ console.log(`FBP ${VERSION}`);
65
+ break;
66
+ case "info":
67
+ console.log("FBP — French Bot Programming");
68
+ console.log("Moteur : TypeScript / Node.js");
69
+ console.log("Fichiers source : .fbp");
70
+ break;
71
+ case "ping":
72
+ ping();
73
+ break;
74
+ case "run":
75
+ if (!args[0])
76
+ throw new errors_1.FbpError("Indique le nom de la commande. Exemple : fbp run lancer");
77
+ runCommand(args[0]);
78
+ break;
79
+ case "run-file":
80
+ if (!args[0])
81
+ throw new errors_1.FbpError("Indique un fichier. Exemple : fbp run-file bot/main.fbp");
82
+ new compiler_1.FbpCompiler().runFile(args[0]);
83
+ break;
84
+ default:
85
+ throw new errors_1.FbpError(`Commande CLI inconnue : « ${command} ». Utilise « fbp help » pour afficher l'aide.`);
86
+ }
87
+ }
88
+ catch (error) {
89
+ if (error instanceof errors_1.FbpError) {
90
+ console.error(error.format());
91
+ process.exitCode = 1;
92
+ }
93
+ else {
94
+ console.error("Erreur FBP : une erreur inattendue est survenue.");
95
+ console.error(error instanceof Error ? error.message : String(error));
96
+ process.exitCode = 1;
97
+ }
98
+ }
99
+ }
100
+ main();
@@ -0,0 +1,186 @@
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
+ function valueType(value) {
11
+ const v = value.trim();
12
+ if (/^"(?:[^"\\]|\\.)*"$/.test(v) || /^'(?:[^'\\]|\\.)*'$/.test(v))
13
+ return "texte";
14
+ if (/^-?\d+(?:\.\d+)?$/.test(v))
15
+ return "nombre";
16
+ if (v === "true" || v === "false")
17
+ return "booleen";
18
+ return "inconnu";
19
+ }
20
+ function splitArguments(raw) {
21
+ const result = [];
22
+ let current = "";
23
+ let quote = "";
24
+ let depth = 0;
25
+ for (const char of raw) {
26
+ if (quote) {
27
+ current += char;
28
+ if (char === quote)
29
+ quote = "";
30
+ }
31
+ else if (char === '"' || char === "'") {
32
+ quote = char;
33
+ current += char;
34
+ }
35
+ else if (char === "(") {
36
+ depth++;
37
+ current += char;
38
+ }
39
+ else if (char === ")") {
40
+ depth--;
41
+ current += char;
42
+ }
43
+ else if (char === "," && depth === 0) {
44
+ if (current.trim())
45
+ result.push(current.trim());
46
+ current = "";
47
+ }
48
+ else {
49
+ current += char;
50
+ }
51
+ }
52
+ if (current.trim())
53
+ result.push(current.trim());
54
+ return result;
55
+ }
56
+ function parseFile(file, source) {
57
+ const lines = source.split(/\r?\n/);
58
+ const root = [];
59
+ const stack = [];
60
+ const target = () => stack.length ? stack[stack.length - 1].body : root;
61
+ for (let index = 0; index < lines.length; index++) {
62
+ const raw = lines[index];
63
+ const line = raw.trim();
64
+ const lineNo = index + 1;
65
+ if (!line || line.startsWith("//") || line.startsWith("#"))
66
+ continue;
67
+ const importMatch = line.match(/^import\s*\{\s*([^}]+)\s*\}\s*de\s*(["'])(.+?)\2\s*$/);
68
+ if (importMatch) {
69
+ target().push({ kind: "import", names: importMatch[1].split(",").map(x => x.trim()), source: importMatch[3], line: lineNo });
70
+ continue;
71
+ }
72
+ const functionMatch = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*\(\s*\)\s*\{$/u);
73
+ if (functionMatch) {
74
+ const entry = { name: functionMatch[1], line: lineNo, body: [] };
75
+ target().push({ kind: "function", ...entry });
76
+ stack.push(entry);
77
+ continue;
78
+ }
79
+ if (line === "}") {
80
+ if (!stack.length)
81
+ throw new errors_1.FbpError("Accolade fermante sans fonction correspondante.", file, lineNo);
82
+ stack.pop();
83
+ continue;
84
+ }
85
+ const assignment = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*=\s*(.+)$/u);
86
+ if (assignment) {
87
+ target().push({ kind: "assign", name: assignment[1], value: assignment[2].trim(), line: lineNo });
88
+ continue;
89
+ }
90
+ const call = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*\((.*)\)\s*;?$/u);
91
+ if (call) {
92
+ target().push({ kind: "call", name: call[1], args: splitArguments(call[2]), line: lineNo });
93
+ continue;
94
+ }
95
+ throw new errors_1.FbpError(`Syntaxe non reconnue : « ${line} ». Vérifie les parenthèses, les accolades et le signe =.`, file, lineNo);
96
+ }
97
+ if (stack.length) {
98
+ const open = stack[stack.length - 1];
99
+ throw new errors_1.FbpError(`La fonction « ${open.name} » n'est pas fermée par }.`, file, open.line);
100
+ }
101
+ return root;
102
+ }
103
+ class FbpCompiler {
104
+ variables = new Map();
105
+ functions = new Map();
106
+ imports = new Map();
107
+ output;
108
+ constructor(options = {}) {
109
+ this.output = options.onOutput ?? (value => console.log(value));
110
+ }
111
+ runFile(filePath) {
112
+ const absolute = node_path_1.default.resolve(filePath);
113
+ if (!node_fs_1.default.existsSync(absolute))
114
+ throw new errors_1.FbpError(`Fichier introuvable : ${absolute}`);
115
+ if (!node_fs_1.default.statSync(absolute).isFile())
116
+ throw new errors_1.FbpError(`Le chemin indiqué n'est pas un fichier : ${absolute}`);
117
+ this.execute(parseFile(absolute, node_fs_1.default.readFileSync(absolute, "utf8")), node_path_1.default.dirname(absolute));
118
+ }
119
+ execute(statements, baseDir) {
120
+ for (const statement of statements) {
121
+ if (statement.kind === "import") {
122
+ const targetFile = node_path_1.default.resolve(baseDir, statement.source);
123
+ if (!node_fs_1.default.existsSync(targetFile))
124
+ throw new errors_1.FbpError(`Fichier importé introuvable : ${statement.source}`);
125
+ const nested = parseFile(targetFile, node_fs_1.default.readFileSync(targetFile, "utf8"));
126
+ for (const item of nested) {
127
+ if (item.kind === "function")
128
+ this.functions.set(item.name, item);
129
+ }
130
+ for (const name of statement.names) {
131
+ if (!this.functions.has(name))
132
+ throw new errors_1.FbpError(`La fonction « ${name} » n'existe pas dans ${statement.source}.`);
133
+ this.imports.set(name, targetFile);
134
+ }
135
+ }
136
+ else if (statement.kind === "function") {
137
+ if (this.functions.has(statement.name))
138
+ throw new errors_1.FbpError(`La fonction « ${statement.name} » est définie plusieurs fois.`, undefined, statement.line);
139
+ this.functions.set(statement.name, statement);
140
+ }
141
+ else if (statement.kind === "assign") {
142
+ const type = valueType(statement.value);
143
+ const previous = this.variables.get(statement.name);
144
+ if (previous && type !== "inconnu" && previous.type !== "inconnu" && previous.type !== type) {
145
+ throw new errors_1.FbpError(`Type incompatible pour « ${statement.name} » : attendu ${previous.type}, reçu ${type}.`, undefined, statement.line);
146
+ }
147
+ const value = this.evaluate(statement.value, statement.line);
148
+ this.variables.set(statement.name, { type: previous?.type ?? type, value });
149
+ }
150
+ else {
151
+ this.call(statement.name, statement.args, statement.line, baseDir);
152
+ }
153
+ }
154
+ }
155
+ evaluate(expression, line) {
156
+ const value = expression.trim();
157
+ if (value === "true")
158
+ return true;
159
+ if (value === "false")
160
+ return false;
161
+ if (/^-?\d+(?:\.\d+)?$/.test(value))
162
+ return Number(value);
163
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
164
+ return value.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\(["'\\])/g, "$1");
165
+ }
166
+ if (this.variables.has(value))
167
+ return this.variables.get(value).value;
168
+ throw new errors_1.FbpError(`Valeur inconnue « ${value} ». Utilise un texte, un nombre, true, false ou une variable déclarée.`, undefined, line);
169
+ }
170
+ call(name, args, line, baseDir) {
171
+ const values = args.map(arg => this.evaluate(arg, line));
172
+ if (name === "affiche") {
173
+ this.output(values.map(value => value === undefined ? "" : String(value)).join(" "));
174
+ return;
175
+ }
176
+ if (name === "ping") {
177
+ this.output("FBP fonctionne. Cette commande ne vérifie pas à elle seule une connexion Discord.");
178
+ return;
179
+ }
180
+ const fn = this.functions.get(name);
181
+ if (!fn)
182
+ throw new errors_1.FbpError(`Fonction « ${name} » inconnue. Vérifie son nom ou son import.`, undefined, line);
183
+ this.execute(fn.body, this.imports.has(name) ? node_path_1.default.dirname(this.imports.get(name)) : baseDir);
184
+ }
185
+ }
186
+ exports.FbpCompiler = FbpCompiler;
package/dist/errors.js ADDED
@@ -0,0 +1,24 @@
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;
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "fbp-language",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "displayName": "FBP - French Bot Programming",
4
+ "description": "Interpréteur et CLI du langage FBP.",
5
+ "version": "1.0.0",
6
+ "type": "commonjs",
7
+ "main": "dist/compiler.js",
8
+ "bin": {
9
+ "fbp": "dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist/",
13
+ "README.md"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/GabyY-1/French-Bot-Programming.git"
18
+ },
19
+ "homepage": "https://github.com/GabyY-1/French-Bot-Programming#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/GabyY-1/French-Bot-Programming/issues"
22
+ },
23
+ "keywords": [
24
+ "fbp",
25
+ "language",
26
+ "interpreter",
27
+ "french",
28
+ "cli"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "prepare": "npm run build",
33
+ "prepublishOnly": "npm test",
34
+ "start": "node dist/cli.js",
35
+ "dev": "ts-node src/cli.ts",
36
+ "test": "npm run build && node --test tests/*.test.cjs"
37
+ },
38
+ "engines": {
39
+ "node": ">=20"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.10.0",
43
+ "ts-node": "^10.9.2",
44
+ "typescript": "^5.7.2"
45
+ }
46
+ }