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 +15 -1
- package/docs/getting-started.md +130 -23
- package/docs/stdlib.md +71 -99
- package/fazer.js +420 -68
- package/package.json +2 -2
- package/tools/builder.js +208 -0
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`),
|
|
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
|
package/docs/getting-started.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
11
|
+
## Votre premier script
|
|
12
|
+
|
|
13
|
+
Créez un fichier `hello.fz` :
|
|
14
|
+
|
|
15
|
+
```fazer
|
|
16
|
+
"Bonjour Fazer !" ->> println
|
|
9
17
|
```
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
Exécutez-le :
|
|
20
|
+
```bash
|
|
21
|
+
fazer run hello.fz
|
|
22
|
+
```
|
|
12
23
|
|
|
24
|
+
Ou simplement (si installé) :
|
|
13
25
|
```bash
|
|
14
|
-
fazer
|
|
26
|
+
fazer hello.fz
|
|
15
27
|
```
|
|
16
28
|
|
|
17
|
-
##
|
|
29
|
+
## Concepts de Base
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
### Variables et Fonctions
|
|
20
32
|
|
|
21
33
|
```fazer
|
|
22
|
-
|
|
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
|
-
|
|
45
|
+
### Pipe (`->>`)
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
### 1. Système de Fichiers
|
|
59
|
+
```fazer
|
|
60
|
+
# Lire
|
|
61
|
+
contenu := fs_read("config.txt")
|
|
34
62
|
|
|
35
|
-
|
|
63
|
+
# Écrire
|
|
64
|
+
fs_write("log.txt", "Succès")
|
|
65
|
+
```
|
|
36
66
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
|
3
|
+
Fazer inclut une bibliothèque standard "batteries included" pour le développement d'applications robustes.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Système de Fichiers (fs)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
Affiche une valeur sur la sortie standard (console).
|
|
7
|
+
Manipulation de fichiers synchrones et performante.
|
|
9
8
|
|
|
10
|
-
### `
|
|
11
|
-
|
|
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
|
-
### `
|
|
14
|
-
|
|
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
|
-
### `
|
|
17
|
-
|
|
21
|
+
### `fs_append(path, content)`
|
|
22
|
+
Ajoute du contenu à la fin d'un fichier.
|
|
18
23
|
```fazer
|
|
19
|
-
|
|
24
|
+
fs_append("log.txt", "\nNouvelle entrée")
|
|
20
25
|
```
|
|
21
26
|
|
|
22
|
-
### `
|
|
23
|
-
|
|
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
|
-
|
|
30
|
+
if fs_exists("data") -> ... end
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
##
|
|
33
|
+
## Manipulation de Données
|
|
30
34
|
|
|
31
|
-
###
|
|
32
|
-
|
|
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
|
-
###
|
|
35
|
-
|
|
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
|
-
###
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
Lecture/Écriture binaire (encodé en Base64).
|
|
55
|
+
## Base de Données (db)
|
|
42
56
|
|
|
43
|
-
### `
|
|
44
|
-
|
|
57
|
+
### `db(path)`
|
|
58
|
+
Crée ou charge une base de données JSON persistante.
|
|
45
59
|
```fazer
|
|
46
|
-
|
|
60
|
+
store := db("data.json")
|
|
61
|
+
store.set("key", "value")
|
|
62
|
+
val := store.get("key")
|
|
63
|
+
data := store.all()
|
|
47
64
|
```
|
|
48
65
|
|
|
49
|
-
##
|
|
66
|
+
## Automation & Système
|
|
50
67
|
|
|
51
|
-
###
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
###
|
|
58
|
-
Affiche une notification système
|
|
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
|
-
###
|
|
64
|
-
|
|
65
|
-
* `
|
|
66
|
-
* `
|
|
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
|
-
|
|
69
|
-
Met le script en pause pour `ms` millisecondes.
|
|
80
|
+
## Interface Graphique (GUI Native)
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
Lit une variable d'environnement.
|
|
82
|
+
Créez des applications Windows Forms natives.
|
|
73
83
|
|
|
74
|
-
###
|
|
75
|
-
|
|
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
|
-
###
|
|
78
|
-
|
|
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
|
|
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
|
|
101
|
+
return({ "body": "Hello World" })
|
|
92
102
|
end
|
|
93
103
|
server(8080, handler)
|
|
94
104
|
```
|
|
95
105
|
|
|
96
|
-
### `
|
|
97
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|
package/tools/builder.js
ADDED
|
@@ -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
|
+
};
|