arc-lang 0.6.5 → 0.6.7

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.
@@ -215,6 +215,12 @@ function resolveAsync(v) {
215
215
  function makePrelude(env) {
216
216
  const fns = {
217
217
  print: (...args) => { console.log(args.map(toStr).join(" ")); return null; },
218
+ throw: (msg) => { throw new Error(toStr(msg ?? "error")); },
219
+ arity: (v) => {
220
+ if (v && typeof v === "object" && v.__fn)
221
+ return (v.params || []).length;
222
+ return null;
223
+ },
218
224
  len: (v) => {
219
225
  if (typeof v === "string")
220
226
  return [...v].length; // codepoint count, not UTF-16
@@ -262,6 +268,25 @@ function makePrelude(env) {
262
268
  return String(a).localeCompare(String(b));
263
269
  });
264
270
  },
271
+ sort_by: (list, fn) => {
272
+ if (!Array.isArray(list))
273
+ throw new Error("sort_by expects a list as first argument");
274
+ if (!fn || !fn.__fn)
275
+ throw new Error("sort_by expects a function as second argument");
276
+ const fnVal = fn;
277
+ const paramCount = fnVal.params ? fnVal.params.length : 0;
278
+ if (paramCount !== 1) {
279
+ throw new ArcRuntimeError(`sort_by takes a key function fn(x) that returns a sort key, not a comparator fn(a, b). ` +
280
+ `For example: sort_by(list, fn(x) => x.name) — not sort_by(list, fn(a, b) => a - b)`, { code: ErrorCode.WRONG_ARITY, category: "TypeError" });
281
+ }
282
+ return [...list].sort((a, b) => {
283
+ const ka = callFn(fnVal, [a]);
284
+ const kb = callFn(fnVal, [b]);
285
+ if (typeof ka === "number" && typeof kb === "number")
286
+ return ka - kb;
287
+ return String(ka).localeCompare(String(kb));
288
+ });
289
+ },
265
290
  take: (list, n) => Array.isArray(list) ? list.slice(0, n) : null,
266
291
  drop: (list, n) => Array.isArray(list) ? list.slice(n) : null,
267
292
  find: (list, fn) => {
@@ -556,21 +581,30 @@ function makePrelude(env) {
556
581
  }
557
582
  },
558
583
  error_throw: (code, message) => {
584
+ if (message === undefined || message === null) {
585
+ throw new Error(toStr(code));
586
+ }
559
587
  throw new Error(`[${toStr(code)}] ${toStr(message)}`);
560
588
  },
561
589
  error_retry: (fn, times) => {
562
590
  const n = typeof times === "number" ? times : 1;
563
591
  let lastErr = null;
564
592
  for (let i = 0; i < n; i++) {
565
- const result = typeof fn === "function" ? fn() : callFn(fn, []);
566
- if (result && typeof result === "object" && "__map" in result) {
567
- const entries = result.entries;
568
- if (entries.has("kind") && entries.has("message")) {
569
- lastErr = result;
570
- continue;
593
+ try {
594
+ const result = typeof fn === "function" ? fn() : callFn(fn, []);
595
+ if (result && typeof result === "object" && "__map" in result) {
596
+ const entries = result.entries;
597
+ if (entries.has("kind") && entries.has("message")) {
598
+ lastErr = result;
599
+ continue;
600
+ }
571
601
  }
602
+ return result;
603
+ }
604
+ catch (e) {
605
+ lastErr = e instanceof Error ? e.message : String(e);
606
+ continue;
572
607
  }
573
- return result;
574
608
  }
575
609
  return lastErr;
576
610
  },
@@ -2548,14 +2582,14 @@ function evalExpr(expr, env) {
2548
2582
  if (typeof left !== "number" || typeof right !== "number")
2549
2583
  throw new ArcRuntimeError(`TypeError: cannot divide non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2550
2584
  if (right === 0)
2551
- return left === 0 ? null : (left > 0 ? Infinity : -Infinity);
2585
+ throw new ArcRuntimeError("Division by zero", { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2552
2586
  return left / right;
2553
2587
  }
2554
2588
  case "%": {
2555
2589
  if (typeof left !== "number" || typeof right !== "number")
2556
2590
  throw new ArcRuntimeError(`TypeError: cannot modulo non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2557
2591
  if (right === 0)
2558
- return null;
2592
+ throw new ArcRuntimeError("Division by zero", { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2559
2593
  return left % right;
2560
2594
  }
2561
2595
  case "**": {
package/dist/lexer.js CHANGED
@@ -150,7 +150,7 @@ export function lex(source) {
150
150
  str += advance();
151
151
  continue;
152
152
  }
153
- if (peek() === "{") {
153
+ if (peek() === "{" && /[a-zA-Z_]/.test(peek(1))) {
154
154
  hasInterp = true;
155
155
  if (str.length > 0 || parts.length === 0) {
156
156
  parts.push(tok(TokenType.String, str, sl, sc));
package/dist/repl.js CHANGED
@@ -5,6 +5,7 @@ import { lex } from "./lexer.js";
5
5
  import { parse } from "./parser.js";
6
6
  import { createEnv, interpretWithEnv, toStr } from "./interpreter.js";
7
7
  import { createUseHandler } from "./modules.js";
8
+ import { ARC_VERSION } from "./version.js";
8
9
  const GREEN = "\x1b[32m";
9
10
  const RED = "\x1b[31m";
10
11
  const CYAN = "\x1b[36m";
@@ -101,7 +102,7 @@ function main() {
101
102
  output: process.stdout,
102
103
  prompt: "arc> ",
103
104
  });
104
- console.log(`${CYAN}Arc REPL v0.6.4${RESET} — Type ${YELLOW}:help${RESET} for commands, ${YELLOW}:builtins${RESET} for functions`);
105
+ console.log(`${CYAN}Arc REPL v${ARC_VERSION}${RESET} — Type ${YELLOW}:help${RESET} for commands, ${YELLOW}:builtins${RESET} for functions`);
105
106
  rl.prompt();
106
107
  let buffer = "";
107
108
  let braceDepth = 0;
@@ -109,7 +110,7 @@ function main() {
109
110
  const trimmed = line.trim();
110
111
  // Handle commands (only when not in multi-line mode)
111
112
  if (braceDepth === 0) {
112
- if (trimmed === ":quit" || trimmed === ":q") {
113
+ if (trimmed === ":quit" || trimmed === ":q" || trimmed === ":exit") {
113
114
  console.log("Goodbye!");
114
115
  process.exit(0);
115
116
  }
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const ARC_VERSION = "0.6.5";
1
+ export declare const ARC_VERSION = "0.6.7";
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.5";
2
+ export const ARC_VERSION = "0.6.7";
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.5",
3
+ "version": "0.6.7",
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": {
@@ -110,7 +110,8 @@ pub fn max_by(list, f) {
110
110
  }
111
111
 
112
112
  pub fn sort_by(list, f) {
113
- # Insertion sort by key function
113
+ # sort_by takes a key function fn(x), not a comparator fn(a, b)
114
+ assert(arity(f) == 1, "sort_by takes a key function fn(x) that returns a sort key, not a comparator fn(a, b). Example: sort_by(list, fn(x) => x.name)")
114
115
  let mut arr = map(list, x => x)
115
116
  for i in 1..len(arr) {
116
117
  let mut j = i
package/stdlib/json.arc CHANGED
@@ -41,10 +41,12 @@ fn _quote(s) {
41
41
 
42
42
  pub fn from_json(s) {
43
43
  let trimmed = trim(s)
44
- if len(trimmed) == 0 { nil }
44
+ if len(trimmed) == 0 { error_throw("Invalid JSON: empty string") }
45
45
  el {
46
46
  let parsed = _parse_value(trimmed)
47
- parsed.value
47
+ if parsed == nil { error_throw("Invalid JSON: " ++ s) }
48
+ el if len(trim(parsed.rest)) > 0 { error_throw("Invalid JSON: unexpected trailing content") }
49
+ el { parsed.value }
48
50
  }
49
51
  }
50
52
 
package/stdlib/regex.arc CHANGED
@@ -19,14 +19,14 @@ pub fn test(pattern, text) {
19
19
  regex_test(re, text)
20
20
  }
21
21
 
22
- # Replaces all occurrences of `pattern` with `replacement` in `text`
23
- # (For first-occurrence-only replacement, use replace_first)
22
+ # Replaces the first occurrence of `pattern` with `replacement` in `text`
23
+ # (For global replacement, use replace_all)
24
24
  pub fn replace(pattern, replacement, text) {
25
25
  let re = regex_new(pattern)
26
26
  regex_replace(re, replacement, text)
27
27
  }
28
28
 
29
- # Replaces only the first occurrence of `pattern` with `replacement` in `text`
29
+ # Alias for replace — replaces only the first occurrence
30
30
  pub fn replace_first(pattern, replacement, text) {
31
31
  let re = regex_new(pattern)
32
32
  regex_replace(re, replacement, text)