arc-lang 0.6.0 → 0.6.1
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/dist/interpreter.js +378 -56
- package/dist/lexer.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/interpreter.js
CHANGED
|
@@ -170,6 +170,7 @@ function jsToArc(v) {
|
|
|
170
170
|
}
|
|
171
171
|
return String(v);
|
|
172
172
|
}
|
|
173
|
+
let callDepth = 0;
|
|
173
174
|
function resolveAsync(v) {
|
|
174
175
|
if (v && typeof v === "object" && "__async" in v) {
|
|
175
176
|
return v.thunk();
|
|
@@ -356,7 +357,7 @@ function makePrelude(env) {
|
|
|
356
357
|
time_ms: () => Date.now(),
|
|
357
358
|
// --- crypto natives ---
|
|
358
359
|
crypto_hash: (algorithm, data) => {
|
|
359
|
-
return nodeCrypto.createHash(algorithm).update(data).digest("hex");
|
|
360
|
+
return nodeCrypto.createHash(algorithm).update(data == null ? "" : data).digest("hex");
|
|
360
361
|
},
|
|
361
362
|
crypto_hmac: (algorithm, key, data) => {
|
|
362
363
|
return nodeCrypto.createHmac(algorithm, key).update(data).digest("hex");
|
|
@@ -371,7 +372,7 @@ function makePrelude(env) {
|
|
|
371
372
|
return nodeCrypto.randomInt(lo, hi + 1);
|
|
372
373
|
},
|
|
373
374
|
crypto_uuid: () => nodeCrypto.randomUUID(),
|
|
374
|
-
crypto_encode_base64: (s) => Buffer.from(s).toString("base64"),
|
|
375
|
+
crypto_encode_base64: (s) => Buffer.from(s == null ? "" : s).toString("base64"),
|
|
375
376
|
crypto_decode_base64: (s) => Buffer.from(s, "base64").toString("utf-8"),
|
|
376
377
|
// --- net natives ---
|
|
377
378
|
net_url_parse: (url) => {
|
|
@@ -391,8 +392,15 @@ function makePrelude(env) {
|
|
|
391
392
|
}
|
|
392
393
|
},
|
|
393
394
|
net_url_encode: (s) => encodeURIComponent(s),
|
|
394
|
-
net_url_decode: (s) =>
|
|
395
|
+
net_url_decode: (s) => { try {
|
|
396
|
+
return decodeURIComponent(s);
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return null;
|
|
400
|
+
} },
|
|
395
401
|
net_query_parse: (s) => {
|
|
402
|
+
if (s == null)
|
|
403
|
+
return { __map: true, entries: new Map() };
|
|
396
404
|
const str = s.startsWith("?") ? s.slice(1) : s;
|
|
397
405
|
const params = new URLSearchParams(str);
|
|
398
406
|
const m = new Map();
|
|
@@ -409,6 +417,8 @@ function makePrelude(env) {
|
|
|
409
417
|
return "";
|
|
410
418
|
},
|
|
411
419
|
net_ip_is_valid: (s) => {
|
|
420
|
+
if (s == null)
|
|
421
|
+
return false;
|
|
412
422
|
const str = s;
|
|
413
423
|
// IPv4
|
|
414
424
|
const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(str);
|
|
@@ -455,8 +465,13 @@ function makePrelude(env) {
|
|
|
455
465
|
},
|
|
456
466
|
error_try: (fn) => {
|
|
457
467
|
try {
|
|
458
|
-
|
|
459
|
-
|
|
468
|
+
if (typeof fn !== "function" && !(fn && typeof fn === "object" && "__fn" in fn)) {
|
|
469
|
+
const errMap = new Map();
|
|
470
|
+
errMap.set("ok", false);
|
|
471
|
+
errMap.set("error", "argument is not a function");
|
|
472
|
+
return { __map: true, entries: errMap };
|
|
473
|
+
}
|
|
474
|
+
const result = typeof fn === "function" ? fn() : callFn(fn, []);
|
|
460
475
|
const okMap = new Map();
|
|
461
476
|
okMap.set("ok", true);
|
|
462
477
|
okMap.set("value", result);
|
|
@@ -664,7 +679,8 @@ function makePrelude(env) {
|
|
|
664
679
|
const fmt = format;
|
|
665
680
|
// Support ISO format and custom format tokens
|
|
666
681
|
if (fmt === "ISO" || fmt === "iso") {
|
|
667
|
-
|
|
682
|
+
const t = new Date(s).getTime();
|
|
683
|
+
return isNaN(t) ? null : t;
|
|
668
684
|
}
|
|
669
685
|
// Parse using format tokens: YYYY, MM, DD, hh, mm, ss
|
|
670
686
|
let year = 2000, month = 1, day = 1, hour = 0, min = 0, sec = 0;
|
|
@@ -705,21 +721,22 @@ function makePrelude(env) {
|
|
|
705
721
|
si++;
|
|
706
722
|
}
|
|
707
723
|
}
|
|
708
|
-
|
|
724
|
+
const result = new Date(year, month - 1, day, hour, min, sec).getTime();
|
|
725
|
+
return isNaN(result) ? null : result;
|
|
709
726
|
},
|
|
710
727
|
__builtin_date_format: (ts, fmt) => {
|
|
711
728
|
const d = new Date(ts);
|
|
712
729
|
let result = fmt;
|
|
713
|
-
result = result.
|
|
714
|
-
result = result.
|
|
715
|
-
result = result.
|
|
716
|
-
result = result.
|
|
717
|
-
result = result.
|
|
718
|
-
result = result.
|
|
730
|
+
result = result.replaceAll("YYYY", String(d.getFullYear()));
|
|
731
|
+
result = result.replaceAll("MM", String(d.getMonth() + 1).padStart(2, "0"));
|
|
732
|
+
result = result.replaceAll("DD", String(d.getDate()).padStart(2, "0"));
|
|
733
|
+
result = result.replaceAll("hh", String(d.getHours()).padStart(2, "0"));
|
|
734
|
+
result = result.replaceAll("mm", String(d.getMinutes()).padStart(2, "0"));
|
|
735
|
+
result = result.replaceAll("ss", String(d.getSeconds()).padStart(2, "0"));
|
|
719
736
|
return result;
|
|
720
737
|
},
|
|
721
738
|
__builtin_date_to_iso: (ts) => new Date(ts).toISOString(),
|
|
722
|
-
__builtin_date_from_iso: (s) => new Date(s).getTime(),
|
|
739
|
+
__builtin_date_from_iso: (s) => { const t = new Date(s).getTime(); return isNaN(t) ? null : t; },
|
|
723
740
|
// --- os natives ---
|
|
724
741
|
__native: (name, ...args) => {
|
|
725
742
|
const cmd = name;
|
|
@@ -737,6 +754,13 @@ function makePrelude(env) {
|
|
|
737
754
|
entries.set(k, toArcValue(val));
|
|
738
755
|
return { __map: true, entries };
|
|
739
756
|
}
|
|
757
|
+
if (typeof v === "object" && v !== null && !("__map" in v)) {
|
|
758
|
+
// Convert plain objects to MapValue to prevent re-evaluation as Arc code
|
|
759
|
+
const entries = new Map();
|
|
760
|
+
for (const [k, val] of Object.entries(v))
|
|
761
|
+
entries.set(k, toArcValue(val));
|
|
762
|
+
return { __map: true, entries };
|
|
763
|
+
}
|
|
740
764
|
return v;
|
|
741
765
|
}
|
|
742
766
|
// Convert Arc MapValue back to raw Maps for stringifying
|
|
@@ -774,7 +798,6 @@ function makePrelude(env) {
|
|
|
774
798
|
case "os.temp_dir": return nodeOs.tmpdir();
|
|
775
799
|
case "os.list_dir": {
|
|
776
800
|
try {
|
|
777
|
-
const fs = require("fs");
|
|
778
801
|
return nodeFs.readdirSync(args[0]);
|
|
779
802
|
}
|
|
780
803
|
catch {
|
|
@@ -783,7 +806,6 @@ function makePrelude(env) {
|
|
|
783
806
|
}
|
|
784
807
|
case "os.is_file": {
|
|
785
808
|
try {
|
|
786
|
-
const fs = require("fs");
|
|
787
809
|
return nodeFs.statSync(args[0]).isFile();
|
|
788
810
|
}
|
|
789
811
|
catch {
|
|
@@ -792,7 +814,6 @@ function makePrelude(env) {
|
|
|
792
814
|
}
|
|
793
815
|
case "os.is_dir": {
|
|
794
816
|
try {
|
|
795
|
-
const fs = require("fs");
|
|
796
817
|
return nodeFs.statSync(args[0]).isDirectory();
|
|
797
818
|
}
|
|
798
819
|
catch {
|
|
@@ -801,7 +822,6 @@ function makePrelude(env) {
|
|
|
801
822
|
}
|
|
802
823
|
case "os.mkdir": {
|
|
803
824
|
try {
|
|
804
|
-
const fs = require("fs");
|
|
805
825
|
nodeFs.mkdirSync(args[0], { recursive: true });
|
|
806
826
|
return true;
|
|
807
827
|
}
|
|
@@ -811,7 +831,6 @@ function makePrelude(env) {
|
|
|
811
831
|
}
|
|
812
832
|
case "os.rmdir": {
|
|
813
833
|
try {
|
|
814
|
-
const fs = require("fs");
|
|
815
834
|
nodeFs.rmdirSync(args[0]);
|
|
816
835
|
return true;
|
|
817
836
|
}
|
|
@@ -821,7 +840,6 @@ function makePrelude(env) {
|
|
|
821
840
|
}
|
|
822
841
|
case "os.remove": {
|
|
823
842
|
try {
|
|
824
|
-
const fs = require("fs");
|
|
825
843
|
nodeFs.unlinkSync(args[0]);
|
|
826
844
|
return true;
|
|
827
845
|
}
|
|
@@ -831,7 +849,6 @@ function makePrelude(env) {
|
|
|
831
849
|
}
|
|
832
850
|
case "os.rename": {
|
|
833
851
|
try {
|
|
834
|
-
const fs = require("fs");
|
|
835
852
|
nodeFs.renameSync(args[0], args[1]);
|
|
836
853
|
return true;
|
|
837
854
|
}
|
|
@@ -841,7 +858,6 @@ function makePrelude(env) {
|
|
|
841
858
|
}
|
|
842
859
|
case "os.copy": {
|
|
843
860
|
try {
|
|
844
|
-
const fs = require("fs");
|
|
845
861
|
nodeFs.copyFileSync(args[0], args[1]);
|
|
846
862
|
return true;
|
|
847
863
|
}
|
|
@@ -851,7 +867,6 @@ function makePrelude(env) {
|
|
|
851
867
|
}
|
|
852
868
|
case "os.file_size": {
|
|
853
869
|
try {
|
|
854
|
-
const fs = require("fs");
|
|
855
870
|
return nodeFs.statSync(args[0]).size;
|
|
856
871
|
}
|
|
857
872
|
catch {
|
|
@@ -861,12 +876,11 @@ function makePrelude(env) {
|
|
|
861
876
|
case "os.exec": {
|
|
862
877
|
try {
|
|
863
878
|
const cmd = args[0];
|
|
864
|
-
// Command injection protection:
|
|
865
|
-
const dangerous =
|
|
879
|
+
// Command injection protection: block backticks and $() subshells
|
|
880
|
+
const dangerous = /`|\$\(|>\s*>|<\s*<|\beval\b|\bsource\b/;
|
|
866
881
|
if (dangerous.test(cmd)) {
|
|
867
882
|
throw new Error(`Potentially unsafe command (injection risk): ${cmd}`);
|
|
868
883
|
}
|
|
869
|
-
const cp = require("child_process");
|
|
870
884
|
return execSync(cmd, { encoding: "utf-8", timeout: 10000 }).trim();
|
|
871
885
|
}
|
|
872
886
|
catch (e) {
|
|
@@ -875,6 +889,19 @@ function makePrelude(env) {
|
|
|
875
889
|
return null;
|
|
876
890
|
}
|
|
877
891
|
}
|
|
892
|
+
case "regex.escape": {
|
|
893
|
+
const s = String(args[0] ?? "");
|
|
894
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
895
|
+
}
|
|
896
|
+
case "time.now": return Date.now();
|
|
897
|
+
case "time.sleep": {
|
|
898
|
+
const ms = args[0];
|
|
899
|
+
if (ms > 0) {
|
|
900
|
+
const end = Date.now() + ms;
|
|
901
|
+
while (Date.now() < end) { /* busy wait */ }
|
|
902
|
+
}
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
878
905
|
// --- prompt natives ---
|
|
879
906
|
case "prompt.token_count": {
|
|
880
907
|
const text = String(args[0] ?? "");
|
|
@@ -891,6 +918,8 @@ function makePrelude(env) {
|
|
|
891
918
|
case "prompt.chunk": {
|
|
892
919
|
const text = String(args[0] ?? "");
|
|
893
920
|
const maxTokens = args[1];
|
|
921
|
+
if (maxTokens <= 0)
|
|
922
|
+
return [];
|
|
894
923
|
const chunkSize = maxTokens * 4;
|
|
895
924
|
const chunks = [];
|
|
896
925
|
for (let i = 0; i < text.length; i += chunkSize) {
|
|
@@ -1013,6 +1042,60 @@ function makePrelude(env) {
|
|
|
1013
1042
|
case "math.exp": return Math.exp(args[0]);
|
|
1014
1043
|
case "math.hypot": return Math.hypot(args[0], args[1]);
|
|
1015
1044
|
case "math.cbrt": return Math.cbrt(args[0]);
|
|
1045
|
+
case "math.pow": return Math.pow(args[0], args[1]);
|
|
1046
|
+
case "math.ceil": return Math.ceil(args[0]);
|
|
1047
|
+
case "csv.parse": {
|
|
1048
|
+
const text = args[0].trim();
|
|
1049
|
+
if (text === "")
|
|
1050
|
+
return [];
|
|
1051
|
+
const rows = [];
|
|
1052
|
+
let i = 0;
|
|
1053
|
+
while (i < text.length) {
|
|
1054
|
+
const row = [];
|
|
1055
|
+
while (true) {
|
|
1056
|
+
let value = "";
|
|
1057
|
+
if (i < text.length && text[i] === '"') {
|
|
1058
|
+
i++; // skip opening quote
|
|
1059
|
+
while (i < text.length) {
|
|
1060
|
+
if (text[i] === '"') {
|
|
1061
|
+
if (i + 1 < text.length && text[i + 1] === '"') {
|
|
1062
|
+
value += '"';
|
|
1063
|
+
i += 2;
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
i++; // skip closing quote
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
value += text[i];
|
|
1072
|
+
i++;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
while (i < text.length && text[i] !== ',' && text[i] !== '\n' && text[i] !== '\r') {
|
|
1078
|
+
value += text[i];
|
|
1079
|
+
i++;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
row.push(value.trim());
|
|
1083
|
+
if (i < text.length && text[i] === ',') {
|
|
1084
|
+
i++;
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
break;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
// skip line ending
|
|
1091
|
+
if (i < text.length && text[i] === '\r')
|
|
1092
|
+
i++;
|
|
1093
|
+
if (i < text.length && text[i] === '\n')
|
|
1094
|
+
i++;
|
|
1095
|
+
rows.push(row);
|
|
1096
|
+
}
|
|
1097
|
+
return rows;
|
|
1098
|
+
}
|
|
1016
1099
|
// --- html natives ---
|
|
1017
1100
|
case "html.parse": {
|
|
1018
1101
|
const src = args[0];
|
|
@@ -1023,7 +1106,9 @@ function makePrelude(env) {
|
|
|
1023
1106
|
return m;
|
|
1024
1107
|
}
|
|
1025
1108
|
function decodeEntities(s) {
|
|
1026
|
-
return s.replace(
|
|
1109
|
+
return s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)))
|
|
1110
|
+
.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(parseInt(d, 10)))
|
|
1111
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'");
|
|
1027
1112
|
}
|
|
1028
1113
|
function parseHTML(html) {
|
|
1029
1114
|
const nodes = [];
|
|
@@ -1033,6 +1118,18 @@ function makePrelude(env) {
|
|
|
1033
1118
|
if (html[i + 1] === '/') {
|
|
1034
1119
|
break;
|
|
1035
1120
|
}
|
|
1121
|
+
// Skip comments
|
|
1122
|
+
if (html.slice(i, i + 4) === '<!--') {
|
|
1123
|
+
const endComment = html.indexOf('-->', i + 4);
|
|
1124
|
+
i = endComment !== -1 ? endComment + 3 : html.length;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
// Skip DOCTYPE
|
|
1128
|
+
if (html.slice(i, i + 9).toLowerCase() === '<!doctype') {
|
|
1129
|
+
const endDoc = html.indexOf('>', i);
|
|
1130
|
+
i = endDoc !== -1 ? endDoc + 1 : html.length;
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1036
1133
|
const tagMatch = html.slice(i).match(/^<([a-zA-Z][a-zA-Z0-9]*)/);
|
|
1037
1134
|
if (!tagMatch) {
|
|
1038
1135
|
nodes.push(decodeEntities(html[i]));
|
|
@@ -1088,15 +1185,32 @@ function makePrelude(env) {
|
|
|
1088
1185
|
i++;
|
|
1089
1186
|
const voidTags = new Set(['br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr']);
|
|
1090
1187
|
let children = [];
|
|
1188
|
+
const rawTags = new Set(['script', 'style']);
|
|
1091
1189
|
if (!selfClosing && !voidTags.has(tag)) {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1190
|
+
if (rawTags.has(tag)) {
|
|
1191
|
+
// Treat script/style content as raw text — don't parse inner content
|
|
1192
|
+
const closeTag = `</${tag}>`;
|
|
1193
|
+
const closeIdx = html.toLowerCase().indexOf(closeTag, i);
|
|
1194
|
+
if (closeIdx !== -1) {
|
|
1195
|
+
const rawText = html.slice(i, closeIdx);
|
|
1196
|
+
if (rawText.trim())
|
|
1197
|
+
children = [rawText];
|
|
1198
|
+
i = closeIdx + closeTag.length;
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
i = html.length;
|
|
1202
|
+
}
|
|
1097
1203
|
}
|
|
1098
1204
|
else {
|
|
1099
|
-
|
|
1205
|
+
children = parseHTML(html.slice(i));
|
|
1206
|
+
const closeTag = `</${tag}>`;
|
|
1207
|
+
const closeIdx = html.toLowerCase().indexOf(closeTag, i);
|
|
1208
|
+
if (closeIdx !== -1) {
|
|
1209
|
+
i = closeIdx + closeTag.length;
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
i = html.length;
|
|
1213
|
+
}
|
|
1100
1214
|
}
|
|
1101
1215
|
}
|
|
1102
1216
|
nodes.push(mkMap([['tag', tag], ['attrs', mkMap(attrEntries)], ['children', children]]));
|
|
@@ -1231,7 +1345,12 @@ function makePrelude(env) {
|
|
|
1231
1345
|
case "env.get": return process.env[args[0]] ?? null;
|
|
1232
1346
|
case "env.get_or": return process.env[args[0]] ?? args[1];
|
|
1233
1347
|
case "env.set": {
|
|
1234
|
-
|
|
1348
|
+
if (args[1] == null) {
|
|
1349
|
+
delete process.env[args[0]];
|
|
1350
|
+
}
|
|
1351
|
+
else {
|
|
1352
|
+
process.env[args[0]] = String(args[1]);
|
|
1353
|
+
}
|
|
1235
1354
|
return null;
|
|
1236
1355
|
}
|
|
1237
1356
|
case "env.remove": {
|
|
@@ -1263,13 +1382,34 @@ function makePrelude(env) {
|
|
|
1263
1382
|
// --- YAML natives ---
|
|
1264
1383
|
case "yaml.parse": {
|
|
1265
1384
|
const src = args[0];
|
|
1385
|
+
function yamlParseFlowMapping(s) {
|
|
1386
|
+
const inner = s.slice(1, -1).trim();
|
|
1387
|
+
const m = new Map();
|
|
1388
|
+
if (inner === "")
|
|
1389
|
+
return m;
|
|
1390
|
+
// Simple comma-split (doesn't handle nested structures)
|
|
1391
|
+
const parts = inner.split(",");
|
|
1392
|
+
for (const part of parts) {
|
|
1393
|
+
const colon = part.indexOf(":");
|
|
1394
|
+
if (colon > 0) {
|
|
1395
|
+
m.set(part.slice(0, colon).trim(), yamlParseValue(part.slice(colon + 1).trim()));
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return m;
|
|
1399
|
+
}
|
|
1400
|
+
function yamlParseFlowSequence(s) {
|
|
1401
|
+
const inner = s.slice(1, -1).trim();
|
|
1402
|
+
if (inner === "")
|
|
1403
|
+
return [];
|
|
1404
|
+
return inner.split(",").map(x => yamlParseValue(x.trim()));
|
|
1405
|
+
}
|
|
1266
1406
|
function yamlParseValue(s) {
|
|
1267
1407
|
s = s.trim();
|
|
1268
1408
|
if (s === "" || s === "~" || s === "null")
|
|
1269
1409
|
return null;
|
|
1270
|
-
if (s === "true" || s === "True" || s === "TRUE")
|
|
1410
|
+
if (s === "true" || s === "True" || s === "TRUE" || s === "yes" || s === "Yes" || s === "YES" || s === "on" || s === "On" || s === "ON")
|
|
1271
1411
|
return true;
|
|
1272
|
-
if (s === "false" || s === "False" || s === "FALSE")
|
|
1412
|
+
if (s === "false" || s === "False" || s === "FALSE" || s === "no" || s === "No" || s === "NO" || s === "off" || s === "Off" || s === "OFF")
|
|
1273
1413
|
return false;
|
|
1274
1414
|
if (/^-?\d+$/.test(s))
|
|
1275
1415
|
return parseInt(s, 10);
|
|
@@ -1277,6 +1417,10 @@ function makePrelude(env) {
|
|
|
1277
1417
|
return parseFloat(s);
|
|
1278
1418
|
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'")))
|
|
1279
1419
|
return s.slice(1, -1);
|
|
1420
|
+
if (s.startsWith("{") && s.endsWith("}"))
|
|
1421
|
+
return yamlParseFlowMapping(s);
|
|
1422
|
+
if (s.startsWith("[") && s.endsWith("]"))
|
|
1423
|
+
return yamlParseFlowSequence(s);
|
|
1280
1424
|
return s;
|
|
1281
1425
|
}
|
|
1282
1426
|
function yamlParse(lines, baseIndent) {
|
|
@@ -1300,13 +1444,36 @@ function makePrelude(env) {
|
|
|
1300
1444
|
continue;
|
|
1301
1445
|
}
|
|
1302
1446
|
const lt = line.trimStart();
|
|
1303
|
-
if (!lt.startsWith("- ")) {
|
|
1447
|
+
if (!lt.startsWith("- ") && lt !== "-") {
|
|
1304
1448
|
i++;
|
|
1305
1449
|
continue;
|
|
1306
1450
|
}
|
|
1307
|
-
const itemVal = lt.slice(2);
|
|
1451
|
+
const itemVal = lt === "-" ? "" : lt.slice(2);
|
|
1308
1452
|
const lineIndent = line.length - line.trimStart().length;
|
|
1309
1453
|
const childIndent = lineIndent + 2;
|
|
1454
|
+
// Handle nested list: `- - val`
|
|
1455
|
+
if (itemVal.startsWith("- ") || itemVal === "-") {
|
|
1456
|
+
const nestedLines = [" ".repeat(childIndent) + itemVal];
|
|
1457
|
+
let j = i + 1;
|
|
1458
|
+
while (j < lines.length) {
|
|
1459
|
+
const cl = lines[j];
|
|
1460
|
+
if (cl.trim() === "") {
|
|
1461
|
+
nestedLines.push(cl);
|
|
1462
|
+
j++;
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
const ci = cl.length - cl.trimStart().length;
|
|
1466
|
+
if (ci >= childIndent) {
|
|
1467
|
+
nestedLines.push(lines[j]);
|
|
1468
|
+
j++;
|
|
1469
|
+
}
|
|
1470
|
+
else
|
|
1471
|
+
break;
|
|
1472
|
+
}
|
|
1473
|
+
result.push(yamlParse(nestedLines, childIndent));
|
|
1474
|
+
i = j;
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1310
1477
|
const children = [];
|
|
1311
1478
|
let j = i + 1;
|
|
1312
1479
|
while (j < lines.length) {
|
|
@@ -1324,7 +1491,11 @@ function makePrelude(env) {
|
|
|
1324
1491
|
else
|
|
1325
1492
|
break;
|
|
1326
1493
|
}
|
|
1327
|
-
if (
|
|
1494
|
+
if (itemVal.trim() === "" && children.length === 0) {
|
|
1495
|
+
// Empty list item: `- ` or bare `-`
|
|
1496
|
+
result.push(null);
|
|
1497
|
+
}
|
|
1498
|
+
else if (children.length > 0 && children.some(c => c.trim() !== "" && c.trimStart().match(/^[^:]+:\s/))) {
|
|
1328
1499
|
if (itemVal.trim() !== "") {
|
|
1329
1500
|
const allLines = [" ".repeat(childIndent) + itemVal, ...children];
|
|
1330
1501
|
result.push(yamlParse(allLines, childIndent));
|
|
@@ -1403,10 +1574,14 @@ function makePrelude(env) {
|
|
|
1403
1574
|
else
|
|
1404
1575
|
break;
|
|
1405
1576
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1577
|
+
if (children.length === 0 || children.every(c => c.trim() === "")) {
|
|
1578
|
+
// No children → nil, not empty map
|
|
1579
|
+
result.set(key, null);
|
|
1580
|
+
}
|
|
1581
|
+
else {
|
|
1582
|
+
const childIndent = (children.find(c => c.trim() !== "")?.length ?? indent + 2) - (children.find(c => c.trim() !== "")?.trimStart().length ?? 0);
|
|
1583
|
+
result.set(key, yamlParse(children, childIndent));
|
|
1584
|
+
}
|
|
1410
1585
|
i = j;
|
|
1411
1586
|
}
|
|
1412
1587
|
else {
|
|
@@ -1471,12 +1646,50 @@ function makePrelude(env) {
|
|
|
1471
1646
|
const root = new Map();
|
|
1472
1647
|
let current = root;
|
|
1473
1648
|
const lines = src.split("\n");
|
|
1649
|
+
function tomlStripComment(s) {
|
|
1650
|
+
// Strip inline comments (not inside strings)
|
|
1651
|
+
let inStr = false;
|
|
1652
|
+
let strCh = "";
|
|
1653
|
+
for (let i = 0; i < s.length; i++) {
|
|
1654
|
+
if (inStr) {
|
|
1655
|
+
if (s[i] === strCh && s[i - 1] !== "\\")
|
|
1656
|
+
inStr = false;
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
if (s[i] === '"' || s[i] === "'") {
|
|
1660
|
+
inStr = true;
|
|
1661
|
+
strCh = s[i];
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (s[i] === "#")
|
|
1665
|
+
return s.slice(0, i).trim();
|
|
1666
|
+
}
|
|
1667
|
+
return s;
|
|
1668
|
+
}
|
|
1474
1669
|
function tomlParseValue(s) {
|
|
1475
|
-
s = s.trim();
|
|
1670
|
+
s = tomlStripComment(s).trim();
|
|
1476
1671
|
if (s === "true")
|
|
1477
1672
|
return true;
|
|
1478
1673
|
if (s === "false")
|
|
1479
1674
|
return false;
|
|
1675
|
+
if (s === "inf" || s === "+inf")
|
|
1676
|
+
return Infinity;
|
|
1677
|
+
if (s === "-inf")
|
|
1678
|
+
return -Infinity;
|
|
1679
|
+
if (s === "nan" || s === "+nan" || s === "-nan")
|
|
1680
|
+
return NaN;
|
|
1681
|
+
// Multiline basic strings
|
|
1682
|
+
if (s.startsWith('"""')) {
|
|
1683
|
+
const end = s.indexOf('"""', 3);
|
|
1684
|
+
if (end !== -1)
|
|
1685
|
+
return s.slice(3, end).replace(/^\n/, "").replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\\\/g, "\\");
|
|
1686
|
+
}
|
|
1687
|
+
// Multiline literal strings
|
|
1688
|
+
if (s.startsWith("'''")) {
|
|
1689
|
+
const end = s.indexOf("'''", 3);
|
|
1690
|
+
if (end !== -1)
|
|
1691
|
+
return s.slice(3, end).replace(/^\n/, "");
|
|
1692
|
+
}
|
|
1480
1693
|
if (s.startsWith('"') && s.endsWith('"'))
|
|
1481
1694
|
return s.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\\\/g, "\\");
|
|
1482
1695
|
if (s.startsWith("'") && s.endsWith("'"))
|
|
@@ -1574,8 +1787,8 @@ function makePrelude(env) {
|
|
|
1574
1787
|
}
|
|
1575
1788
|
return cur;
|
|
1576
1789
|
}
|
|
1577
|
-
for (
|
|
1578
|
-
const trimmed =
|
|
1790
|
+
for (let li = 0; li < lines.length; li++) {
|
|
1791
|
+
const trimmed = lines[li].trim();
|
|
1579
1792
|
if (trimmed === "" || trimmed.startsWith("#"))
|
|
1580
1793
|
continue;
|
|
1581
1794
|
const arrMatch = trimmed.match(/^\[\[([^\]]+)\]\]$/);
|
|
@@ -1600,8 +1813,44 @@ function makePrelude(env) {
|
|
|
1600
1813
|
const eq = trimmed.indexOf("=");
|
|
1601
1814
|
if (eq > 0) {
|
|
1602
1815
|
const key = trimmed.slice(0, eq).trim();
|
|
1603
|
-
|
|
1604
|
-
|
|
1816
|
+
let valStr = trimmed.slice(eq + 1).trim();
|
|
1817
|
+
// Handle multiline basic strings
|
|
1818
|
+
if (valStr.startsWith('"""') && !valStr.slice(3).includes('"""')) {
|
|
1819
|
+
while (li + 1 < lines.length && !lines[li + 1].includes('"""')) {
|
|
1820
|
+
li++;
|
|
1821
|
+
valStr += "\n" + lines[li];
|
|
1822
|
+
}
|
|
1823
|
+
if (li + 1 < lines.length) {
|
|
1824
|
+
li++;
|
|
1825
|
+
valStr += "\n" + lines[li];
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
// Handle multiline literal strings
|
|
1829
|
+
if (valStr.startsWith("'''") && !valStr.slice(3).includes("'''")) {
|
|
1830
|
+
while (li + 1 < lines.length && !lines[li + 1].includes("'''")) {
|
|
1831
|
+
li++;
|
|
1832
|
+
valStr += "\n" + lines[li];
|
|
1833
|
+
}
|
|
1834
|
+
if (li + 1 < lines.length) {
|
|
1835
|
+
li++;
|
|
1836
|
+
valStr += "\n" + lines[li];
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
const val = tomlParseValue(valStr);
|
|
1840
|
+
// Expand dotted keys: a.b.c = 1 → nested maps
|
|
1841
|
+
if (key.includes(".")) {
|
|
1842
|
+
const parts = key.split(".").map(k => k.trim());
|
|
1843
|
+
let target = current;
|
|
1844
|
+
for (let ki = 0; ki < parts.length - 1; ki++) {
|
|
1845
|
+
if (!target.has(parts[ki]))
|
|
1846
|
+
target.set(parts[ki], new Map());
|
|
1847
|
+
target = target.get(parts[ki]);
|
|
1848
|
+
}
|
|
1849
|
+
target.set(parts[parts.length - 1], val);
|
|
1850
|
+
}
|
|
1851
|
+
else {
|
|
1852
|
+
current.set(key, val);
|
|
1853
|
+
}
|
|
1605
1854
|
}
|
|
1606
1855
|
}
|
|
1607
1856
|
return toArcValue(root);
|
|
@@ -1661,6 +1910,10 @@ function makePrelude(env) {
|
|
|
1661
1910
|
}
|
|
1662
1911
|
return lines.join("\n");
|
|
1663
1912
|
}
|
|
1913
|
+
if (val === null || val === undefined)
|
|
1914
|
+
return "";
|
|
1915
|
+
if (!(val instanceof Map))
|
|
1916
|
+
return "";
|
|
1664
1917
|
return tomlStringifySection(val, "");
|
|
1665
1918
|
}
|
|
1666
1919
|
// --- log natives ---
|
|
@@ -1697,7 +1950,12 @@ function makePrelude(env) {
|
|
|
1697
1950
|
return null;
|
|
1698
1951
|
}
|
|
1699
1952
|
case "log.set_level": {
|
|
1700
|
-
|
|
1953
|
+
const lvl = args[0];
|
|
1954
|
+
const validLevels = ["debug", "info", "warn", "error", "fatal"];
|
|
1955
|
+
if (!validLevels.includes(lvl)) {
|
|
1956
|
+
throw new ArcRuntimeError(`Invalid log level '${lvl}'. Must be one of: ${validLevels.join(", ")}`, { code: ErrorCode.UNDEFINED_VARIABLE });
|
|
1957
|
+
}
|
|
1958
|
+
globalThis.__arc_log_level = lvl;
|
|
1701
1959
|
return null;
|
|
1702
1960
|
}
|
|
1703
1961
|
case "log.with": {
|
|
@@ -1726,6 +1984,8 @@ function makePrelude(env) {
|
|
|
1726
1984
|
embed_dot_product: (a, b) => {
|
|
1727
1985
|
if (!Array.isArray(a) || !Array.isArray(b))
|
|
1728
1986
|
return 0;
|
|
1987
|
+
if (a.length !== b.length)
|
|
1988
|
+
throw new Error(`Vector length mismatch: ${a.length} vs ${b.length}`);
|
|
1729
1989
|
let sum = 0;
|
|
1730
1990
|
for (let i = 0; i < a.length; i++)
|
|
1731
1991
|
sum += a[i] * b[i];
|
|
@@ -1742,6 +2002,8 @@ function makePrelude(env) {
|
|
|
1742
2002
|
embed_cosine_similarity: (a, b) => {
|
|
1743
2003
|
if (!Array.isArray(a) || !Array.isArray(b))
|
|
1744
2004
|
return 0;
|
|
2005
|
+
if (a.length !== b.length)
|
|
2006
|
+
throw new Error(`Vector length mismatch: ${a.length} vs ${b.length}`);
|
|
1745
2007
|
let dot = 0, magA = 0, magB = 0;
|
|
1746
2008
|
for (let i = 0; i < a.length; i++) {
|
|
1747
2009
|
const ai = a[i], bi = b[i];
|
|
@@ -1766,6 +2028,8 @@ function makePrelude(env) {
|
|
|
1766
2028
|
embed_euclidean_distance: (a, b) => {
|
|
1767
2029
|
if (!Array.isArray(a) || !Array.isArray(b))
|
|
1768
2030
|
return 0;
|
|
2031
|
+
if (a.length !== b.length)
|
|
2032
|
+
throw new Error(`Vector length mismatch: ${a.length} vs ${b.length}`);
|
|
1769
2033
|
let sum = 0;
|
|
1770
2034
|
for (let i = 0; i < a.length; i++) {
|
|
1771
2035
|
const d = a[i] - b[i];
|
|
@@ -1841,13 +2105,20 @@ function makePrelude(env) {
|
|
|
1841
2105
|
};
|
|
1842
2106
|
function callFn(fn, args) {
|
|
1843
2107
|
if (fn && typeof fn === "object" && "__fn" in fn) {
|
|
2108
|
+
if (++callDepth > 10000) {
|
|
2109
|
+
callDepth = 0;
|
|
2110
|
+
throw new ArcRuntimeError("Maximum call stack depth exceeded");
|
|
2111
|
+
}
|
|
1844
2112
|
const f = fn;
|
|
1845
2113
|
const fnEnv = new Env(f.closure);
|
|
1846
2114
|
bindParams(f, args, fnEnv, evalExpr);
|
|
1847
2115
|
try {
|
|
1848
|
-
|
|
2116
|
+
const result = evalExpr(f.body, fnEnv);
|
|
2117
|
+
callDepth--;
|
|
2118
|
+
return result;
|
|
1849
2119
|
}
|
|
1850
2120
|
catch (e) {
|
|
2121
|
+
callDepth--;
|
|
1851
2122
|
if (e instanceof ReturnSignal)
|
|
1852
2123
|
return e.value;
|
|
1853
2124
|
throw e;
|
|
@@ -1974,13 +2245,31 @@ function evalExpr(expr, env) {
|
|
|
1974
2245
|
switch (expr.op) {
|
|
1975
2246
|
case "+": {
|
|
1976
2247
|
if (typeof left === "string" || typeof right === "string") {
|
|
2248
|
+
if (left === null || right === null)
|
|
2249
|
+
throw new ArcRuntimeError(`TypeError: cannot add nil`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
1977
2250
|
return toStr(left) + toStr(right);
|
|
1978
2251
|
}
|
|
2252
|
+
if (left === null || right === null)
|
|
2253
|
+
throw new ArcRuntimeError(`TypeError: cannot add nil`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
2254
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
2255
|
+
throw new ArcRuntimeError(`TypeError: cannot add ${typeof left} and ${typeof right}`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
1979
2256
|
return left + right;
|
|
1980
2257
|
}
|
|
1981
|
-
case "-":
|
|
1982
|
-
|
|
2258
|
+
case "-": {
|
|
2259
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
2260
|
+
throw new ArcRuntimeError(`TypeError: cannot subtract non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
2261
|
+
return left - right;
|
|
2262
|
+
}
|
|
2263
|
+
case "*": {
|
|
2264
|
+
if (typeof left === "string" && typeof right === "number" && Number.isInteger(right) && right >= 0)
|
|
2265
|
+
return left.repeat(right);
|
|
2266
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
2267
|
+
throw new ArcRuntimeError(`TypeError: cannot multiply non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
2268
|
+
return left * right;
|
|
2269
|
+
}
|
|
1983
2270
|
case "/": {
|
|
2271
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
2272
|
+
throw new ArcRuntimeError(`TypeError: cannot divide non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
1984
2273
|
if (right === 0)
|
|
1985
2274
|
throw new ArcRuntimeError(`Division by zero`, {
|
|
1986
2275
|
code: ErrorCode.DIVISION_BY_ZERO, loc: expr.loc,
|
|
@@ -1989,6 +2278,8 @@ function evalExpr(expr, env) {
|
|
|
1989
2278
|
return left / right;
|
|
1990
2279
|
}
|
|
1991
2280
|
case "%": {
|
|
2281
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
2282
|
+
throw new ArcRuntimeError(`TypeError: cannot modulo non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
1992
2283
|
if (right === 0)
|
|
1993
2284
|
throw new ArcRuntimeError(`Modulo by zero`, {
|
|
1994
2285
|
code: ErrorCode.DIVISION_BY_ZERO, loc: expr.loc,
|
|
@@ -1996,7 +2287,11 @@ function evalExpr(expr, env) {
|
|
|
1996
2287
|
});
|
|
1997
2288
|
return left % right;
|
|
1998
2289
|
}
|
|
1999
|
-
case "**":
|
|
2290
|
+
case "**": {
|
|
2291
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
2292
|
+
throw new ArcRuntimeError(`TypeError: cannot exponentiate non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
|
|
2293
|
+
return Math.pow(left, right);
|
|
2294
|
+
}
|
|
2000
2295
|
case "==": return left === right;
|
|
2001
2296
|
case "!=": return left !== right;
|
|
2002
2297
|
case "<": return left < right;
|
|
@@ -2017,7 +2312,7 @@ function evalExpr(expr, env) {
|
|
|
2017
2312
|
const operand = evalExpr(expr.operand, env);
|
|
2018
2313
|
if (expr.op === "-")
|
|
2019
2314
|
return -operand;
|
|
2020
|
-
if (expr.op === "not")
|
|
2315
|
+
if (expr.op === "not" || expr.op === "!")
|
|
2021
2316
|
return !isTruthy(operand);
|
|
2022
2317
|
throw new Error(`Unknown unary op: ${expr.op}`);
|
|
2023
2318
|
}
|
|
@@ -2030,10 +2325,19 @@ function evalExpr(expr, env) {
|
|
|
2030
2325
|
}
|
|
2031
2326
|
else if (callee && typeof callee === "object" && "__fn" in callee) {
|
|
2032
2327
|
let fn = callee;
|
|
2328
|
+
if (++callDepth > 10000) {
|
|
2329
|
+
callDepth = 0;
|
|
2330
|
+
throw new ArcRuntimeError("Maximum call stack depth exceeded");
|
|
2331
|
+
}
|
|
2033
2332
|
// Tail call optimization loop: if the function body resolves to
|
|
2034
2333
|
// a tail call back to itself, reuse the frame instead of recursing
|
|
2334
|
+
let tcoIterations = 0;
|
|
2035
2335
|
try {
|
|
2036
2336
|
tailLoop: while (true) {
|
|
2337
|
+
if (++tcoIterations > 10000) {
|
|
2338
|
+
callDepth--;
|
|
2339
|
+
throw new ArcRuntimeError("Maximum call stack depth exceeded");
|
|
2340
|
+
}
|
|
2037
2341
|
const fnEnv = new Env(fn.closure);
|
|
2038
2342
|
bindParams(fn, args, fnEnv, evalExpr);
|
|
2039
2343
|
const bodyResult = evalExprTCO(fn.body, fnEnv, fn.name);
|
|
@@ -2046,8 +2350,10 @@ function evalExpr(expr, env) {
|
|
|
2046
2350
|
result = bodyResult;
|
|
2047
2351
|
break;
|
|
2048
2352
|
}
|
|
2353
|
+
callDepth--;
|
|
2049
2354
|
}
|
|
2050
2355
|
catch (e) {
|
|
2356
|
+
callDepth--;
|
|
2051
2357
|
if (e instanceof ReturnSignal) {
|
|
2052
2358
|
result = e.value;
|
|
2053
2359
|
}
|
|
@@ -2105,6 +2411,8 @@ function evalExpr(expr, env) {
|
|
|
2105
2411
|
case "IndexExpr": {
|
|
2106
2412
|
const obj = evalExpr(expr.object, env);
|
|
2107
2413
|
const idx = evalExpr(expr.index, env);
|
|
2414
|
+
if (typeof obj === "string" && typeof idx === "number")
|
|
2415
|
+
return idx >= 0 && idx < obj.length ? obj.charAt(idx) : null;
|
|
2108
2416
|
if (Array.isArray(obj) && typeof idx === "number")
|
|
2109
2417
|
return obj[idx] ?? null;
|
|
2110
2418
|
if (obj && typeof obj === "object" && "__map" in obj) {
|
|
@@ -2464,7 +2772,14 @@ export function interpret(program, onUse) {
|
|
|
2464
2772
|
onUse(stmt, env);
|
|
2465
2773
|
}
|
|
2466
2774
|
else {
|
|
2467
|
-
|
|
2775
|
+
try {
|
|
2776
|
+
evalStmt(stmt, env);
|
|
2777
|
+
}
|
|
2778
|
+
catch (e) {
|
|
2779
|
+
if (e instanceof ReturnSignal)
|
|
2780
|
+
throw new ArcRuntimeError("ret used outside of function");
|
|
2781
|
+
throw e;
|
|
2782
|
+
}
|
|
2468
2783
|
}
|
|
2469
2784
|
}
|
|
2470
2785
|
}
|
|
@@ -2476,7 +2791,14 @@ export function interpretWithEnv(program, env, onUse) {
|
|
|
2476
2791
|
result = null;
|
|
2477
2792
|
}
|
|
2478
2793
|
else {
|
|
2479
|
-
|
|
2794
|
+
try {
|
|
2795
|
+
result = evalStmt(stmt, env);
|
|
2796
|
+
}
|
|
2797
|
+
catch (e) {
|
|
2798
|
+
if (e instanceof ReturnSignal)
|
|
2799
|
+
throw new ArcRuntimeError("ret used outside of function");
|
|
2800
|
+
throw e;
|
|
2801
|
+
}
|
|
2480
2802
|
}
|
|
2481
2803
|
}
|
|
2482
2804
|
return result;
|
package/dist/lexer.js
CHANGED
|
@@ -377,6 +377,11 @@ export function lex(source) {
|
|
|
377
377
|
tokens.push(tok(TokenType.Neq, "!=", sl, sc));
|
|
378
378
|
continue;
|
|
379
379
|
}
|
|
380
|
+
if (ch === "!") {
|
|
381
|
+
advance();
|
|
382
|
+
tokens.push(tok(TokenType.Not, "!", sl, sc));
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
380
385
|
if (ch === "<" && peek(1) === "=") {
|
|
381
386
|
advance();
|
|
382
387
|
advance();
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED