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/LICENSE +21 -0
- package/README.md +28 -220
- package/docs/README.md +26 -0
- package/docs/examples.md +60 -0
- package/docs/getting-started.md +46 -0
- package/docs/stdlib.md +139 -0
- package/docs/syntax.md +86 -0
- package/fazer.js +475 -86
- package/package.json +22 -12
- package/tools/announce.fz +48 -0
- package/apply_icon.js +0 -28
- package/apply_icon_robust.js +0 -51
- package/bin/fazer.exe +0 -0
- package/package.json.bak +0 -44
- package/standalone.bundled.js +0 -12910
- package/standalone.js +0 -186
- package/test.fz +0 -92
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
|
-
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)
|
|
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
|
-
|
|
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 =
|
|
997
|
-
|
|
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
|
-
|
|
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
|
|
1011
|
-
|
|
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
|
-
|
|
1030
|
-
|
|
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
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
-
|
|
1077
|
-
|
|
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(
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
-
|
|
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
|
|
1171
|
-
|
|
1172
|
-
|
|
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
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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); });
|