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/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: /#[^\n]*/, group: Lexer.SKIPPED });
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
- { 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; } },
@@ -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
- return (obj == null) ? null : obj[String(key)];
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.2.1",
4
- "description": "Fazer — A unique, high-performance scripting language with powerful pipe operators (→>) and batteries-included stdlib (crypto, http, fs, json).",
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
- "package.json"
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