fazer-lang 2.2.1 → 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 +43 -42
- package/docs/README.md +26 -0
- package/docs/examples.md +60 -0
- package/docs/getting-started.md +153 -0
- package/docs/stdlib.md +111 -0
- package/docs/syntax.md +86 -0
- package/fazer.js +521 -7
- package/package.json +13 -5
- package/tools/announce.fz +48 -0
- package/tools/builder.js +208 -0
package/fazer.js
CHANGED
|
@@ -18,12 +18,12 @@ const { Lexer, createToken, EmbeddedActionsParser } = require("chevrotain");
|
|
|
18
18
|
/* ────────────────────────────────────────────────────────────────────────── */
|
|
19
19
|
|
|
20
20
|
const WhiteSpace = createToken({ name: "WhiteSpace", pattern: /[ \t\r\n]+/, group: Lexer.SKIPPED });
|
|
21
|
-
const Comment = createToken({ name: "Comment", pattern:
|
|
21
|
+
const Comment = createToken({ name: "Comment", pattern: /(#|\/\/)[^\n]*/, group: Lexer.SKIPPED });
|
|
22
22
|
|
|
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; } },
|
|
@@ -780,6 +895,8 @@ class FazerRuntime {
|
|
|
780
895
|
|
|
781
896
|
json: (x) => JSON.stringify(x, null, 2),
|
|
782
897
|
parseJson: (s) => JSON.parse(s),
|
|
898
|
+
int: (x) => parseInt(String(x)) || 0,
|
|
899
|
+
float: (x) => parseFloat(String(x)) || 0.0,
|
|
783
900
|
|
|
784
901
|
exec: (cmd) => {
|
|
785
902
|
try {
|
|
@@ -900,9 +1017,395 @@ class FazerRuntime {
|
|
|
900
1017
|
};
|
|
901
1018
|
},
|
|
902
1019
|
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
menu: async (options) => {
|
|
1023
|
+
if (!Array.isArray(options)) throw new FazerError("menu expects a list of options");
|
|
1024
|
+
options.forEach((o, i) => console.log(`[${i+1}] ${o}`));
|
|
1025
|
+
const readline = require("readline");
|
|
1026
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1027
|
+
return new Promise(resolve => {
|
|
1028
|
+
rl.question("Choice > ", (ans) => {
|
|
1029
|
+
rl.close();
|
|
1030
|
+
const n = Number(ans);
|
|
1031
|
+
if (isNaN(n) || n < 1 || n > options.length) resolve(null);
|
|
1032
|
+
else resolve(options[n-1]);
|
|
1033
|
+
});
|
|
1034
|
+
});
|
|
1035
|
+
},
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
exec: (cmd) => {
|
|
1040
|
+
try {
|
|
1041
|
+
return require("child_process").execSync(String(cmd), { encoding: "utf8" }).trim();
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
throw new FazerError("exec failed: " + e.message);
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
|
|
1047
|
+
style: (text, color) => {
|
|
1048
|
+
const codes = {
|
|
1049
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
1050
|
+
red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m",
|
|
1051
|
+
blue: "\x1b[34m", magenta: "\x1b[35m", cyan: "\x1b[36m", white: "\x1b[37m"
|
|
1052
|
+
};
|
|
1053
|
+
return (codes[String(color)] || "") + String(text) + codes.reset;
|
|
1054
|
+
},
|
|
1055
|
+
|
|
1056
|
+
server: async (port, handler) => {
|
|
1057
|
+
const http = require("http");
|
|
1058
|
+
const srv = http.createServer(async (req, res) => {
|
|
1059
|
+
const bodyParts = [];
|
|
1060
|
+
for await (const chunk of req) bodyParts.push(chunk);
|
|
1061
|
+
const bodyStr = Buffer.concat(bodyParts).toString();
|
|
1062
|
+
|
|
1063
|
+
const reqObj = {
|
|
1064
|
+
method: req.method,
|
|
1065
|
+
url: req.url,
|
|
1066
|
+
headers: req.headers,
|
|
1067
|
+
body: bodyStr
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
try {
|
|
1071
|
+
let result = null;
|
|
1072
|
+
if (typeof handler === "object" && handler !== null && !handler.__fnref__) {
|
|
1073
|
+
const url = req.url.split("?")[0];
|
|
1074
|
+
if (handler[url]) {
|
|
1075
|
+
const h = handler[url];
|
|
1076
|
+
if (typeof h === "function" || (typeof h === "object" && h.__fnref__)) {
|
|
1077
|
+
result = await this._call(h, [reqObj], this.global);
|
|
1078
|
+
} else {
|
|
1079
|
+
result = h;
|
|
1080
|
+
}
|
|
1081
|
+
} else {
|
|
1082
|
+
result = { status: 404, body: "Not Found" };
|
|
1083
|
+
}
|
|
1084
|
+
} else {
|
|
1085
|
+
result = await this._call(handler, [reqObj], this.global);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
let status = 200;
|
|
1089
|
+
let headers = { "Content-Type": "text/plain" };
|
|
1090
|
+
let resBody = "";
|
|
1091
|
+
|
|
1092
|
+
if (typeof result === "object" && result !== null) {
|
|
1093
|
+
if (result.status) status = Number(result.status);
|
|
1094
|
+
if (result.headers) headers = { ...headers, ...result.headers };
|
|
1095
|
+
if (result.body) resBody = String(result.body);
|
|
1096
|
+
else if (result.status === undefined) resBody = JSON.stringify(result);
|
|
1097
|
+
} else {
|
|
1098
|
+
resBody = String(result);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
res.writeHead(status, headers);
|
|
1102
|
+
res.end(resBody);
|
|
1103
|
+
} catch (e) {
|
|
1104
|
+
res.writeHead(500);
|
|
1105
|
+
res.end("Internal Server Error: " + e.message);
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
return new Promise((resolve) => {
|
|
1110
|
+
srv.listen(Number(port), () => {
|
|
1111
|
+
console.log(`Server listening on port ${port}`);
|
|
1112
|
+
resolve({
|
|
1113
|
+
close: () => { srv.close(); return null; }
|
|
1114
|
+
});
|
|
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
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
},
|
|
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
|
+
|
|
903
1287
|
argv: argvFn,
|
|
904
1288
|
env: envFn,
|
|
905
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)),
|
|
906
1409
|
};
|
|
907
1410
|
|
|
908
1411
|
this.global.set("__builtins__", builtins, false);
|
|
@@ -1051,15 +1554,16 @@ class FazerRuntime {
|
|
|
1051
1554
|
case "get": {
|
|
1052
1555
|
const obj = await this._eval(expr.obj, scope);
|
|
1053
1556
|
const key = await this._eval(expr.key, scope);
|
|
1054
|
-
|
|
1557
|
+
const v = (obj == null) ? null : obj[String(key)];
|
|
1558
|
+
return v === undefined ? null : v;
|
|
1055
1559
|
}
|
|
1056
1560
|
|
|
1057
1561
|
case "idx": {
|
|
1058
1562
|
const obj = await this._eval(expr.obj, scope);
|
|
1059
1563
|
const idx = await this._eval(expr.idx, scope);
|
|
1060
1564
|
if (obj == null) return null;
|
|
1061
|
-
if (Array.isArray(obj)) return obj[Number(idx)];
|
|
1062
|
-
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)];
|
|
1063
1567
|
}
|
|
1064
1568
|
|
|
1065
1569
|
case "call": {
|
|
@@ -1385,6 +1889,16 @@ async function main() {
|
|
|
1385
1889
|
// 1. fazer run file.fz ...
|
|
1386
1890
|
// 2. fazer file.fz ...
|
|
1387
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
|
+
|
|
1388
1902
|
if (cmd === "run") {
|
|
1389
1903
|
fileArg = argv[1];
|
|
1390
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 —
|
|
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"
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"fazer.js",
|
|
11
11
|
"README.md",
|
|
12
12
|
"LICENSE",
|
|
13
|
-
"
|
|
13
|
+
"docs/",
|
|
14
|
+
"tools/"
|
|
14
15
|
],
|
|
15
16
|
"dependencies": {
|
|
16
17
|
"chevrotain": "^11.0.3",
|
|
@@ -29,12 +30,19 @@
|
|
|
29
30
|
"pipe-operator",
|
|
30
31
|
"functional",
|
|
31
32
|
"secure",
|
|
32
|
-
"automation"
|
|
33
|
+
"automation",
|
|
34
|
+
"discord-bot",
|
|
35
|
+
"http-server",
|
|
36
|
+
"system-automation"
|
|
33
37
|
],
|
|
34
38
|
"author": "Fazer Corp",
|
|
35
39
|
"license": "MIT",
|
|
36
40
|
"repository": {
|
|
37
41
|
"type": "git",
|
|
38
42
|
"url": "https://github.com/viced/fazer-lang.git"
|
|
39
|
-
}
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/viced/fazer-lang/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/viced/fazer-lang#readme"
|
|
40
48
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// tools/announce.fz
|
|
2
|
+
webhook := "https://canary.discord.com/api/webhooks/1463728147202838652/vEd4MqqtYmAFg15GKX3FBxLYS1XImycormdOMwTa3Dbwcm6r8lYI3dfIWlZiLH2Dxq9e"
|
|
3
|
+
|
|
4
|
+
fn send(title, desc, color) ->
|
|
5
|
+
payload := {
|
|
6
|
+
"embeds": [{
|
|
7
|
+
"title": title,
|
|
8
|
+
"description": desc,
|
|
9
|
+
"color": int(color),
|
|
10
|
+
"footer": { "text": "Fazer Automation System" }
|
|
11
|
+
}]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
println("Envoi vers Discord...")
|
|
15
|
+
res := fetch(webhook, {
|
|
16
|
+
"method": "POST",
|
|
17
|
+
"headers": { "Content-Type": "application/json" },
|
|
18
|
+
"body": json(payload)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
case res.status >= 200 and res.status < 300
|
|
22
|
+
true -> println("Succès !") end
|
|
23
|
+
else -> println("Erreur " + res.status + ": " + res.body) end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
a := argv()
|
|
28
|
+
start := 1
|
|
29
|
+
|
|
30
|
+
case len(a) <= start
|
|
31
|
+
true -> println("Usage: fazer tools/announce.fz \"Titre\" \"Description\" [color]") end
|
|
32
|
+
else ->
|
|
33
|
+
title := a[start]
|
|
34
|
+
desc := ""
|
|
35
|
+
case len(a) > start + 1
|
|
36
|
+
true -> desc := a[start + 1] end
|
|
37
|
+
else -> end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
color := 5763719
|
|
41
|
+
case len(a) > start + 2
|
|
42
|
+
true -> color := a[start + 2] end
|
|
43
|
+
else -> end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
send(title, desc, color)
|
|
47
|
+
end
|
|
48
|
+
end
|