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.
@@ -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) => decodeURIComponent(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
- const result = typeof fn === "function" ? fn() :
459
- (fn && typeof fn === "object" && "__fn" in fn) ? callFn(fn, []) : null;
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
- return new Date(s).getTime();
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
- return new Date(year, month - 1, day, hour, min, sec).getTime();
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.replace("YYYY", String(d.getFullYear()));
714
- result = result.replace("MM", String(d.getMonth() + 1).padStart(2, "0"));
715
- result = result.replace("DD", String(d.getDate()).padStart(2, "0"));
716
- result = result.replace("hh", String(d.getHours()).padStart(2, "0"));
717
- result = result.replace("mm", String(d.getMinutes()).padStart(2, "0"));
718
- result = result.replace("ss", String(d.getSeconds()).padStart(2, "0"));
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: reject commands with common shell injection patterns
865
- const dangerous = /[;&|`$]|\$\(|>\s*>|<\s*<|\beval\b|\bsource\b/;
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(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'");
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(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/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
- children = parseHTML(html.slice(i));
1093
- const closeTag = `</${tag}>`;
1094
- const closeIdx = html.toLowerCase().indexOf(closeTag, i);
1095
- if (closeIdx !== -1) {
1096
- i = closeIdx + closeTag.length;
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
- i = html.length;
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
- process.env[args[0]] = String(args[1]);
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 (children.length > 0 && children.some(c => c.trim() !== "" && c.trimStart().match(/^[^:]+:\s/))) {
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
- const childIndent = children.length > 0 ?
1407
- (children.find(c => c.trim() !== "")?.length ?? indent + 2) - (children.find(c => c.trim() !== "")?.trimStart().length ?? 0) :
1408
- indent + 2;
1409
- result.set(key, yamlParse(children, childIndent));
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 (const line of lines) {
1578
- const trimmed = line.trim();
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
- const val = tomlParseValue(trimmed.slice(eq + 1));
1604
- current.set(key, val);
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
- globalThis.__arc_log_level = args[0];
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
- return evalExpr(f.body, fnEnv);
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 "-": return left - right;
1982
- case "*": return left * right;
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 "**": return Math.pow(left, right);
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
- evalStmt(stmt, env);
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
- result = evalStmt(stmt, env);
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
@@ -1,4 +1,4 @@
1
- export declare const ARC_VERSION = "0.6.0";
1
+ export declare const ARC_VERSION = "0.6.1";
2
2
  export declare const ARC_BUILD_DATE: string;
3
3
  export declare const ARC_PLATFORM: string;
4
4
  /** Print version info */
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Arc Version System
2
- export const ARC_VERSION = "0.6.0";
2
+ export const ARC_VERSION = "0.6.1";
3
3
  export const ARC_BUILD_DATE = new Date().toISOString().split("T")[0];
4
4
  export const ARC_PLATFORM = `${process.platform}-${process.arch}`;
5
5
  /** Print version info */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arc-lang",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Arc ⚡ — A programming language designed by AI agents, for AI agents. 27-63% fewer tokens than JavaScript.",
5
5
  "type": "module",
6
6
  "bin": {