fazer-lang 2.3.0 → 2.4.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
@@ -24,6 +24,19 @@ Exécuter un script :
24
24
  fazer mon_script.fz
25
25
  ```
26
26
 
27
+ ## Création d'Exécutable (.exe)
28
+
29
+ Transformez vos scripts Fazer en applications Windows portables et natives :
30
+
31
+ 1. (Optionnel) Placez une icône `app.ico` dans le dossier.
32
+ 2. Lancez la commande de build :
33
+ ```bash
34
+ fazer build mon_script.fz
35
+ ```
36
+ 3. Récupérez votre application dans `dist/mon_script/mon_script.exe`.
37
+
38
+ Le dossier généré est **portable** : zippez-le et envoyez-le à n'importe qui, aucune installation n'est requise !
39
+
27
40
  ## Documentation
28
41
 
29
42
  La documentation complète est disponible dans le dossier [`docs/`](./docs/README.md).
@@ -35,12 +48,13 @@ La documentation complète est disponible dans le dossier [`docs/`](./docs/READM
35
48
 
36
49
  ## Fonctionnalités Clés
37
50
 
51
+ * **GUI Native** : Créez de vraies applications Windows (WinForms) avec widgets natifs (`window`, `button`, `entry`, etc.).
38
52
  * **Pipe Operator (`->>`)** : Enchaînez les opérations proprement.
39
53
  * **Pattern Matching (`case`)** : Contrôle de flux expressif.
40
54
  * **Sécurité Intégrée** : Chiffrement AES-256-GCM et SHA256 natifs.
41
55
  * **Réseau & Web** : Client HTTP `fetch`, serveur web `server`, et module `discord`.
42
56
  * **Système de Fichiers** : Manipulation simple et puissante.
43
- * **Automation** : Gestion du presse-papier (`clipboard`), menus interactifs, et persistance (`db`).
57
+ * **Automation** : Gestion du presse-papier (`clipboard`), notifications (`notify`), et persistance (`db`).
44
58
  * **Extensible** : Système de modules `import`.
45
59
 
46
60
  ## Copyright
@@ -1,46 +1,153 @@
1
- # Guide de Démarrage
1
+ # Guide de Démarrage Fazer
2
+
3
+ Bienvenue dans Fazer, le langage de script puissant et moderne pour Windows.
2
4
 
3
5
  ## Installation
4
6
 
5
- La façon la plus simple d'installer Fazer est via NPM (Node Package Manager).
7
+ 1. Téléchargez la dernière version.
8
+ 2. Lancez `install_system.ps1` pour configurer le PATH et les associations de fichiers.
9
+ 3. Ouvrez un terminal et tapez `fazer` pour lancer le REPL.
6
10
 
7
- ```bash
8
- npm install -g fazer-lang
11
+ ## Votre premier script
12
+
13
+ Créez un fichier `hello.fz` :
14
+
15
+ ```fazer
16
+ "Bonjour Fazer !" ->> println
9
17
  ```
10
18
 
11
- Vérifiez l'installation :
19
+ Exécutez-le :
20
+ ```bash
21
+ fazer run hello.fz
22
+ ```
12
23
 
24
+ Ou simplement (si installé) :
13
25
  ```bash
14
- fazer --version
26
+ fazer hello.fz
15
27
  ```
16
28
 
17
- ## Votre Premier Script
29
+ ## Concepts de Base
18
30
 
19
- Créez un fichier nommé `hello.fz` avec le contenu suivant :
31
+ ### Variables et Fonctions
20
32
 
21
33
  ```fazer
22
- "Hello World!" ->> println
34
+ # Variable
35
+ nom := "Monde"
36
+
37
+ # Fonction
38
+ fn saluer(n) ->
39
+ "Bonjour " + n
40
+ end
41
+
42
+ saluer(nom) ->> println
23
43
  ```
24
44
 
25
- Exécutez-le via la ligne de commande :
45
+ ### Pipe (`->>`)
26
46
 
27
- ```bash
28
- fazer hello.fz
47
+ L'opérateur pipe passe le résultat de gauche comme premier argument à la fonction de droite.
48
+
49
+ ```fazer
50
+ " texte sale " ->> str_trim ->> str_upper ->> println
51
+ # Affiche : "TEXTE SALE"
29
52
  ```
30
53
 
31
- Vous devriez voir `Hello World!` s'afficher.
54
+ ## Bibliothèque Standard (Nouveau !)
55
+
56
+ Fazer dispose maintenant d'une bibliothèque standard riche pour les tâches réelles.
32
57
 
33
- ## Le Mode Interactif (REPL)
58
+ ### 1. Système de Fichiers
59
+ ```fazer
60
+ # Lire
61
+ contenu := fs_read("config.txt")
34
62
 
35
- Si vous lancez `fazer` sans arguments, vous entrez dans le mode interactif. C'est idéal pour tester des petites lignes de code.
63
+ # Écrire
64
+ fs_write("log.txt", "Succès")
65
+ ```
36
66
 
37
- ```bash
38
- $ fazer
39
- Welcome to Fazer v2.2.1
40
- > 10 ->> println
41
- 10
42
- > "test" ->> sha256 ->> println
43
- 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
67
+ ### 2. Modules (`import`)
68
+ Organisez votre code en plusieurs fichiers.
69
+
70
+ **math.fz** :
71
+ ```fazer
72
+ add := (a, b) => a + b
73
+ PI := 3.14159
74
+ ```
75
+
76
+ **main.fz** :
77
+ ```fazer
78
+ m := import("math.fz")
79
+ m.add(10, 5) ->> println
80
+ m.PI ->> println
44
81
  ```
45
82
 
46
- Tapez `exit` ou faites `Ctrl+C` pour quitter.
83
+ ### 3. Base de Données Native (`db`)
84
+ Persistance des données JSON simple et efficace.
85
+
86
+ ```fazer
87
+ # Initialiser (charge ou crée le fichier)
88
+ store := db("data.json")
89
+
90
+ # Sauvegarder une valeur
91
+ store.set("score", 100)
92
+ store.set("user", "viced")
93
+
94
+ # Lire une valeur
95
+ store.get("score") ->> println
96
+
97
+ # Récupérer tout
98
+ all := store.all()
99
+ ```
100
+
101
+ ### 4. Automation Système
102
+ Interagissez avec Windows.
103
+
104
+ ```fazer
105
+ # Presse-papier (Copier)
106
+ clipboard_set("Copié depuis Fazer !")
107
+
108
+ # Notifications
109
+ notify("Titre", "Ceci est une notification Windows native")
110
+
111
+ # Exécution de commandes
112
+ ip := exec("ipconfig")
113
+ ```
114
+
115
+ ### 5. GUI Native (Vrai Native)
116
+ Créez des applications Windows natives (WinForms) directement en Fazer.
117
+
118
+ ```fazer
119
+ # 1. Définir l'interface
120
+ window("Mon Application", 400, 300)
121
+ label("lbl_msg", "Bienvenue !", 20, 20, 360, 30)
122
+ button("btn_ok", "Cliquez-moi", 20, 60, 150, 40)
123
+ entry("txt_nom", "Votre nom", 20, 120, 360, 30)
124
+
125
+ # 2. Gestionnaire d'événements
126
+ fn handler(ev) ->
127
+ id = ev["id"]
128
+
129
+ if id == "btn_ok"
130
+ msgbox("Bouton cliqué !")
131
+ set_text("lbl_msg", "Merci !")
132
+ end
133
+
134
+ if id == "txt_nom"
135
+ # ev["value"] contient le texte tapé
136
+ print("Nom changé: " + ev["value"])
137
+ end
138
+ end
139
+
140
+ # 3. Lancer l'interface
141
+ gui(handler)
142
+ ```
143
+
144
+ ### 6. Serveur Web
145
+ Créez des API ou des sites web.
146
+
147
+ ```fazer
148
+ fn handler(req) ->
149
+ "Vous avez demandé : " + req.url
150
+ end
151
+
152
+ server(8080, handler)
153
+ ```
package/docs/stdlib.md CHANGED
@@ -1,139 +1,111 @@
1
1
  # Bibliothèque Standard (Stdlib)
2
2
 
3
- Fazer inclut une bibliothèque standard riche pour interagir avec le système, les fichiers, le réseau et plus encore.
3
+ Fazer inclut une bibliothèque standard "batteries included" pour le développement d'applications robustes.
4
4
 
5
- ## Entrées / Sorties
5
+ ## Système de Fichiers (fs)
6
6
 
7
- ### `print(valeur)` / `println(valeur)`
8
- Affiche une valeur sur la sortie standard (console).
7
+ Manipulation de fichiers synchrones et performante.
9
8
 
10
- ### `input(message)`
11
- Affiche un message et attend une entrée utilisateur (retourne une chaîne).
9
+ ### `fs_read(path)`
10
+ Lit le contenu d'un fichier en texte (UTF-8). Retourne `null` en cas d'erreur.
11
+ ```fazer
12
+ content := fs_read("config.json")
13
+ ```
12
14
 
13
- ### `box(titre, contenu)`
14
- Affiche une boîte stylisée avec un titre et du contenu (texte ou liste).
15
+ ### `fs_write(path, content)`
16
+ Écrit (écrase) du contenu dans un fichier. Retourne `true` si succès.
17
+ ```fazer
18
+ fs_write("log.txt", "Initialisation...")
19
+ ```
15
20
 
16
- ### `menu(options)`
17
- Affiche un menu interactif dans la console et retourne l'option choisie.
21
+ ### `fs_append(path, content)`
22
+ Ajoute du contenu à la fin d'un fichier.
18
23
  ```fazer
19
- choix := menu(["Option A", "Option B"])
24
+ fs_append("log.txt", "\nNouvelle entrée")
20
25
  ```
21
26
 
22
- ### `style(texte, couleur)`
23
- Applique une couleur ANSI au texte pour l'affichage console.
24
- Couleurs : "red", "green", "blue", "yellow", "cyan", "magenta", "white", "bold", "dim".
27
+ ### `fs_exists(path)`
28
+ Vérifie si un fichier ou dossier existe.
25
29
  ```fazer
26
- println(style("Succès !", "green"))
30
+ if fs_exists("data") -> ... end
27
31
  ```
28
32
 
29
- ## Fichiers (File System)
33
+ ## Manipulation de Données
30
34
 
31
- ### `read(path)`
32
- Lit un fichier texte entier.
35
+ ### JSON
36
+ * `json_parse(str)` : Convertit une chaîne JSON en objet/liste.
37
+ * `json_stringify(obj)` : Convertit un objet en chaîne JSON formatée.
38
+ * Alias : `json(obj)`
33
39
 
34
- ### `write(path, content)`
35
- Écrit du contenu dans un fichier (écrase le fichier).
40
+ ### Chaînes de Caractères (String)
41
+ * `str_split(str, delimiter)` : Découpe une chaîne en liste.
42
+ * `str_replace(str, old, new)` : Remplace toutes les occurrences.
43
+ * `str_trim(str)` : Retire les espaces au début et à la fin.
44
+ * `str_upper(str)` : Convertit en majuscules.
45
+ * `str_lower(str)` : Convertit en minuscules.
36
46
 
37
- ### `exists(path)`
38
- Retourne `true` si le fichier ou dossier existe.
47
+ ### Mathématiques
48
+ * `random()` : Retourne un nombre aléatoire entre 0.0 et 1.0.
49
+ * `round(n)` : Arrondi à l'entier le plus proche.
50
+ * `floor(n)` : Arrondi à l'entier inférieur.
51
+ * `ceil(n)` : Arrondi à l'entier supérieur.
52
+ * `int(n)` : Conversion en entier.
53
+ * `float(n)` : Conversion en flottant.
39
54
 
40
- ### `readBytes(path)` / `writeBytes(path, b64)`
41
- Lecture/Écriture binaire (encodé en Base64).
55
+ ## Base de Données (db)
42
56
 
43
- ### `import(chemin)`
44
- Charge et exécute un autre script Fazer.
57
+ ### `db(path)`
58
+ Crée ou charge une base de données JSON persistante.
45
59
  ```fazer
46
- import("utils.fz")
60
+ store := db("data.json")
61
+ store.set("key", "value")
62
+ val := store.get("key")
63
+ data := store.all()
47
64
  ```
48
65
 
49
- ## Système & Exécution
66
+ ## Automation & Système
50
67
 
51
- ### `exec(cmd)`
52
- Exécute une commande shell et retourne la sortie (stdout) sous forme de chaîne.
53
- ```fazer
54
- files := exec("ls -la")
55
- ```
68
+ ### Presse-papier
69
+ * `clipboard_set(text)` : Copie du texte dans le presse-papier.
70
+ * `clipboard_get()` : Récupère le texte du presse-papier.
56
71
 
57
- ### `notify(titre, message)`
58
- Affiche une notification système (Windows Toast).
59
- ```fazer
60
- notify("Fazer", "Tâche terminée avec succès !")
61
- ```
72
+ ### Notifications
73
+ * `notify(title, msg)` : Affiche une notification système native.
62
74
 
63
- ### `clipboard`
64
- Objet global pour interagir avec le presse-papier système.
65
- * `clipboard.read()` : Retourne le contenu texte du presse-papier.
66
- * `clipboard.write(texte)` : Écrit du texte dans le presse-papier.
75
+ ### Exécution
76
+ * `exec(cmd)` : Exécute une commande système et capture la sortie.
77
+ * `sleep(ms)` : Pause l'exécution.
78
+ * `import(path)` : Charge un module Fazer externe.
67
79
 
68
- ### `sleep(ms)`
69
- Met le script en pause pour `ms` millisecondes.
80
+ ## Interface Graphique (GUI Native)
70
81
 
71
- ### `env(nom_var)`
72
- Lit une variable d'environnement.
82
+ Créez des applications Windows Forms natives.
73
83
 
74
- ### `cwd()`
75
- Retourne le dossier de travail actuel.
84
+ ### Widgets
85
+ * `window(title, width, height, icon_path)` : Définit la fenêtre principale. `icon_path` est optionnel (ex: `"app.ico"`).
86
+ * `button(id, text, x, y, w, h)` : Ajoute un bouton.
87
+ * `label(id, text, x, y, w, h)` : Ajoute une étiquette de texte.
88
+ * `entry(id, text, x, y, w, h)` : Ajoute un champ de saisie texte.
76
89
 
77
- ### `argv()`
78
- Retourne les arguments de la ligne de commande.
90
+ ### Interaction
91
+ * `gui(handler)` : Lance la boucle d'événements. `handler` reçoit des événements `{id, type, value}`.
92
+ * `set_text(id, text)` : Change le texte d'un widget (à utiliser dans le handler).
93
+ * `msgbox(text)` : Affiche une boîte de dialogue modale.
79
94
 
80
95
  ## Réseau & Web
81
96
 
82
- ### `fetch(url, options)`
83
- Effectue une requête HTTP. `options` est un objet (method, headers, body).
84
- Retourne `{ status, body, headers }`.
85
-
86
97
  ### `server(port, handler)`
87
- Démarre un serveur HTTP simple.
88
- `handler` peut être une fonction qui prend `req` et retourne `res`, ou un objet de routage simple.
98
+ Démarre un serveur HTTP.
89
99
  ```fazer
90
100
  fn handler(req) ->
91
- "Hello from Fazer!"
101
+ return({ "body": "Hello World" })
92
102
  end
93
103
  server(8080, handler)
94
104
  ```
95
105
 
96
- ### `discord(token)`
97
- Crée un client Discord (Bot).
98
- * `.on("message", fn)`
99
- * `.send(channelId, content)`
100
-
101
- ## Cryptographie
102
-
103
- ### `sha256(texte)`
104
- Retourne le hash SHA-256 (hex).
105
-
106
- ### `encrypt(texte, mot_de_passe)`
107
- Chiffre le texte avec AES-256-GCM (retourne Base64).
108
-
109
- ### `decrypt(b64, mot_de_passe)`
110
- Déchiffre le texte.
111
-
112
- ## JSON & Données
113
-
114
- ### `json(obj)`
115
- Convertit un objet en chaîne JSON.
116
-
117
- ### `parseJson(str)`
118
- Parse une chaîne JSON en objet.
119
-
120
- ### `int(valeur)`
121
- Convertit une valeur en entier.
122
-
123
- ### `float(valeur)`
124
- Convertit une valeur en nombre flottant.
125
-
126
- ### `db(chemin_json)`
127
- Crée ou charge une base de données JSON persistante.
128
- Retourne un objet avec `.get(k)`, `.set(k, v)`, `.save()` et `.data()`.
106
+ ### `fetch(url, options)`
107
+ Effectue une requête HTTP asynchrone.
129
108
  ```fazer
130
- mydb := db("data.json")
131
- mydb.set("score", 100)
132
- mydb.save()
109
+ res := fetch("https://api.example.com/data")
110
+ print(res.body)
133
111
  ```
134
-
135
- ### `keys(obj)`
136
- Retourne les clés d'un objet JSON.
137
-
138
- ### `get(obj, clé)`
139
- Récupère une valeur dans un objet/map.
package/fazer.js CHANGED
@@ -23,7 +23,7 @@ const Comment = createToken({ name: "Comment", pattern: /(#|\/\/)[^\n]*/, group:
23
23
  const Assign = createToken({ name: "Assign", pattern: /:=/ });
24
24
 
25
25
  const Arrow = createToken({ name: "Arrow", pattern: /->|→/ });
26
- const DoublePipe = createToken({ name: "DoublePipe", pattern: /\|>|→>/ });
26
+ const DoublePipe = createToken({ name: "DoublePipe", pattern: /->>|\|>|→>/ });
27
27
 
28
28
  const Case = createToken({ name: "Case", pattern: /case\b/ });
29
29
  const Else = createToken({ name: "Else", pattern: /else\b/ });
@@ -158,7 +158,15 @@ class FazerParser extends EmbeddedActionsParser {
158
158
  $.OR([
159
159
  { ALT: () => $.SUBRULE($.fnDef) },
160
160
  { ALT: () => $.SUBRULE($.returnStmt) },
161
- { ALT: () => $.SUBRULE($.assignStmt) },
161
+ {
162
+ GATE: () => {
163
+ const t1 = $.LA(1).tokenType;
164
+ if (t1 === Mut) return true;
165
+ const t2 = $.LA(2).tokenType;
166
+ return t1 === Identifier && t2 === Assign;
167
+ },
168
+ ALT: () => $.SUBRULE($.assignStmt)
169
+ },
162
170
  { ALT: () => $.SUBRULE($.caseBlock) },
163
171
  { ALT: () => $.SUBRULE($.exprStmt) },
164
172
  ])
@@ -203,7 +211,8 @@ class FazerParser extends EmbeddedActionsParser {
203
211
 
204
212
  $.RULE("caseBlock", () => {
205
213
  const caseTok = $.CONSUME(Case);
206
- const expr = $.SUBRULE($.expression);
214
+ // Use addExpr to avoid consuming comparison operators (==, >, etc.) which start patterns
215
+ const expr = $.SUBRULE($.addExpr);
207
216
  const arms = [];
208
217
  $.AT_LEAST_ONE(() => {
209
218
  const pat = $.OR([
@@ -588,6 +597,7 @@ class FazerRuntime {
588
597
  this.code = code;
589
598
  this.global = new Scope(null);
590
599
  this.fns = new Map(); // name -> {params, body, closure}
600
+ this.native_ui_state = { widgets: [], updates: {} };
591
601
  this._installStdlib(argv);
592
602
  }
593
603
 
@@ -748,6 +758,111 @@ class FazerRuntime {
748
758
  writeText,
749
759
  saveText: (s, p) => { fs.writeFileSync(path.resolve(String(p)), String(s), "utf8"); return null; },
750
760
  exists: (p) => fs.existsSync(path.resolve(String(p))),
761
+
762
+ // Core Utils (Standard Library)
763
+ fs_read: (p) => { try { return fs.readFileSync(path.resolve(String(p)), "utf8"); } catch(e) { return null; } },
764
+ fs_write: (p, c) => { try { fs.writeFileSync(path.resolve(String(p)), String(c)); return true; } catch(e) { return false; } },
765
+ fs_exists: (p) => fs.existsSync(path.resolve(String(p))),
766
+
767
+ // Module System
768
+ import: async (p) => {
769
+ const pAbs = path.resolve(String(p));
770
+ if (!fs.existsSync(pAbs)) return null;
771
+ const code = fs.readFileSync(pAbs, "utf8");
772
+ const lex = lexer.tokenize(code);
773
+ if (lex.errors.length) throw new FazerError("Import Lexer Error: " + lex.errors[0].message);
774
+ const parser = new FazerParser();
775
+ parser.input = lex.tokens;
776
+ const ast = parser.program();
777
+ if (parser.errors.length) throw new FazerError("Import Parser Error: " + parser.errors[0].message);
778
+
779
+ const rt = new FazerRuntime({ filename: pAbs, code });
780
+ await rt.run(ast);
781
+
782
+ const exports = {};
783
+ console.log("Exporting vars from module...");
784
+ for (const [k, v] of rt.global.vars) {
785
+ console.log("Exporting:", k, v);
786
+ if (k === "__builtins__" || builtins[k]) continue;
787
+
788
+ let value = v.value;
789
+ // Handle functions: migrate definition to current runtime
790
+ if (value && typeof value === "object" && value.__fnref__) {
791
+ const fnName = value.__fnref__;
792
+ const fnDef = rt.fns.get(fnName);
793
+ if (fnDef) {
794
+ const uniqueName = `__import_${Math.floor(Math.random()*1000000)}_${fnName}`;
795
+ // console.log("Importing function:", fnName, "->", uniqueName);
796
+ this.fns.set(uniqueName, fnDef);
797
+ value = { __fnref__: uniqueName };
798
+ } else {
799
+ // Debug log
800
+ console.log("Warning: Function definition not found for", fnName);
801
+ }
802
+ }
803
+
804
+ exports[k] = value;
805
+ }
806
+ return exports;
807
+ },
808
+
809
+ // Persistence (Simple JSON DB)
810
+ db_load: (p) => { try { return JSON.parse(fs.readFileSync(path.resolve(String(p)), "utf8")); } catch(e) { return {}; } },
811
+ db_save: (p, data) => { try { fs.writeFileSync(path.resolve(String(p)), JSON.stringify(data, null, 2)); return true; } catch(e) { return false; } },
812
+
813
+ // System Automation
814
+ clipboard_set: (text) => {
815
+ if (process.platform === "win32") {
816
+ try {
817
+ const script = `Set-Clipboard -Value '${String(text).replace(/'/g, "''")}'`;
818
+ const b64 = Buffer.from(script, 'utf16le').toString('base64');
819
+ child_process.execSync(`powershell -EncodedCommand ${b64}`);
820
+ return true;
821
+ } catch(e) { return false; }
822
+ }
823
+ return false;
824
+ },
825
+ clipboard_get: () => {
826
+ if (process.platform === "win32") {
827
+ try { return child_process.execSync(`powershell -command "Get-Clipboard"`).toString().trim(); } catch(e) { return ""; }
828
+ }
829
+ return "";
830
+ },
831
+ notify: (title, msg) => {
832
+ if (process.platform === "win32") {
833
+ const cmd = `
834
+ [reflection.assembly]::loadwithpartialname("System.Windows.Forms");
835
+ [reflection.assembly]::loadwithpartialname("System.Drawing");
836
+ $n = new-object system.windows.forms.notifyicon;
837
+ $n.icon = [system.drawing.systemicons]::information;
838
+ $n.visible = $true;
839
+ $n.showballoontip(10, "${String(title).replace(/"/g, '`"')}", "${String(msg).replace(/"/g, '`"')}", [system.windows.forms.tooltipicon]::none);
840
+ Start-Sleep -s 3;
841
+ $n.Visible = $false;
842
+ `;
843
+ try {
844
+ const b64 = Buffer.from(cmd, 'utf16le').toString('base64');
845
+ child_process.execSync(`powershell -EncodedCommand ${b64}`);
846
+ return true;
847
+ } catch(e) { return false; }
848
+ }
849
+ return false;
850
+ },
851
+
852
+ json_parse: (s) => { try { return JSON.parse(String(s)); } catch(e) { return null; } },
853
+ json_stringify: (x) => JSON.stringify(x, null, 2),
854
+
855
+ str_split: (s, d) => String(s).split(String(d)),
856
+ str_replace: (s, a, b) => String(s).split(String(a)).join(String(b)), // simple replace all
857
+ str_trim: (s) => String(s).trim(),
858
+ str_upper: (s) => String(s).toUpperCase(),
859
+ str_lower: (s) => String(s).toLowerCase(),
860
+
861
+ random: () => Math.random(),
862
+ round: (x) => Math.round(Number(x)),
863
+ floor: (x) => Math.floor(Number(x)),
864
+ ceil: (x) => Math.ceil(Number(x)),
865
+
751
866
  ls: (p) => { try { return fs.readdirSync(path.resolve(String(p || "."))); } catch(e) { return []; } },
752
867
  rm: (p) => { try { fs.rmSync(path.resolve(String(p)), { recursive: true, force: true }); return true; } catch(e) { return false; } },
753
868
  mkdir: (p) => { try { fs.mkdirSync(path.resolve(String(p)), { recursive: true }); return true; } catch(e) { return false; } },
@@ -902,52 +1017,7 @@ class FazerRuntime {
902
1017
  };
903
1018
  },
904
1019
 
905
- import: async (p) => {
906
- const pAbs = path.resolve(String(p));
907
- if (!fs.existsSync(pAbs)) throw new FazerError("Module not found: " + p);
908
- const code = fs.readFileSync(pAbs, "utf8");
909
- const lex = lexer.tokenize(code);
910
- if (lex.errors.length) throw new FazerError("Lexer error in module: " + lex.errors[0].message);
911
- const parser = new FazerParser();
912
- parser.input = lex.tokens;
913
- const ast = parser.program();
914
- if (parser.errors.length) throw new FazerError("Parser error in module: " + parser.errors[0].message);
915
- return await this._execBlock(ast, this.global);
916
- },
917
-
918
- db: (dbPath) => {
919
- const absPath = path.resolve(String(dbPath));
920
- let data = {};
921
- if (fs.existsSync(absPath)) {
922
- try { data = JSON.parse(fs.readFileSync(absPath, "utf8")); } catch(e) {}
923
- }
924
- return {
925
- get: (k) => data[String(k)],
926
- set: (k, v) => { data[String(k)] = v; return null; },
927
- save: () => { fs.writeFileSync(absPath, JSON.stringify(data, null, 2), "utf8"); return null; },
928
- data: () => data
929
- };
930
- },
931
1020
 
932
- clipboard: {
933
- read: () => {
934
- try {
935
- if (process.platform === "win32") {
936
- return require("child_process").execSync("powershell -command \"Get-Clipboard\"", {encoding: "utf8"}).trim();
937
- }
938
- return "";
939
- } catch (e) { return ""; }
940
- },
941
- write: (s) => {
942
- try {
943
- const str = String(s);
944
- if (process.platform === "win32") {
945
- require("child_process").execSync("powershell -command \"Set-Clipboard -Value '" + str.replace(/'/g, "''") + "'\"");
946
- }
947
- } catch (e) {}
948
- return null;
949
- }
950
- },
951
1021
 
952
1022
  menu: async (options) => {
953
1023
  if (!Array.isArray(options)) throw new FazerError("menu expects a list of options");
@@ -964,23 +1034,7 @@ class FazerRuntime {
964
1034
  });
965
1035
  },
966
1036
 
967
- notify: (title, message) => {
968
- try {
969
- if (process.platform === "win32") {
970
- const psScript = `
971
- [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
972
- $obj = New-Object System.Windows.Forms.NotifyIcon
973
- $obj.Icon = [System.Drawing.SystemIcons]::Information
974
- $obj.Visible = $true
975
- $obj.ShowBalloonTip(3000, "${String(title).replace(/"/g, '`"')}", "${String(message).replace(/"/g, '`"')}", [System.Windows.Forms.ToolTipIcon]::Info)
976
- Start-Sleep -s 3
977
- $obj.Visible = $false
978
- `;
979
- require("child_process").execSync("powershell -command \"" + psScript.replace(/\n/g, ";") + "\"");
980
- }
981
- } catch(e) {}
982
- return null;
983
- },
1037
+
984
1038
 
985
1039
  exec: (cmd) => {
986
1040
  try {
@@ -1059,12 +1113,299 @@ class FazerRuntime {
1059
1113
  close: () => { srv.close(); return null; }
1060
1114
  });
1061
1115
  });
1116
+ srv.on('error', (e) => {
1117
+ if (e.code === 'EADDRINUSE') {
1118
+ console.error(`Error: Port ${port} is already in use.`);
1119
+ } else {
1120
+ console.error(`Server error: ${e.message}`);
1121
+ }
1122
+ resolve(null); // Resolve with null to avoid crashing
1123
+ });
1062
1124
  });
1063
1125
  },
1064
1126
 
1127
+ // --- NATIVE UI (WinForms) ---
1128
+
1129
+ window: (title, w, h, icon) => {
1130
+ this.native_ui_state.widgets = [{ type: 'window', title, w, h, icon }];
1131
+ return "window";
1132
+ },
1133
+
1134
+ button: (id, text, x, y, w, h) => {
1135
+ this.native_ui_state.widgets.push({ type: 'widget', cls: 'Button', id, text, x, y, w, h });
1136
+ return id;
1137
+ },
1138
+
1139
+ label: (id, text, x, y, w, h) => {
1140
+ this.native_ui_state.widgets.push({ type: 'widget', cls: 'Label', id, text, x, y, w, h });
1141
+ return id;
1142
+ },
1143
+
1144
+ entry: (id, text, x, y, w, h) => {
1145
+ this.native_ui_state.widgets.push({ type: 'widget', cls: 'TextBox', id, text, x, y, w, h });
1146
+ return id;
1147
+ },
1148
+
1149
+ set_text: (id, val) => {
1150
+ if (!this.native_ui_state.updates.set_text) this.native_ui_state.updates.set_text = {};
1151
+ this.native_ui_state.updates.set_text[id] = val;
1152
+ return val;
1153
+ },
1154
+
1155
+ msgbox: (msg) => {
1156
+ this.native_ui_state.updates.msgbox = String(msg);
1157
+ return true;
1158
+ },
1159
+
1160
+ gui: async (handler) => {
1161
+ if (process.platform !== 'win32') throw new FazerError("Native GUI is Windows only.");
1162
+
1163
+ const http = require("http");
1164
+ const port = await new Promise(r => {
1165
+ const s = http.createServer();
1166
+ s.listen(0, () => {
1167
+ const p = s.address().port;
1168
+ s.close(() => r(p));
1169
+ });
1170
+ });
1171
+
1172
+ const srv = http.createServer(async (req, res) => {
1173
+ if (req.method === "POST" && req.url === "/event") {
1174
+ let body = "";
1175
+ for await (const chunk of req) body += chunk;
1176
+ try {
1177
+ const event = JSON.parse(body);
1178
+ this.native_ui_state.updates = {}; // Reset
1179
+
1180
+ if (handler) {
1181
+ await this._call(handler, [event], this.global);
1182
+ }
1183
+
1184
+ res.writeHead(200, { "Content-Type": "application/json" });
1185
+ res.end(JSON.stringify(this.native_ui_state.updates));
1186
+ } catch(e) {
1187
+ console.error(e);
1188
+ res.writeHead(500); res.end("{}");
1189
+ }
1190
+ }
1191
+ });
1192
+
1193
+ srv.listen(port);
1194
+
1195
+ // Generate PowerShell Script
1196
+ let ps = `
1197
+ Add-Type -AssemblyName System.Windows.Forms
1198
+ Add-Type -AssemblyName System.Drawing
1199
+ $url = "http://localhost:${port}/event"
1200
+
1201
+ function Send-Event($id, $type, $val) {
1202
+ $body = @{id=$id; type=$type; value=$val} | ConvertTo-Json -Compress
1203
+ try {
1204
+ $res = Invoke-RestMethod -Uri $url -Method POST -Body $body -ContentType "application/json"
1205
+ if ($res.set_text) {
1206
+ foreach($k in $res.set_text.PSObject.Properties) {
1207
+ $c = $form.Controls.Find($k.Name, $true)
1208
+ if ($c) { $c[0].Text = $k.Value }
1209
+ }
1210
+ }
1211
+ if ($res.msgbox) { [System.Windows.Forms.MessageBox]::Show($res.msgbox) }
1212
+ } catch {}
1213
+ }
1214
+ `;
1215
+
1216
+ const widgets = this.native_ui_state.widgets;
1217
+ const win = widgets.find(w => w.type === 'window');
1218
+ if (!win) throw new FazerError("No window defined. Use window().");
1219
+
1220
+ let iconCmd = "";
1221
+ if (win.icon) {
1222
+ const iconAbs = require('path').resolve(String(win.icon));
1223
+ const iconPath = iconAbs.replace(/\\/g, '\\\\');
1224
+ if (require('fs').existsSync(iconAbs)) {
1225
+ if (String(win.icon).endsWith(".ico")) {
1226
+ iconCmd = `$form.Icon = New-Object System.Drawing.Icon("${iconPath}")`;
1227
+ } else {
1228
+ iconCmd = `$form.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("${iconPath}")`;
1229
+ }
1230
+ }
1231
+ }
1232
+
1233
+ ps += `
1234
+ $form = New-Object System.Windows.Forms.Form
1235
+ $form.Text = "${win.title}"
1236
+ ${iconCmd}
1237
+ $form.Width = ${win.w}
1238
+ $form.Height = ${win.h}
1239
+ $form.StartPosition = "CenterScreen"
1240
+ $form.BackColor = [System.Drawing.Color]::FromArgb(30, 30, 30)
1241
+ $form.ForeColor = [System.Drawing.Color]::White
1242
+ `;
1243
+
1244
+ for (const w of widgets) {
1245
+ if (w.type === 'window') continue;
1246
+ ps += `
1247
+ $${w.id} = New-Object System.Windows.Forms.${w.cls}
1248
+ $${w.id}.Name = "${w.id}"
1249
+ $${w.id}.Text = "${w.text}"
1250
+ $${w.id}.Left = ${w.x}
1251
+ $${w.id}.Top = ${w.y}
1252
+ $${w.id}.Width = ${w.w}
1253
+ $${w.id}.Height = ${w.h}
1254
+ $${w.id}.Font = New-Object System.Drawing.Font("Segoe UI", 10)
1255
+ `;
1256
+
1257
+ if (w.cls === 'Button') {
1258
+ ps += `
1259
+ $${w.id}.FlatStyle = "Flat"
1260
+ $${w.id}.BackColor = [System.Drawing.Color]::FromArgb(60, 60, 60)
1261
+ $${w.id}.FlatAppearance.BorderSize = 0
1262
+ $${w.id}.Add_Click({ Send-Event "${w.id}" "click" "" })
1263
+ `;
1264
+ } else if (w.cls === 'TextBox') {
1265
+ ps += `
1266
+ $${w.id}.BorderStyle = "FixedSingle"
1267
+ $${w.id}.BackColor = [System.Drawing.Color]::FromArgb(50, 50, 50)
1268
+ $${w.id}.ForeColor = [System.Drawing.Color]::White
1269
+ $${w.id}.Add_TextChanged({ Send-Event "${w.id}" "change" $this.Text })
1270
+ `;
1271
+ }
1272
+
1273
+ ps += `$form.Controls.Add($${w.id})\n`;
1274
+ }
1275
+
1276
+ ps += `
1277
+ [void]$form.ShowDialog()
1278
+ `;
1279
+
1280
+ // Run PS
1281
+ const b64 = Buffer.from(ps, 'utf16le').toString('base64');
1282
+ require('child_process').spawn('powershell', ['-EncodedCommand', b64], { stdio: 'inherit' });
1283
+
1284
+ return new Promise(r => {});
1285
+ },
1286
+
1065
1287
  argv: argvFn,
1066
1288
  env: envFn,
1067
1289
  cwd: cwdFn,
1290
+ input: (p) => builtins.ask(p),
1291
+ nowMs: () => Date.now(),
1292
+
1293
+ // --- EXTENDED FEATURES (Automation, State, DB) ---
1294
+
1295
+ set: (obj, key, val) => {
1296
+ if (obj && typeof obj === 'object') {
1297
+ obj[key] = val;
1298
+ return val;
1299
+ }
1300
+ return null;
1301
+ },
1302
+
1303
+ clipboard_set: (text) => {
1304
+ try {
1305
+ if (process.platform === 'win32') {
1306
+ require('child_process').execSync(`echo ${String(text).replace(/[&|<>^]/g, '^$&')} | clip`);
1307
+ } else {
1308
+ // TODO: Linux/Mac support
1309
+ }
1310
+ return true;
1311
+ } catch(e) { return false; }
1312
+ },
1313
+
1314
+ notify: (title, msg) => {
1315
+ try {
1316
+ if (process.platform === 'win32') {
1317
+ const script = `
1318
+ [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms");
1319
+ $objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon;
1320
+ $objNotifyIcon.Icon = [System.Drawing.SystemIcons]::Information;
1321
+ $objNotifyIcon.Visible = $True;
1322
+ $objNotifyIcon.BalloonTipTitle = "${String(title).replace(/"/g, '`"')}";
1323
+ $objNotifyIcon.BalloonTipText = "${String(msg).replace(/"/g, '`"')}";
1324
+ $objNotifyIcon.ShowBalloonTip(5000);
1325
+ Start-Sleep -s 5;
1326
+ $objNotifyIcon.Dispose();
1327
+ `;
1328
+ require('child_process').exec(`powershell -Command "${script.replace(/\n/g, ' ')}"`);
1329
+ }
1330
+ return true;
1331
+ } catch(e) { return false; }
1332
+ },
1333
+
1334
+ db: (p) => {
1335
+ const dbPath = path.resolve(String(p));
1336
+ let data = {};
1337
+ if (fs.existsSync(dbPath)) {
1338
+ try { data = JSON.parse(fs.readFileSync(dbPath, "utf8")); } catch(e){}
1339
+ }
1340
+
1341
+ return {
1342
+ get: (k) => data[k],
1343
+ set: (k, v) => {
1344
+ data[k] = v;
1345
+ fs.writeFileSync(dbPath, JSON.stringify(data, null, 2));
1346
+ return v;
1347
+ },
1348
+ all: () => data
1349
+ };
1350
+ },
1351
+
1352
+ import: async (p) => {
1353
+ const fullPath = path.resolve(String(p));
1354
+ if (!fs.existsSync(fullPath)) throw new FazerError(`Module not found: ${p}`);
1355
+ const code = fs.readFileSync(fullPath, "utf8");
1356
+
1357
+ const lexResult = lexer.tokenize(code);
1358
+ if (lexResult.errors.length > 0) throw new FazerError(`Lexer error in ${p}: ${lexResult.errors[0].message}`);
1359
+
1360
+ const parser = new FazerParser();
1361
+ parser.input = lexResult.tokens;
1362
+ const ast = parser.program();
1363
+ if (parser.errors.length > 0) throw new FazerError(`Parser error in ${p}: ${parser.errors[0].message}`);
1364
+
1365
+ const moduleScope = new Scope(this.global);
1366
+ await this._execBlock(ast, moduleScope);
1367
+
1368
+ const exports = {};
1369
+ for(const [k, v] of moduleScope.vars) {
1370
+ exports[k] = v.value;
1371
+ }
1372
+ return exports;
1373
+ },
1374
+
1375
+ // File System
1376
+ fs_read: (p) => {
1377
+ try { return require("fs").readFileSync(String(p), "utf8"); } catch(e) { return null; }
1378
+ },
1379
+ fs_write: (p, c) => {
1380
+ try { require("fs").writeFileSync(String(p), String(c)); return true; } catch(e) { return false; }
1381
+ },
1382
+ fs_append: (p, c) => {
1383
+ try { require("fs").appendFileSync(String(p), String(c)); return true; } catch(e) { return false; }
1384
+ },
1385
+ fs_exists: (p) => {
1386
+ try { return require("fs").existsSync(String(p)); } catch(e) { return false; }
1387
+ },
1388
+
1389
+ // JSON
1390
+ json_parse: (s) => {
1391
+ try { return JSON.parse(String(s)); } catch(e) { return null; }
1392
+ },
1393
+ json_stringify: (o) => {
1394
+ try { return JSON.stringify(o, null, 2); } catch(e) { return null; }
1395
+ },
1396
+
1397
+ // String Utils
1398
+ str_split: (s, d) => String(s).split(String(d)),
1399
+ str_replace: (s, old, n) => String(s).split(String(old)).join(String(n)),
1400
+ str_trim: (s) => String(s).trim(),
1401
+ str_upper: (s) => String(s).toUpperCase(),
1402
+ str_lower: (s) => String(s).toLowerCase(),
1403
+
1404
+ // Math
1405
+ random: () => Math.random(),
1406
+ round: (n) => Math.round(Number(n)),
1407
+ floor: (n) => Math.floor(Number(n)),
1408
+ ceil: (n) => Math.ceil(Number(n)),
1068
1409
  };
1069
1410
 
1070
1411
  this.global.set("__builtins__", builtins, false);
@@ -1213,15 +1554,16 @@ class FazerRuntime {
1213
1554
  case "get": {
1214
1555
  const obj = await this._eval(expr.obj, scope);
1215
1556
  const key = await this._eval(expr.key, scope);
1216
- return (obj == null) ? null : obj[String(key)];
1557
+ const v = (obj == null) ? null : obj[String(key)];
1558
+ return v === undefined ? null : v;
1217
1559
  }
1218
1560
 
1219
1561
  case "idx": {
1220
1562
  const obj = await this._eval(expr.obj, scope);
1221
1563
  const idx = await this._eval(expr.idx, scope);
1222
1564
  if (obj == null) return null;
1223
- if (Array.isArray(obj)) return obj[Number(idx)];
1224
- return obj[String(idx)];
1565
+ if (Array.isArray(obj)) return obj[Number(idx)] === undefined ? null : obj[Number(idx)];
1566
+ return obj[String(idx)] === undefined ? null : obj[String(idx)];
1225
1567
  }
1226
1568
 
1227
1569
  case "call": {
@@ -1547,6 +1889,16 @@ async function main() {
1547
1889
  // 1. fazer run file.fz ...
1548
1890
  // 2. fazer file.fz ...
1549
1891
 
1892
+ if (cmd === "build") {
1893
+ try {
1894
+ const builder = require("./tools/builder.js");
1895
+ await builder(argv[1], argv.slice(2));
1896
+ } catch (e) {
1897
+ console.error(e);
1898
+ }
1899
+ return;
1900
+ }
1901
+
1550
1902
  if (cmd === "run") {
1551
1903
  fileArg = argv[1];
1552
1904
  if (!fileArg) usage();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fazer-lang",
3
- "version": "2.3.0",
4
- "description": "Fazer — The ultimate automation language. Batteries-included: HTTP Server, System Exec, Clipboard, Discord, Crypto, and Pipe Operators (->).",
3
+ "version": "2.4.0",
4
+ "description": "Fazer — The ultimate automation language. Batteries-included: Native EXE Build, Icons, HTTP Server, System Exec, Clipboard, Discord, Crypto, and Pipe Operators (->).",
5
5
  "main": "fazer.js",
6
6
  "bin": {
7
7
  "fazer": "fazer.js"
@@ -0,0 +1,208 @@
1
+
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { execSync, spawnSync } = require('child_process');
5
+
6
+ function log(msg) { console.log(`[Fazer Build] ${msg}`); }
7
+ function error(msg) { console.error(`[Error] ${msg}`); process.exit(1); }
8
+
9
+ module.exports = async function build(inputFile, args) {
10
+ if (!inputFile) error("No input file specified. Usage: fazer build <app.fz>");
11
+
12
+ const inputPath = path.resolve(inputFile);
13
+ if (!fs.existsSync(inputPath)) error(`Input file not found: ${inputPath}`);
14
+
15
+ const appName = path.basename(inputFile, '.fz');
16
+ const distDir = path.resolve(process.cwd(), 'dist', appName);
17
+
18
+ // Parse args
19
+ let iconPath = null;
20
+ for(let i=0; i<args.length; i++) {
21
+ if (args[i] === '--icon' && args[i+1]) {
22
+ iconPath = path.resolve(args[i+1]);
23
+ i++;
24
+ }
25
+ }
26
+
27
+ log(`Building '${appName}'...`);
28
+ log(`Output Directory: ${distDir}`);
29
+
30
+ // 1. Prepare Directory
31
+ if (fs.existsSync(distDir)) {
32
+ fs.rmSync(distDir, { recursive: true, force: true });
33
+ }
34
+ fs.mkdirSync(distDir, { recursive: true });
35
+
36
+ // 2. Copy Core Files
37
+ const fazerRoot = path.dirname(__dirname); // tools/.. -> fazer-lang/
38
+ const fazerJsPath = path.join(fazerRoot, 'fazer.js');
39
+
40
+ fs.copyFileSync(fazerJsPath, path.join(distDir, 'fazer.js'));
41
+ fs.copyFileSync(inputPath, path.join(distDir, 'app.fz'));
42
+
43
+ // 3. Copy node_modules (Optimized: only copy chevrotain/ws if possible, but full copy is safer)
44
+ const nodeModulesSrc = path.join(fazerRoot, 'node_modules');
45
+ if (fs.existsSync(nodeModulesSrc)) {
46
+ log("Copying dependencies...");
47
+ // Use robocopy on Windows for speed, or recursive copy
48
+ try {
49
+ // Recursive copy
50
+ fs.cpSync(nodeModulesSrc, path.join(distDir, 'node_modules'), { recursive: true });
51
+ } catch(e) {
52
+ log("Warning: Failed to copy node_modules. You may need to run 'npm install' in dist.");
53
+ }
54
+ } else {
55
+ log("Warning: node_modules not found. The app might not run without dependencies.");
56
+ }
57
+
58
+ // 4. Generate Launcher (C#)
59
+ log("Generating Native Launcher...");
60
+
61
+ // We embed the logic to find 'node'
62
+ // If we want to be truly portable, we should copy node.exe here too.
63
+ // For now, we assume 'node' is in PATH or next to the exe.
64
+
65
+ const launcherCs = `
66
+ using System;
67
+ using System.Diagnostics;
68
+ using System.IO;
69
+ using System.Windows.Forms;
70
+
71
+ class Program {
72
+ [STAThread]
73
+ static void Main() {
74
+ string appDir = AppDomain.CurrentDomain.BaseDirectory;
75
+ string script = Path.Combine(appDir, "fazer.js");
76
+ string app = Path.Combine(appDir, "app.fz");
77
+
78
+ string nodeExe = "node";
79
+ if (File.Exists(Path.Combine(appDir, "node.exe"))) {
80
+ nodeExe = Path.Combine(appDir, "node.exe");
81
+ }
82
+
83
+ string extraArgs = "";
84
+ string[] cmdArgs = Environment.GetCommandLineArgs();
85
+ // Skip first arg which is the executable itself
86
+ for (int i = 1; i < cmdArgs.Length; i++) {
87
+ extraArgs += " \\\"" + cmdArgs[i] + "\\\"";
88
+ }
89
+
90
+ ProcessStartInfo psi = new ProcessStartInfo();
91
+ psi.FileName = nodeExe;
92
+ // Arguments: "path/to/fazer.js" "path/to/app.fz" extraArgs
93
+ psi.Arguments = "\\\"" + script + "\\\" \\\"" + app + "\\\" " + extraArgs;
94
+
95
+ psi.UseShellExecute = false;
96
+ psi.CreateNoWindow = true;
97
+ psi.WindowStyle = ProcessWindowStyle.Hidden;
98
+
99
+ try {
100
+ Process p = Process.Start(psi);
101
+ } catch (Exception e) {
102
+ MessageBox.Show("Failed to launch Fazer App:\\n" + e.Message + "\\n\\nEnsure Node.js is installed or node.exe is in the folder.", "Launch Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
103
+ }
104
+ }
105
+ }
106
+ `;
107
+
108
+ const csPath = path.join(distDir, 'Launcher.cs');
109
+ fs.writeFileSync(csPath, launcherCs);
110
+
111
+ // 5. Compile Launcher
112
+ // We use C# compiler (csc.exe) which is available on most Windows
113
+ // Or we use PowerShell Add-Type hack to compile to exe?
114
+ // Add-Type -OutputAssembly is easiest from PS.
115
+
116
+ const exeName = `${appName}.exe`;
117
+ const exePath = path.join(distDir, exeName);
118
+
119
+ let iconArg = "";
120
+ if (iconPath && fs.existsSync(iconPath)) {
121
+ // Copy icon to dist
122
+ const distIcon = path.join(distDir, 'app.ico');
123
+ fs.copyFileSync(iconPath, distIcon);
124
+ iconArg = `-Win32Icon "${distIcon}"`; // PowerShell param
125
+ // For csc: /win32icon:app.ico
126
+ }
127
+
128
+ log("Compiling EXE...");
129
+
130
+ // We use PowerShell to compile because locating csc.exe can be annoying
131
+ // Add-Type -TypeDefinition $code -OutputAssembly $out -Target WinExe ...
132
+
133
+ const psScript = `
134
+ $code = Get-Content -Raw "${csPath}"
135
+ $params = @{
136
+ TypeDefinition = $code
137
+ OutputAssembly = "${exePath}"
138
+ Target = "WinExe"
139
+ ReferencedAssemblies = "System.Windows.Forms"
140
+ }
141
+ ${iconArg ? `$compilerOptions = New-Object System.CodeDom.Compiler.CompilerParameters
142
+ $compilerOptions.CompilerOptions = "/win32icon:${path.join(distDir, 'app.ico').replace(/\\/g, '\\\\')}"
143
+ # Add-Type doesn't easily support CompilerOptions for icons in all versions.
144
+ # Fallback to direct csc call if needed or use specific Add-Type overload.
145
+ ` : ''}
146
+
147
+ # Simple compilation without icon for now via Add-Type,
148
+ # but for Icon we usually need csc. Let's try to find csc.
149
+
150
+ $csc = (Get-ChildItem -Path "$env:windir\\Microsoft.NET\\Framework64\\v4*" -Filter csc.exe | Select-Object -Last 1).FullName
151
+ if (-not $csc) {
152
+ $csc = (Get-ChildItem -Path "$env:windir\\Microsoft.NET\\Framework\\v4*" -Filter csc.exe | Select-Object -Last 1).FullName
153
+ }
154
+
155
+ if ($csc) {
156
+ Write-Host "Compiling with CSC: $csc"
157
+ $args = @("/target:winexe", "/out:${exePath}", "${csPath}")
158
+ if ("${iconArg}") { $args += "/win32icon:${path.join(distDir, 'app.ico')}" }
159
+ & $csc $args
160
+ } else {
161
+ Write-Host "CSC not found, using CSharpCodeProvider..."
162
+ $codeProvider = New-Object Microsoft.CSharp.CSharpCodeProvider
163
+ $parameters = New-Object System.CodeDom.Compiler.CompilerParameters
164
+ $parameters.GenerateExecutable = $true
165
+ $parameters.OutputAssembly = "${exePath}"
166
+ $parameters.CompilerOptions = "/target:winexe"
167
+ $parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll")
168
+ $parameters.ReferencedAssemblies.Add("System.dll")
169
+ $parameters.ReferencedAssemblies.Add("System.Drawing.dll")
170
+
171
+ if ("${iconArg}") {
172
+ $parameters.CompilerOptions += " /win32icon:${path.join(distDir, 'app.ico').replace(/\\/g, '\\\\')}"
173
+ }
174
+
175
+ $results = $codeProvider.CompileAssemblyFromSource($parameters, $code)
176
+
177
+ if ($results.Errors.HasErrors) {
178
+ foreach($e in $results.Errors) {
179
+ Write-Error $e.ToString()
180
+ }
181
+ exit 1
182
+ }
183
+ }
184
+ `;
185
+
186
+ // Write PS build script
187
+ const psBuildPath = path.join(distDir, 'build_exe.ps1');
188
+ fs.writeFileSync(psBuildPath, psScript);
189
+
190
+ try {
191
+ execSync(`powershell -ExecutionPolicy Bypass -File "${psBuildPath}"`, { stdio: 'inherit' });
192
+ } catch(e) {
193
+ log("Compilation failed. See error above.");
194
+ // Clean up temp files
195
+ return;
196
+ }
197
+
198
+ // Clean up
199
+ if (fs.existsSync(exePath)) {
200
+ fs.unlinkSync(csPath);
201
+ fs.unlinkSync(psBuildPath);
202
+ log("Build Success!");
203
+ log(`Created: ${exePath}`);
204
+ log("You can now zip the folder '${distDir}' and share it.");
205
+ } else {
206
+ error("EXE file was not created.");
207
+ }
208
+ };