fazer-lang 2.1.2 → 2.3.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
- const Arrow = createToken({ name: "Arrow", pattern: /→/ });
26
- const DoublePipe = createToken({ name: "DoublePipe", pattern: /→>/ });
25
+ const Arrow = createToken({ name: "Arrow", 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/ });
@@ -593,6 +593,8 @@ class FazerRuntime {
593
593
 
594
594
  _installStdlib(argv) {
595
595
  const { execFileSync } = require("child_process");
596
+ let WebSocket = null;
597
+ try { WebSocket = require("ws"); } catch (e) {}
596
598
 
597
599
  const ANSI = {
598
600
  reset: "\x1b[0m",
@@ -710,6 +712,7 @@ class FazerRuntime {
710
712
  const cwdFn = () => process.cwd();
711
713
 
712
714
  const http = require("http");
715
+ const https = require("https");
713
716
  const child_process = require("child_process");
714
717
 
715
718
  // register builtins in global scope
@@ -723,7 +726,10 @@ class FazerRuntime {
723
726
  try {
724
727
  while (true) {
725
728
  const n = fs.readSync(0, buf, 0, 1, null);
726
- if (n === 0) break;
729
+ if (n === 0) {
730
+ if (str === "") return null;
731
+ break;
732
+ }
727
733
  const c = buf.toString();
728
734
  if (c === "\r") continue;
729
735
  if (c === "\n") break;
@@ -774,6 +780,8 @@ class FazerRuntime {
774
780
 
775
781
  json: (x) => JSON.stringify(x, null, 2),
776
782
  parseJson: (s) => JSON.parse(s),
783
+ int: (x) => parseInt(String(x)) || 0,
784
+ float: (x) => parseFloat(String(x)) || 0.0,
777
785
 
778
786
  exec: (cmd) => {
779
787
  try {
@@ -784,7 +792,7 @@ class FazerRuntime {
784
792
  },
785
793
 
786
794
  server: (port, handlerName) => {
787
- const srv = http.createServer((req, res) => {
795
+ const srv = http.createServer(async (req, res) => {
788
796
  // We need to call the Fazer function `handlerName`
789
797
  // But `this._call` requires scope context.
790
798
  // This is tricky in a sync interpreter loop.
@@ -811,7 +819,7 @@ class FazerRuntime {
811
819
  try {
812
820
  const inner = new Scope(fn.closure);
813
821
  inner.set(fn.params[0], reqObj, true);
814
- const result = this._execBlock(fn.body, inner);
822
+ const result = await this._execBlock(fn.body, inner);
815
823
  const out = (result instanceof ReturnSignal) ? result.value : result;
816
824
 
817
825
  const status = (out && out.status) || 200;
@@ -830,6 +838,230 @@ class FazerRuntime {
830
838
  console.log(`Server listening on port ${port}`);
831
839
  },
832
840
 
841
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, Number(ms))),
842
+
843
+ fetch: async (url, opts = {}) => {
844
+ return new Promise((resolve, reject) => {
845
+ const req = https.request(url, { method: opts.method || "GET", headers: opts.headers || {} }, (res) => {
846
+ let data = "";
847
+ res.on("data", (chunk) => data += chunk);
848
+ res.on("end", () => resolve({ status: res.statusCode, body: data, headers: res.headers }));
849
+ });
850
+ req.on("error", reject);
851
+ if (opts.body) req.write(String(opts.body));
852
+ req.end();
853
+ });
854
+ },
855
+
856
+ discord: (token) => {
857
+ if (!WebSocket) throw new FazerError("WebSocket module (ws) not found. Install it to use discord.");
858
+ const listeners = {};
859
+ const ws = new WebSocket("wss://gateway.discord.gg/?v=10&encoding=json");
860
+
861
+ ws.on("open", () => {
862
+ ws.send(JSON.stringify({
863
+ op: 2,
864
+ d: { token: String(token), intents: 33280, properties: { os: "fazer", browser: "fazer", device: "fazer" } }
865
+ }));
866
+ });
867
+
868
+ ws.on("message", async (data) => {
869
+ const p = JSON.parse(data);
870
+ if (p.op === 10) {
871
+ setInterval(() => ws.send(JSON.stringify({ op: 1, d: null })), p.d.heartbeat_interval);
872
+ }
873
+ if (p.t === "MESSAGE_CREATE") {
874
+ const handlers = listeners["message"];
875
+ if (handlers) {
876
+ for (const fn of handlers) {
877
+ try {
878
+ await this._call(fn, [p.d], this.global);
879
+ } catch (e) { console.error("Discord handler error:", e); }
880
+ }
881
+ }
882
+ }
883
+ });
884
+
885
+ return {
886
+ on: (ev, fn) => { listeners[ev] = listeners[ev] || []; listeners[ev].push(fn); },
887
+ send: async (chanId, content) => {
888
+ const body = JSON.stringify({ content: String(content) });
889
+ return new Promise((resolve) => {
890
+ const req = https.request(`https://discord.com/api/v10/channels/${chanId}/messages`, {
891
+ method: "POST",
892
+ headers: { "Authorization": `Bot ${token}`, "Content-Type": "application/json" }
893
+ }, (res) => {
894
+ let d = "";
895
+ res.on("data", c => d += c);
896
+ res.on("end", () => resolve(JSON.parse(d)));
897
+ });
898
+ req.write(body);
899
+ req.end();
900
+ });
901
+ }
902
+ };
903
+ },
904
+
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
+
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
+
952
+ menu: async (options) => {
953
+ if (!Array.isArray(options)) throw new FazerError("menu expects a list of options");
954
+ options.forEach((o, i) => console.log(`[${i+1}] ${o}`));
955
+ const readline = require("readline");
956
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
957
+ return new Promise(resolve => {
958
+ rl.question("Choice > ", (ans) => {
959
+ rl.close();
960
+ const n = Number(ans);
961
+ if (isNaN(n) || n < 1 || n > options.length) resolve(null);
962
+ else resolve(options[n-1]);
963
+ });
964
+ });
965
+ },
966
+
967
+ notify: (title, message) => {
968
+ try {
969
+ if (process.platform === "win32") {
970
+ const psScript = `
971
+ [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
972
+ $obj = New-Object System.Windows.Forms.NotifyIcon
973
+ $obj.Icon = [System.Drawing.SystemIcons]::Information
974
+ $obj.Visible = $true
975
+ $obj.ShowBalloonTip(3000, "${String(title).replace(/"/g, '`"')}", "${String(message).replace(/"/g, '`"')}", [System.Windows.Forms.ToolTipIcon]::Info)
976
+ Start-Sleep -s 3
977
+ $obj.Visible = $false
978
+ `;
979
+ require("child_process").execSync("powershell -command \"" + psScript.replace(/\n/g, ";") + "\"");
980
+ }
981
+ } catch(e) {}
982
+ return null;
983
+ },
984
+
985
+ exec: (cmd) => {
986
+ try {
987
+ return require("child_process").execSync(String(cmd), { encoding: "utf8" }).trim();
988
+ } catch (e) {
989
+ throw new FazerError("exec failed: " + e.message);
990
+ }
991
+ },
992
+
993
+ style: (text, color) => {
994
+ const codes = {
995
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
996
+ red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m",
997
+ blue: "\x1b[34m", magenta: "\x1b[35m", cyan: "\x1b[36m", white: "\x1b[37m"
998
+ };
999
+ return (codes[String(color)] || "") + String(text) + codes.reset;
1000
+ },
1001
+
1002
+ server: async (port, handler) => {
1003
+ const http = require("http");
1004
+ const srv = http.createServer(async (req, res) => {
1005
+ const bodyParts = [];
1006
+ for await (const chunk of req) bodyParts.push(chunk);
1007
+ const bodyStr = Buffer.concat(bodyParts).toString();
1008
+
1009
+ const reqObj = {
1010
+ method: req.method,
1011
+ url: req.url,
1012
+ headers: req.headers,
1013
+ body: bodyStr
1014
+ };
1015
+
1016
+ try {
1017
+ let result = null;
1018
+ if (typeof handler === "object" && handler !== null && !handler.__fnref__) {
1019
+ const url = req.url.split("?")[0];
1020
+ if (handler[url]) {
1021
+ const h = handler[url];
1022
+ if (typeof h === "function" || (typeof h === "object" && h.__fnref__)) {
1023
+ result = await this._call(h, [reqObj], this.global);
1024
+ } else {
1025
+ result = h;
1026
+ }
1027
+ } else {
1028
+ result = { status: 404, body: "Not Found" };
1029
+ }
1030
+ } else {
1031
+ result = await this._call(handler, [reqObj], this.global);
1032
+ }
1033
+
1034
+ let status = 200;
1035
+ let headers = { "Content-Type": "text/plain" };
1036
+ let resBody = "";
1037
+
1038
+ if (typeof result === "object" && result !== null) {
1039
+ if (result.status) status = Number(result.status);
1040
+ if (result.headers) headers = { ...headers, ...result.headers };
1041
+ if (result.body) resBody = String(result.body);
1042
+ else if (result.status === undefined) resBody = JSON.stringify(result);
1043
+ } else {
1044
+ resBody = String(result);
1045
+ }
1046
+
1047
+ res.writeHead(status, headers);
1048
+ res.end(resBody);
1049
+ } catch (e) {
1050
+ res.writeHead(500);
1051
+ res.end("Internal Server Error: " + e.message);
1052
+ }
1053
+ });
1054
+
1055
+ return new Promise((resolve) => {
1056
+ srv.listen(Number(port), () => {
1057
+ console.log(`Server listening on port ${port}`);
1058
+ resolve({
1059
+ close: () => { srv.close(); return null; }
1060
+ });
1061
+ });
1062
+ });
1063
+ },
1064
+
833
1065
  argv: argvFn,
834
1066
  env: envFn,
835
1067
  cwd: cwdFn,
@@ -840,28 +1072,28 @@ class FazerRuntime {
840
1072
  for (const [k, v] of Object.entries(builtins)) this.global.set(k, v, false);
841
1073
  }
842
1074
 
843
- run(ast) {
1075
+ async run(ast) {
844
1076
  try {
845
- return this._execBlock(ast, this.global);
1077
+ return await this._execBlock(ast, this.global);
846
1078
  } catch (e) {
847
1079
  throw e;
848
1080
  }
849
1081
  }
850
1082
 
851
- _execBlock(stmts, scope) {
1083
+ async _execBlock(stmts, scope) {
852
1084
  let last = null;
853
1085
  for (const s of stmts) {
854
- const v = this._execStmt(s, scope);
1086
+ const v = await this._execStmt(s, scope);
855
1087
  if (v instanceof ReturnSignal) return v;
856
1088
  last = v;
857
1089
  }
858
1090
  return last;
859
1091
  }
860
1092
 
861
- _execStmt(stmt, scope) {
1093
+ async _execStmt(stmt, scope) {
862
1094
  switch (stmt.type) {
863
1095
  case "assign": {
864
- const val = this._eval(stmt.value, scope);
1096
+ const val = await this._eval(stmt.value, scope);
865
1097
  if (scope.hasHere(stmt.name)) {
866
1098
  scope.assign(stmt.name, val);
867
1099
  } else {
@@ -870,7 +1102,7 @@ class FazerRuntime {
870
1102
  return val;
871
1103
  }
872
1104
  case "exprstmt":
873
- return this._eval(stmt.expr, scope);
1105
+ return await this._eval(stmt.expr, scope);
874
1106
 
875
1107
  case "fn": {
876
1108
  this.fns.set(stmt.name, { params: stmt.params, body: stmt.body, closure: scope });
@@ -879,18 +1111,18 @@ class FazerRuntime {
879
1111
  }
880
1112
 
881
1113
  case "return": {
882
- const v = stmt.value ? this._eval(stmt.value, scope) : null;
1114
+ const v = stmt.value ? await this._eval(stmt.value, scope) : null;
883
1115
  return new ReturnSignal(v);
884
1116
  }
885
1117
 
886
1118
  case "case": {
887
- const val = this._eval(stmt.expr, scope);
1119
+ const val = await this._eval(stmt.expr, scope);
888
1120
  for (const arm of stmt.arms) {
889
- const { matched, bindings } = this._matchPattern(val, arm.pat, scope);
1121
+ const { matched, bindings } = await this._matchPattern(val, arm.pat, scope);
890
1122
  if (matched) {
891
1123
  const inner = new Scope(scope);
892
1124
  for (const [k, v] of Object.entries(bindings)) inner.set(k, v, true);
893
- const out = this._execBlock(arm.body, inner);
1125
+ const out = await this._execBlock(arm.body, inner);
894
1126
  if (out instanceof ReturnSignal) return out;
895
1127
  return out;
896
1128
  }
@@ -903,7 +1135,7 @@ class FazerRuntime {
903
1135
  }
904
1136
  }
905
1137
 
906
- _eval(expr, scope) {
1138
+ async _eval(expr, scope) {
907
1139
  if (!expr) return null;
908
1140
 
909
1141
  switch (expr.type) {
@@ -915,14 +1147,17 @@ class FazerRuntime {
915
1147
  case "null":
916
1148
  return null;
917
1149
 
918
- case "list":
919
- return expr.items.map((it) => this._eval(it, scope));
1150
+ case "list": {
1151
+ const items = [];
1152
+ for (const it of expr.items) items.push(await this._eval(it, scope));
1153
+ return items;
1154
+ }
920
1155
 
921
1156
  case "map": {
922
1157
  const o = {};
923
1158
  for (const ent of expr.entries) {
924
- const k = this._eval(ent.key, scope);
925
- o[String(k)] = this._eval(ent.value, scope);
1159
+ const k = await this._eval(ent.key, scope);
1160
+ o[String(k)] = await this._eval(ent.value, scope);
926
1161
  }
927
1162
  return o;
928
1163
  }
@@ -931,33 +1166,31 @@ class FazerRuntime {
931
1166
  const cell = scope.get(expr.name);
932
1167
  if (!cell) throw new FazerError(`Undefined variable '${expr.name}'`);
933
1168
  const v = cell.value;
934
- // fnref is stored as {__fnref__: name} to avoid collisions with user objects
935
1169
  if (v && typeof v === "object" && v.__fnref__) return v;
936
1170
  return v;
937
1171
  }
938
1172
 
939
1173
  case "un": {
940
- const v = this._eval(expr.expr, scope);
1174
+ const v = await this._eval(expr.expr, scope);
941
1175
  if (expr.op === "not") return !truthy(v);
942
1176
  if (expr.op === "-") return -Number(v);
943
1177
  throw new FazerError(`Unknown unary op ${expr.op}`);
944
1178
  }
945
1179
 
946
1180
  case "bin": {
947
- // short-circuit for and/or
948
1181
  if (expr.op === "and") {
949
- const l = this._eval(expr.left, scope);
1182
+ const l = await this._eval(expr.left, scope);
950
1183
  if (!truthy(l)) return l;
951
- return this._eval(expr.right, scope);
1184
+ return await this._eval(expr.right, scope);
952
1185
  }
953
1186
  if (expr.op === "or") {
954
- const l = this._eval(expr.left, scope);
1187
+ const l = await this._eval(expr.left, scope);
955
1188
  if (truthy(l)) return l;
956
- return this._eval(expr.right, scope);
1189
+ return await this._eval(expr.right, scope);
957
1190
  }
958
1191
 
959
- const l = this._eval(expr.left, scope);
960
- const r = this._eval(expr.right, scope);
1192
+ const l = await this._eval(expr.left, scope);
1193
+ const r = await this._eval(expr.right, scope);
961
1194
 
962
1195
  switch (expr.op) {
963
1196
  case "+": return (typeof l === "string" || typeof r === "string") ? String(l) + String(r) : Number(l) + Number(r);
@@ -978,46 +1211,44 @@ class FazerRuntime {
978
1211
  }
979
1212
 
980
1213
  case "get": {
981
- const obj = this._eval(expr.obj, scope);
982
- const key = this._eval(expr.key, scope);
1214
+ const obj = await this._eval(expr.obj, scope);
1215
+ const key = await this._eval(expr.key, scope);
983
1216
  return (obj == null) ? null : obj[String(key)];
984
1217
  }
985
1218
 
986
1219
  case "idx": {
987
- const obj = this._eval(expr.obj, scope);
988
- const idx = this._eval(expr.idx, scope);
1220
+ const obj = await this._eval(expr.obj, scope);
1221
+ const idx = await this._eval(expr.idx, scope);
989
1222
  if (obj == null) return null;
990
1223
  if (Array.isArray(obj)) return obj[Number(idx)];
991
1224
  return obj[String(idx)];
992
1225
  }
993
1226
 
994
1227
  case "call": {
995
- const callee = this._eval(expr.callee, scope);
996
- const args = expr.args.map((a) => this._eval(a, scope));
997
- return this._call(callee, args, scope);
1228
+ const callee = await this._eval(expr.callee, scope);
1229
+ const args = [];
1230
+ for (const a of expr.args) args.push(await this._eval(a, scope));
1231
+ return await this._call(callee, args, scope);
998
1232
  }
999
1233
 
1000
1234
  case "pipe": {
1001
- // left →> right
1002
- // If right is a call: pass left as first arg
1003
- // If right is an identifier: treat as function name, call with left
1004
- // Otherwise: if right evaluates to function, call it with left
1005
- const leftVal = this._eval(expr.left, scope);
1235
+ const leftVal = await this._eval(expr.left, scope);
1006
1236
  const rightNode = expr.right;
1007
1237
 
1008
1238
  if (rightNode.type === "call") {
1009
- const callee = this._eval(rightNode.callee, scope);
1010
- const args = [leftVal, ...rightNode.args.map((a) => this._eval(a, scope))];
1011
- return this._call(callee, args, scope);
1239
+ const callee = await this._eval(rightNode.callee, scope);
1240
+ const args = [leftVal];
1241
+ for (const a of rightNode.args) args.push(await this._eval(a, scope));
1242
+ return await this._call(callee, args, scope);
1012
1243
  }
1013
1244
 
1014
1245
  if (rightNode.type === "ident") {
1015
- const fn = this._eval(rightNode, scope);
1016
- return this._call(fn, [leftVal], scope);
1246
+ const fn = await this._eval(rightNode, scope);
1247
+ return await this._call(fn, [leftVal], scope);
1017
1248
  }
1018
1249
 
1019
- const fn = this._eval(rightNode, scope);
1020
- return this._call(fn, [leftVal], scope);
1250
+ const fn = await this._eval(rightNode, scope);
1251
+ return await this._call(fn, [leftVal], scope);
1021
1252
  }
1022
1253
 
1023
1254
  default:
@@ -1025,11 +1256,22 @@ class FazerRuntime {
1025
1256
  }
1026
1257
  }
1027
1258
 
1028
- _call(callee, args, scope) {
1029
- // builtin JS function
1030
- if (typeof callee === "function") return callee(...args);
1259
+ async _call(callee, args, scope) {
1260
+ if (typeof callee === "function") return await callee(...args);
1261
+
1262
+ // Support calling by string name (e.g. from callbacks stored as strings)
1263
+ if (typeof callee === "string") {
1264
+ if (this.fns.has(callee)) {
1265
+ callee = { __fnref__: callee };
1266
+ } else {
1267
+ // Try to look up variable in scope, maybe it's a function ref
1268
+ const cell = scope.get(callee);
1269
+ if (cell && cell.value && cell.value.__fnref__) {
1270
+ callee = cell.value;
1271
+ }
1272
+ }
1273
+ }
1031
1274
 
1032
- // function reference stored as {__fnref__: "name"}
1033
1275
  if (callee && typeof callee === "object" && callee.__fnref__) {
1034
1276
  const name = callee.__fnref__;
1035
1277
  const fn = this.fns.get(name);
@@ -1039,7 +1281,7 @@ class FazerRuntime {
1039
1281
  }
1040
1282
  const inner = new Scope(fn.closure);
1041
1283
  for (let i = 0; i < fn.params.length; i++) inner.set(fn.params[i], args[i], true);
1042
- const out = this._execBlock(fn.body, inner);
1284
+ const out = await this._execBlock(fn.body, inner);
1043
1285
  if (out instanceof ReturnSignal) return out.value;
1044
1286
  return out;
1045
1287
  }
@@ -1047,8 +1289,7 @@ class FazerRuntime {
1047
1289
  throw new FazerError(`Value is not callable`);
1048
1290
  }
1049
1291
 
1050
- _matchPattern(value, pat, scope) {
1051
- // returns { matched, bindings }
1292
+ async _matchPattern(value, pat, scope) {
1052
1293
  if (!pat) return { matched: false, bindings: {} };
1053
1294
 
1054
1295
  if (pat.type === "else") return { matched: true, bindings: {} };
@@ -1059,24 +1300,25 @@ class FazerRuntime {
1059
1300
  }
1060
1301
 
1061
1302
  if (pat.type === "cmpPat") {
1062
- const rhs = this._eval(pat.rhs, scope);
1063
- const l = value;
1064
- const r = rhs;
1065
- switch (pat.op) {
1066
- case "==": return { matched: deepEqual(l, r), bindings: {} };
1067
- case "!=": return { matched: !deepEqual(l, r), bindings: {} };
1068
- case ">": return { matched: Number(l) > Number(r), bindings: {} };
1069
- case "<": return { matched: Number(l) < Number(r), bindings: {} };
1070
- case ">=": return { matched: Number(l) >= Number(r), bindings: {} };
1071
- case "<=": return { matched: Number(l) <= Number(r), bindings: {} };
1072
- default: return { matched: false, bindings: {} };
1073
- }
1303
+ const rhs = await this._eval(pat.rhs, scope);
1304
+ const op = pat.op;
1305
+ let m = false;
1306
+ if (op === "==") m = deepEqual(value, rhs);
1307
+ else if (op === "!=") m = !deepEqual(value, rhs);
1308
+ else if (op === ">") m = (value > rhs);
1309
+ else if (op === "<") m = (value < rhs);
1310
+ else if (op === ">=") m = (value >= rhs);
1311
+ else if (op === "<=") m = (value <= rhs);
1312
+
1313
+ return { matched: m, bindings: {} };
1314
+ }
1315
+
1316
+ if (pat.type === "num" || pat.type === "str" || pat.type === "bool") {
1317
+ return { matched: deepEqual(value, pat.value), bindings: {} };
1074
1318
  }
1075
1319
 
1076
- // literal patterns
1077
- if (pat.type === "num" || pat.type === "str" || pat.type === "bool" || pat.type === "null") {
1078
- const p = pat.type === "null" ? null : pat.value;
1079
- return { matched: deepEqual(value, p), bindings: {} };
1320
+ if (pat.type === "null") {
1321
+ return { matched: value === null, bindings: {} };
1080
1322
  }
1081
1323
 
1082
1324
  return { matched: false, bindings: {} };
@@ -1151,29 +1393,176 @@ function prettyError(err, filename, code) {
1151
1393
  ].join("\n");
1152
1394
  }
1153
1395
 
1396
+ /* ────────────────────────────────────────────────────────────────────────── */
1397
+ /* REPL */
1398
+ /* ────────────────────────────────────────────────────────────────────────── */
1399
+
1400
+ async function repl() {
1401
+ const readline = require("readline");
1402
+ const rl = readline.createInterface({
1403
+ input: process.stdin,
1404
+ output: process.stdout,
1405
+ prompt: "\x1b[36mfz>\x1b[0m "
1406
+ });
1407
+
1408
+ const version = require("./package.json").version;
1409
+ const arch = process.arch;
1410
+ const platform = process.platform;
1411
+
1412
+ process.title = `Fazer v${version}`;
1413
+
1414
+ console.log(`Fazer v${version} (${platform}-${arch})`);
1415
+ console.log(`Type "help", "copyright" or "license" for more information.`);
1416
+ console.log(`Type "load('file.fz')" to execute a script.`);
1417
+
1418
+ const rt = new FazerRuntime({ filename: "<repl>", args: [] });
1419
+
1420
+ // Add a helper to run files
1421
+ rt.global.set("load", (p) => {
1422
+ const pAbs = path.resolve(String(p));
1423
+ if (!fs.existsSync(pAbs)) throw new FazerError("File not found: " + p);
1424
+ const code = fs.readFileSync(pAbs, "utf8");
1425
+ const lex = lexer.tokenize(code);
1426
+ if (lex.errors.length) throw new FazerError("Lexer error: " + lex.errors[0].message);
1427
+ const parser = new FazerParser();
1428
+ parser.input = lex.tokens;
1429
+ const ast = parser.program();
1430
+ if (parser.errors.length) throw new FazerError("Parser error: " + parser.errors[0].message);
1431
+ return rt.run(ast);
1432
+ }, false);
1433
+
1434
+ rl.prompt();
1435
+
1436
+ for await (const line of rl) {
1437
+ const code = line.trim();
1438
+ if (code === "exit") break;
1439
+ if (code === "") {
1440
+ rl.prompt();
1441
+ continue;
1442
+ }
1443
+
1444
+ try {
1445
+ const lex = lexer.tokenize(code);
1446
+ if (lex.errors.length) {
1447
+ console.error("Syntax Error: " + lex.errors[0].message);
1448
+ } else {
1449
+ const parser = new FazerParser();
1450
+ parser.input = lex.tokens;
1451
+ const ast = parser.program();
1452
+
1453
+ if (parser.errors.length) {
1454
+ console.error("Parse Error: " + parser.errors[0].message);
1455
+ } else {
1456
+ // Execute with existing global scope
1457
+ const res = await rt.run(ast);
1458
+ if (res !== null && res !== undefined) {
1459
+ const builtins = rt.global.get("__builtins__");
1460
+ if (builtins && builtins.style) {
1461
+ console.log(builtins.style(res, "green"));
1462
+ } else {
1463
+ console.log("\x1b[32m" + String(res) + "\x1b[0m");
1464
+ }
1465
+ }
1466
+ }
1467
+ }
1468
+ } catch (e) {
1469
+ console.error("\x1b[31mError: " + (e.message || e) + "\x1b[0m");
1470
+ }
1471
+ rl.prompt();
1472
+ }
1473
+ }
1474
+
1154
1475
  /* ────────────────────────────────────────────────────────────────────────── */
1155
1476
  /* CLI */
1156
1477
  /* ────────────────────────────────────────────────────────────────────────── */
1157
1478
 
1158
1479
  function usage() {
1159
- console.log("Usage:");
1160
- console.log(" fazer run <file.fz> [-- args...]");
1161
- process.exit(1);
1480
+ console.log(`
1481
+ Fazer v${require("./package.json").version} The next-gen pipe-based language.
1482
+
1483
+ Usage:
1484
+ fazer Start interactive shell (REPL)
1485
+ fazer <file.fz> [args...] Run a Fazer script
1486
+ fazer run <file.fz> Run a Fazer script (explicit)
1487
+
1488
+ Flags:
1489
+ --help, -h Show this help message
1490
+ --version, -v Show version
1491
+ --license Show license information
1492
+ `);
1493
+ process.exit(0);
1494
+ }
1495
+
1496
+ function printLicense() {
1497
+ console.log(`
1498
+ Fazer Language
1499
+ Copyright (c) 2026 L'EMPRISE
1500
+
1501
+ Permission is hereby granted, free of charge, to any person obtaining a copy
1502
+ of this software and associated documentation files (the "Software"), to deal
1503
+ in the Software without restriction, including without limitation the rights
1504
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1505
+ copies of the Software, and to permit persons to whom the Software is
1506
+ furnished to do so, subject to the following conditions:
1507
+
1508
+ The above copyright notice and this permission notice shall be included in all
1509
+ copies or substantial portions of the Software.
1510
+
1511
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1512
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1513
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1514
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1515
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1516
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1517
+ SOFTWARE.
1518
+ `);
1519
+ process.exit(0);
1162
1520
  }
1163
1521
 
1164
- function main() {
1522
+ async function main() {
1165
1523
  const argv = process.argv.slice(2);
1166
- if (argv.length === 0) usage();
1524
+
1525
+ if (argv.length === 0) {
1526
+ await repl();
1527
+ return;
1528
+ }
1167
1529
 
1168
1530
  const cmd = argv[0];
1169
1531
 
1170
- if (cmd !== "run") usage();
1171
- const fileArg = argv[1];
1172
- if (!fileArg) usage();
1532
+ if (cmd === "--help" || cmd === "-h") {
1533
+ usage();
1534
+ }
1535
+ if (cmd === "--version" || cmd === "-v") {
1536
+ console.log(`Fazer v${require("./package.json").version}`);
1537
+ process.exit(0);
1538
+ }
1539
+ if (cmd === "--license") {
1540
+ printLicense();
1541
+ }
1173
1542
 
1174
- // forward args after "--" to the script
1175
- const sep = argv.indexOf("--");
1176
- const forwarded = sep >= 0 ? argv.slice(sep + 1) : [];
1543
+ let fileArg = null;
1544
+ let forwarded = [];
1545
+
1546
+ // Logic:
1547
+ // 1. fazer run file.fz ...
1548
+ // 2. fazer file.fz ...
1549
+
1550
+ if (cmd === "run") {
1551
+ fileArg = argv[1];
1552
+ if (!fileArg) usage();
1553
+ const sep = argv.indexOf("--");
1554
+ forwarded = sep >= 0 ? argv.slice(sep + 1) : [];
1555
+ } else {
1556
+ // Check if cmd looks like a file or we treat it as one
1557
+ if (cmd.endsWith(".fz") || fs.existsSync(cmd)) {
1558
+ fileArg = cmd;
1559
+ // All subsequent args are passed to script
1560
+ forwarded = argv.slice(1);
1561
+ } else {
1562
+ console.error(`Unknown command or file not found: '${cmd}'\n`);
1563
+ usage();
1564
+ }
1565
+ }
1177
1566
 
1178
1567
  const filePath = path.resolve(fileArg);
1179
1568
  if (!fs.existsSync(filePath)) {
@@ -1203,7 +1592,7 @@ function main() {
1203
1592
 
1204
1593
  const rt = new FazerRuntime({ argv: forwarded, filename: filePath, code });
1205
1594
  try {
1206
- rt.run(ast);
1595
+ await rt.run(ast);
1207
1596
  } catch (err) {
1208
1597
  if (err instanceof FazerError) {
1209
1598
  console.error(prettyError(err, filePath, code));
@@ -1225,4 +1614,4 @@ module.exports = {
1225
1614
  locOf
1226
1615
  };
1227
1616
 
1228
- if (require.main === module) main();
1617
+ if (require.main === module) main().catch(e => { console.error(e); process.exit(1); });