arc-lang 0.6.2 → 0.6.4

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.
Files changed (48) hide show
  1. package/README.md +65 -130
  2. package/dist/ast.d.ts +30 -2
  3. package/dist/build.js +1 -1
  4. package/dist/formatter.js +15 -3
  5. package/dist/index.js +104 -0
  6. package/dist/interpreter.js +130 -17
  7. package/dist/lexer.d.ts +41 -37
  8. package/dist/lexer.js +47 -39
  9. package/dist/linter.js +18 -0
  10. package/dist/modules.js +5 -1
  11. package/dist/parser.d.ts +2 -0
  12. package/dist/parser.js +91 -11
  13. package/dist/repl.js +66 -1
  14. package/dist/version.d.ts +1 -1
  15. package/dist/version.js +1 -1
  16. package/package.json +4 -2
  17. package/stdlib/API_DESIGN.md +357 -0
  18. package/stdlib/ASYNC_DESIGN.md +815 -0
  19. package/stdlib/EXAMPLES.md +710 -0
  20. package/stdlib/MODULES.md +854 -0
  21. package/stdlib/README.md +64 -0
  22. package/stdlib/collections.arc +140 -0
  23. package/stdlib/crypto.arc +62 -0
  24. package/stdlib/csv.arc +40 -0
  25. package/stdlib/datetime.arc +75 -0
  26. package/stdlib/embed.arc +42 -0
  27. package/stdlib/env.arc +10 -0
  28. package/stdlib/error.arc +36 -0
  29. package/stdlib/html.arc +30 -0
  30. package/stdlib/http.arc +43 -0
  31. package/stdlib/io.arc +28 -0
  32. package/stdlib/json.arc +204 -0
  33. package/stdlib/llm.arc +193 -0
  34. package/stdlib/log.arc +20 -0
  35. package/stdlib/map.arc +72 -0
  36. package/stdlib/math.arc +133 -0
  37. package/stdlib/net.arc +17 -0
  38. package/stdlib/os.arc +81 -0
  39. package/stdlib/path.arc +11 -0
  40. package/stdlib/prompt.arc +49 -0
  41. package/stdlib/regex.arc +59 -0
  42. package/stdlib/result.arc +50 -0
  43. package/stdlib/store.arc +62 -0
  44. package/stdlib/strings.arc +44 -0
  45. package/stdlib/test.arc +61 -0
  46. package/stdlib/time.arc +19 -0
  47. package/stdlib/toml.arc +10 -0
  48. package/stdlib/yaml.arc +10 -0
@@ -126,7 +126,7 @@ function syncFetch(method, url, body) {
126
126
  const bodyJson = bodyStr != null ? JSON.stringify(bodyStr) : "null";
127
127
  // Pass config via env to avoid shell escaping issues
128
128
  const fetchConfig = JSON.stringify({ method, url, body: bodyStr, headers: customHeaders });
129
- const script = `const c=JSON.parse(process.env.ARC_FETCH);(async()=>{const o={method:c.method,headers:{...c.headers}};if(c.body!==null){o.body=c.body;if(!o.headers["Content-Type"])o.headers["Content-Type"]="application/json";}try{const r=await fetch(c.url,o);const t=await r.text();let d;try{d=JSON.parse(t)}catch{d=t}console.log(JSON.stringify({ok:true,status:r.status,data:d}))}catch(e){console.log(JSON.stringify({ok:false,status:0,data:e.message}))}})()`;
129
+ const script = `const c=JSON.parse(process.env.ARC_FETCH);(async()=>{const o={method:c.method,headers:{...c.headers}};if(c.body!==null){o.body=c.body;if(!o.headers["Content-Type"])o.headers["Content-Type"]="application/json";}try{const r=await fetch(c.url,o);const t=await r.text();let d;try{d=JSON.parse(t)}catch{d=t}console.log(JSON.stringify({ok:r.status>=200&&r.status<300,status:r.status,data:d}))}catch(e){console.log(JSON.stringify({ok:false,status:0,data:e.message}))}})()`;
130
130
  try {
131
131
  const raw = execSync(`node -e "${script.replace(/"/g, '\\"')}"`, {
132
132
  timeout: 30000,
@@ -182,7 +182,7 @@ function makePrelude(env) {
182
182
  print: (...args) => { console.log(args.map(toStr).join(" ")); return null; },
183
183
  len: (v) => {
184
184
  if (typeof v === "string")
185
- return v.length;
185
+ return [...v].length; // codepoint count, not UTF-16
186
186
  if (Array.isArray(v))
187
187
  return v.length;
188
188
  if (v && typeof v === "object" && "__map" in v)
@@ -276,8 +276,28 @@ function makePrelude(env) {
276
276
  },
277
277
  starts: (s, pre) => typeof s === "string" ? s.startsWith(pre) : false,
278
278
  ends: (s, suf) => typeof s === "string" ? s.endsWith(suf) : false,
279
- int: (v) => typeof v === "string" ? parseInt(v) : typeof v === "number" ? Math.floor(v) : 0,
280
- float: (v) => typeof v === "string" ? parseFloat(v) : typeof v === "number" ? v : 0,
279
+ int: (v) => {
280
+ if (typeof v === "number")
281
+ return Math.floor(v);
282
+ if (typeof v === "string") {
283
+ const n = parseInt(v);
284
+ if (isNaN(n))
285
+ throw new ArcRuntimeError(`ValueError: cannot convert '${v}' to int`, { code: ErrorCode.TYPE_MISMATCH });
286
+ return n;
287
+ }
288
+ return 0;
289
+ },
290
+ float: (v) => {
291
+ if (typeof v === "number")
292
+ return v;
293
+ if (typeof v === "string") {
294
+ const n = parseFloat(v);
295
+ if (isNaN(n))
296
+ throw new ArcRuntimeError(`ValueError: cannot convert '${v}' to float`, { code: ErrorCode.TYPE_MISMATCH });
297
+ return n;
298
+ }
299
+ return 0;
300
+ },
281
301
  str: (v) => toStr(v),
282
302
  bool: (v) => isTruthy(v),
283
303
  min: (...args) => {
@@ -2361,6 +2381,11 @@ class ReturnSignal {
2361
2381
  this.value = value;
2362
2382
  }
2363
2383
  }
2384
+ // Break/Continue signals for loops
2385
+ class BreakSignal {
2386
+ }
2387
+ class ContinueSignal {
2388
+ }
2364
2389
  // Evaluate expression in tail position — returns TCOSignal for self-recursive tail calls
2365
2390
  function evalExprTCO(expr, env, fnName) {
2366
2391
  // Only handle tail-position expressions specially
@@ -2469,20 +2494,14 @@ function evalExpr(expr, env) {
2469
2494
  if (typeof left !== "number" || typeof right !== "number")
2470
2495
  throw new ArcRuntimeError(`TypeError: cannot divide non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2471
2496
  if (right === 0)
2472
- throw new ArcRuntimeError(`Division by zero`, {
2473
- code: ErrorCode.DIVISION_BY_ZERO, loc: expr.loc,
2474
- suggestion: "Check that the divisor is not zero before dividing.",
2475
- });
2497
+ return left === 0 ? null : (left > 0 ? Infinity : -Infinity);
2476
2498
  return left / right;
2477
2499
  }
2478
2500
  case "%": {
2479
2501
  if (typeof left !== "number" || typeof right !== "number")
2480
2502
  throw new ArcRuntimeError(`TypeError: cannot modulo non-numbers`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2481
2503
  if (right === 0)
2482
- throw new ArcRuntimeError(`Modulo by zero`, {
2483
- code: ErrorCode.DIVISION_BY_ZERO, loc: expr.loc,
2484
- suggestion: "Check that the divisor is not zero before dividing.",
2485
- });
2504
+ return null;
2486
2505
  return left % right;
2487
2506
  }
2488
2507
  case "**": {
@@ -2576,8 +2595,24 @@ function evalExpr(expr, env) {
2576
2595
  if (obj && typeof obj === "object" && "__map" in obj) {
2577
2596
  return obj.entries.get(expr.property) ?? null;
2578
2597
  }
2579
- throw new ArcRuntimeError(`Cannot access property '${expr.property}' on ${toStr(obj)}`, {
2580
- code: ErrorCode.PROPERTY_ACCESS, loc: expr.loc,
2598
+ // Teaching error messages for common method-style access
2599
+ const prop = expr.property;
2600
+ const methodBuiltins = {
2601
+ length: "len(value)", repeat: "repeat(str, n)", trim: "trim(str)",
2602
+ split: "split(str, sep)", join: "join(list, sep)", replace: "replace(str, old, new)",
2603
+ includes: "contains(value, sub)", startsWith: "starts(str, prefix)", endsWith: "ends(str, suffix)",
2604
+ toUpperCase: "upper(str)", toLowerCase: "lower(str)", indexOf: "index_of(str, sub)",
2605
+ slice: "slice(value, start, end)", map: "map(list, fn)", filter: "filter(list, fn)",
2606
+ reduce: "reduce(list, fn, init)", find: "find(list, fn)", sort: "sort(list)",
2607
+ reverse: "reverse(list)", push: "push(list, item)", concat: "concat(a, b)",
2608
+ forEach: "for item in list { ... }", charAt: "char_at(str, i)",
2609
+ toString: "str(value)", match: "use regex; regex.find(str, pattern)",
2610
+ };
2611
+ const suggestion = methodBuiltins[prop]
2612
+ ? `Arc uses free functions, not methods. Try: ${methodBuiltins[prop]}`
2613
+ : undefined;
2614
+ throw new ArcRuntimeError(`Cannot access property '${prop}' on ${toStr(obj)}`, {
2615
+ code: ErrorCode.PROPERTY_ACCESS, loc: expr.loc, suggestion,
2581
2616
  });
2582
2617
  }
2583
2618
  case "OptionalMemberExpr": {
@@ -2606,6 +2641,23 @@ function evalExpr(expr, env) {
2606
2641
  // Not a Result type — just return the value
2607
2642
  return val;
2608
2643
  }
2644
+ case "TryCatchExpr": {
2645
+ try {
2646
+ return evalExpr(expr.body, env);
2647
+ }
2648
+ catch (e) {
2649
+ if (e instanceof ReturnSignal)
2650
+ throw e;
2651
+ if (e instanceof BreakSignal)
2652
+ throw e;
2653
+ if (e instanceof ContinueSignal)
2654
+ throw e;
2655
+ const catchEnv = new Env(env);
2656
+ const errMsg = e instanceof Error ? e.message : String(e);
2657
+ catchEnv.set(expr.catchVar, errMsg);
2658
+ return evalExpr(expr.catchBody, catchEnv);
2659
+ }
2660
+ }
2609
2661
  case "IndexExpr": {
2610
2662
  const obj = evalExpr(expr.object, env);
2611
2663
  const idx = evalExpr(expr.index, env);
@@ -2786,10 +2838,11 @@ function evalExpr(expr, env) {
2786
2838
  return resolveAsync(val);
2787
2839
  }
2788
2840
  case "FetchExpr": {
2789
- return expr.targets.map(t => {
2841
+ const results = expr.targets.map(t => {
2790
2842
  const val = evalExpr(t, env);
2791
2843
  return resolveAsync(val);
2792
2844
  });
2845
+ return results.length === 1 ? results[0] : results;
2793
2846
  }
2794
2847
  case "BlockExpr": {
2795
2848
  const blockEnv = new Env(env);
@@ -2894,14 +2947,72 @@ function evalStmt(stmt, env) {
2894
2947
  loopEnv.set(n, m.get(n) ?? null);
2895
2948
  }
2896
2949
  }
2897
- result = evalExpr(stmt.body, loopEnv);
2950
+ try {
2951
+ result = evalExpr(stmt.body, loopEnv);
2952
+ }
2953
+ catch (e) {
2954
+ if (e instanceof BreakSignal)
2955
+ break;
2956
+ if (e instanceof ContinueSignal)
2957
+ continue;
2958
+ throw e;
2959
+ }
2898
2960
  }
2899
2961
  return result;
2900
2962
  }
2963
+ case "WhileStmt": {
2964
+ let result = null;
2965
+ while (isTruthy(evalExpr(stmt.condition, env))) {
2966
+ try {
2967
+ result = evalExpr(stmt.body, env);
2968
+ }
2969
+ catch (e) {
2970
+ if (e instanceof BreakSignal)
2971
+ break;
2972
+ if (e instanceof ContinueSignal)
2973
+ continue;
2974
+ throw e;
2975
+ }
2976
+ }
2977
+ return result;
2978
+ }
2979
+ case "TryCatchStmt": {
2980
+ try {
2981
+ return evalExpr(stmt.body, env);
2982
+ }
2983
+ catch (e) {
2984
+ if (e instanceof ReturnSignal)
2985
+ throw e; // don't catch return/break/continue
2986
+ if (e instanceof BreakSignal)
2987
+ throw e;
2988
+ if (e instanceof ContinueSignal)
2989
+ throw e;
2990
+ const catchEnv = new Env(env);
2991
+ const errMsg = e instanceof Error ? e.message : String(e);
2992
+ catchEnv.set(stmt.catchVar, errMsg);
2993
+ return evalExpr(stmt.catchBody, catchEnv);
2994
+ }
2995
+ }
2901
2996
  case "DoStmt": {
2902
2997
  let result = null;
2903
2998
  do {
2904
- result = evalExpr(stmt.body, env);
2999
+ try {
3000
+ result = evalExpr(stmt.body, env);
3001
+ }
3002
+ catch (e) {
3003
+ if (e instanceof BreakSignal) {
3004
+ break;
3005
+ }
3006
+ if (e instanceof ContinueSignal) {
3007
+ const cond = evalExpr(stmt.condition, env);
3008
+ if (stmt.isWhile && !isTruthy(cond))
3009
+ break;
3010
+ if (!stmt.isWhile && isTruthy(cond))
3011
+ break;
3012
+ continue;
3013
+ }
3014
+ throw e;
3015
+ }
2905
3016
  const cond = evalExpr(stmt.condition, env);
2906
3017
  if (stmt.isWhile && !isTruthy(cond))
2907
3018
  break;
@@ -2910,6 +3021,8 @@ function evalStmt(stmt, env) {
2910
3021
  } while (true);
2911
3022
  return result;
2912
3023
  }
3024
+ case "BreakStmt": throw new BreakSignal();
3025
+ case "ContinueStmt": throw new ContinueSignal();
2913
3026
  case "AssignStmt": {
2914
3027
  const value = evalExpr(stmt.value, env);
2915
3028
  env.assign(stmt.target, value);
package/dist/lexer.d.ts CHANGED
@@ -34,43 +34,47 @@ export declare enum TokenType {
34
34
  Where = 32,
35
35
  Matching = 33,
36
36
  Fetch = 34,
37
- Plus = 35,
38
- Minus = 36,
39
- Star = 37,
40
- Slash = 38,
41
- Percent = 39,
42
- Power = 40,
43
- Eq = 41,
44
- Neq = 42,
45
- Lt = 43,
46
- Gt = 44,
47
- Lte = 45,
48
- Gte = 46,
49
- Pipe = 47,
50
- Bar = 48,
51
- FatArrow = 49,
52
- Arrow = 50,
53
- Question = 51,
54
- QuestionDot = 52,
55
- Range = 53,
56
- Concat = 54,
57
- At = 55,
58
- Hash = 56,
59
- DotDotDot = 57,
60
- Assign = 58,
61
- LParen = 59,
62
- RParen = 60,
63
- LBrace = 61,
64
- RBrace = 62,
65
- LBracket = 63,
66
- RBracket = 64,
67
- Comma = 65,
68
- Colon = 66,
69
- Dot = 67,
70
- Semicolon = 68,
71
- Newline = 69,
72
- Regex = 70,
73
- EOF = 71
37
+ Break = 35,
38
+ Continue = 36,
39
+ Try = 37,
40
+ Catch = 38,
41
+ Plus = 39,
42
+ Minus = 40,
43
+ Star = 41,
44
+ Slash = 42,
45
+ Percent = 43,
46
+ Power = 44,
47
+ Eq = 45,
48
+ Neq = 46,
49
+ Lt = 47,
50
+ Gt = 48,
51
+ Lte = 49,
52
+ Gte = 50,
53
+ Pipe = 51,
54
+ Bar = 52,
55
+ FatArrow = 53,
56
+ Arrow = 54,
57
+ Question = 55,
58
+ QuestionDot = 56,
59
+ Range = 57,
60
+ Concat = 58,
61
+ At = 59,
62
+ Hash = 60,
63
+ DotDotDot = 61,
64
+ Assign = 62,
65
+ LParen = 63,
66
+ RParen = 64,
67
+ LBrace = 65,
68
+ RBrace = 66,
69
+ LBracket = 67,
70
+ RBracket = 68,
71
+ Comma = 69,
72
+ Colon = 70,
73
+ Dot = 71,
74
+ Semicolon = 72,
75
+ Newline = 73,
76
+ Regex = 74,
77
+ EOF = 75
74
78
  }
75
79
  export interface Token {
76
80
  type: TokenType;
package/dist/lexer.js CHANGED
@@ -38,47 +38,51 @@ export var TokenType;
38
38
  TokenType[TokenType["Where"] = 32] = "Where";
39
39
  TokenType[TokenType["Matching"] = 33] = "Matching";
40
40
  TokenType[TokenType["Fetch"] = 34] = "Fetch";
41
+ TokenType[TokenType["Break"] = 35] = "Break";
42
+ TokenType[TokenType["Continue"] = 36] = "Continue";
43
+ TokenType[TokenType["Try"] = 37] = "Try";
44
+ TokenType[TokenType["Catch"] = 38] = "Catch";
41
45
  // Operators
42
- TokenType[TokenType["Plus"] = 35] = "Plus";
43
- TokenType[TokenType["Minus"] = 36] = "Minus";
44
- TokenType[TokenType["Star"] = 37] = "Star";
45
- TokenType[TokenType["Slash"] = 38] = "Slash";
46
- TokenType[TokenType["Percent"] = 39] = "Percent";
47
- TokenType[TokenType["Power"] = 40] = "Power";
48
- TokenType[TokenType["Eq"] = 41] = "Eq";
49
- TokenType[TokenType["Neq"] = 42] = "Neq";
50
- TokenType[TokenType["Lt"] = 43] = "Lt";
51
- TokenType[TokenType["Gt"] = 44] = "Gt";
52
- TokenType[TokenType["Lte"] = 45] = "Lte";
53
- TokenType[TokenType["Gte"] = 46] = "Gte";
54
- TokenType[TokenType["Pipe"] = 47] = "Pipe";
55
- TokenType[TokenType["Bar"] = 48] = "Bar";
56
- TokenType[TokenType["FatArrow"] = 49] = "FatArrow";
57
- TokenType[TokenType["Arrow"] = 50] = "Arrow";
58
- TokenType[TokenType["Question"] = 51] = "Question";
59
- TokenType[TokenType["QuestionDot"] = 52] = "QuestionDot";
60
- TokenType[TokenType["Range"] = 53] = "Range";
61
- TokenType[TokenType["Concat"] = 54] = "Concat";
62
- TokenType[TokenType["At"] = 55] = "At";
63
- TokenType[TokenType["Hash"] = 56] = "Hash";
64
- TokenType[TokenType["DotDotDot"] = 57] = "DotDotDot";
65
- TokenType[TokenType["Assign"] = 58] = "Assign";
46
+ TokenType[TokenType["Plus"] = 39] = "Plus";
47
+ TokenType[TokenType["Minus"] = 40] = "Minus";
48
+ TokenType[TokenType["Star"] = 41] = "Star";
49
+ TokenType[TokenType["Slash"] = 42] = "Slash";
50
+ TokenType[TokenType["Percent"] = 43] = "Percent";
51
+ TokenType[TokenType["Power"] = 44] = "Power";
52
+ TokenType[TokenType["Eq"] = 45] = "Eq";
53
+ TokenType[TokenType["Neq"] = 46] = "Neq";
54
+ TokenType[TokenType["Lt"] = 47] = "Lt";
55
+ TokenType[TokenType["Gt"] = 48] = "Gt";
56
+ TokenType[TokenType["Lte"] = 49] = "Lte";
57
+ TokenType[TokenType["Gte"] = 50] = "Gte";
58
+ TokenType[TokenType["Pipe"] = 51] = "Pipe";
59
+ TokenType[TokenType["Bar"] = 52] = "Bar";
60
+ TokenType[TokenType["FatArrow"] = 53] = "FatArrow";
61
+ TokenType[TokenType["Arrow"] = 54] = "Arrow";
62
+ TokenType[TokenType["Question"] = 55] = "Question";
63
+ TokenType[TokenType["QuestionDot"] = 56] = "QuestionDot";
64
+ TokenType[TokenType["Range"] = 57] = "Range";
65
+ TokenType[TokenType["Concat"] = 58] = "Concat";
66
+ TokenType[TokenType["At"] = 59] = "At";
67
+ TokenType[TokenType["Hash"] = 60] = "Hash";
68
+ TokenType[TokenType["DotDotDot"] = 61] = "DotDotDot";
69
+ TokenType[TokenType["Assign"] = 62] = "Assign";
66
70
  // Delimiters
67
- TokenType[TokenType["LParen"] = 59] = "LParen";
68
- TokenType[TokenType["RParen"] = 60] = "RParen";
69
- TokenType[TokenType["LBrace"] = 61] = "LBrace";
70
- TokenType[TokenType["RBrace"] = 62] = "RBrace";
71
- TokenType[TokenType["LBracket"] = 63] = "LBracket";
72
- TokenType[TokenType["RBracket"] = 64] = "RBracket";
73
- TokenType[TokenType["Comma"] = 65] = "Comma";
74
- TokenType[TokenType["Colon"] = 66] = "Colon";
75
- TokenType[TokenType["Dot"] = 67] = "Dot";
76
- TokenType[TokenType["Semicolon"] = 68] = "Semicolon";
77
- TokenType[TokenType["Newline"] = 69] = "Newline";
71
+ TokenType[TokenType["LParen"] = 63] = "LParen";
72
+ TokenType[TokenType["RParen"] = 64] = "RParen";
73
+ TokenType[TokenType["LBrace"] = 65] = "LBrace";
74
+ TokenType[TokenType["RBrace"] = 66] = "RBrace";
75
+ TokenType[TokenType["LBracket"] = 67] = "LBracket";
76
+ TokenType[TokenType["RBracket"] = 68] = "RBracket";
77
+ TokenType[TokenType["Comma"] = 69] = "Comma";
78
+ TokenType[TokenType["Colon"] = 70] = "Colon";
79
+ TokenType[TokenType["Dot"] = 71] = "Dot";
80
+ TokenType[TokenType["Semicolon"] = 72] = "Semicolon";
81
+ TokenType[TokenType["Newline"] = 73] = "Newline";
78
82
  // Regex
79
- TokenType[TokenType["Regex"] = 70] = "Regex";
83
+ TokenType[TokenType["Regex"] = 74] = "Regex";
80
84
  // Special
81
- TokenType[TokenType["EOF"] = 71] = "EOF";
85
+ TokenType[TokenType["EOF"] = 75] = "EOF";
82
86
  })(TokenType || (TokenType = {}));
83
87
  const KEYWORDS = {
84
88
  fn: TokenType.Fn, let: TokenType.Let, mut: TokenType.Mut, type: TokenType.Type,
@@ -89,6 +93,10 @@ const KEYWORDS = {
89
93
  true: TokenType.True, false: TokenType.False, nil: TokenType.NilKw,
90
94
  and: TokenType.And, or: TokenType.Or, not: TokenType.Not,
91
95
  where: TokenType.Where, matching: TokenType.Matching, fetch: TokenType.Fetch,
96
+ break: TokenType.Break, continue: TokenType.Continue,
97
+ return: TokenType.Ret, // alias: Arc uses 'ret' but accept 'return' too
98
+ else: TokenType.El, // alias: Arc uses 'el' but accept 'else' too
99
+ try: TokenType.Try, catch: TokenType.Catch,
92
100
  };
93
101
  export function lex(source) {
94
102
  const tokens = [];
@@ -124,8 +132,8 @@ export function lex(source) {
124
132
  tokens.push(tok(TokenType.Newline, "\\n", sl, sc));
125
133
  continue;
126
134
  }
127
- // Comments: # to end of line
128
- if (ch === "#") {
135
+ // Comments: # or // to end of line
136
+ if (ch === "#" || (ch === "/" && peek(1) === "/")) {
129
137
  while (i < source.length && peek() !== "\n")
130
138
  advance();
131
139
  continue;
package/dist/linter.js CHANGED
@@ -343,6 +343,24 @@ export function lint(source, options) {
343
343
  }
344
344
  break;
345
345
  }
346
+ case "WhileStmt":
347
+ analyzeExpr(stmt.condition, scope);
348
+ analyzeExpr(stmt.body, scope);
349
+ if (stmt.body.kind === "BlockExpr" && stmt.body.stmts.length === 0) {
350
+ warn("empty-block", "While loop has an empty body", stmt.loc);
351
+ }
352
+ break;
353
+ case "BreakStmt":
354
+ case "ContinueStmt":
355
+ break;
356
+ case "TryCatchStmt":
357
+ analyzeExpr(stmt.body, scope);
358
+ analyzeExpr(stmt.catchBody, scope);
359
+ break;
360
+ case "RetStmt":
361
+ if (stmt.value)
362
+ analyzeExpr(stmt.value, scope);
363
+ break;
346
364
  case "DoStmt":
347
365
  analyzeExpr(stmt.body, scope);
348
366
  analyzeExpr(stmt.condition, scope);
package/dist/modules.js CHANGED
@@ -35,10 +35,14 @@ export function resolveModule(path, basePath) {
35
35
  const relPath = resolve(dirname(basePath), modulePath);
36
36
  if (existsSync(relPath))
37
37
  return relPath;
38
- // 3. Check compiler's sibling stdlib/
38
+ // 3. Check compiler's sibling stdlib/ (monorepo layout)
39
39
  const compilerStdlib = resolve(__dirname2, "..", "..", "stdlib", modulePath);
40
40
  if (existsSync(compilerStdlib))
41
41
  return compilerStdlib;
42
+ // 4. Check stdlib bundled inside the npm package (npm install -g layout)
43
+ const bundledStdlib = resolve(__dirname2, "..", "stdlib", modulePath);
44
+ if (existsSync(bundledStdlib))
45
+ return bundledStdlib;
42
46
  throw new Error(`Module not found: ${path.join("/")} (searched from ${basePath})`);
43
47
  }
44
48
  /**
package/dist/parser.d.ts CHANGED
@@ -21,6 +21,8 @@ export declare class Parser {
21
21
  private parseAsync;
22
22
  private parseFn;
23
23
  private parseFor;
24
+ private parseWhile;
25
+ private parseTryCatch;
24
26
  private parseDo;
25
27
  private parseUse;
26
28
  private parseType;
package/dist/parser.js CHANGED
@@ -52,7 +52,18 @@ export class Parser {
52
52
  case TokenType.Fn: return this.parseFn(false);
53
53
  case TokenType.Async: return this.parseAsync();
54
54
  case TokenType.For: return this.parseFor();
55
+ case TokenType.While: return this.parseWhile();
55
56
  case TokenType.Do: return this.parseDo();
57
+ case TokenType.Break: {
58
+ const loc = this.loc();
59
+ this.advance();
60
+ return { kind: "BreakStmt", loc };
61
+ }
62
+ case TokenType.Continue: {
63
+ const loc = this.loc();
64
+ this.advance();
65
+ return { kind: "ContinueStmt", loc };
66
+ }
56
67
  case TokenType.Use: return this.parseUse();
57
68
  case TokenType.Type: return this.parseType();
58
69
  case TokenType.Ret: return this.parseRet();
@@ -241,6 +252,22 @@ export class Parser {
241
252
  const body = this.parseBlock();
242
253
  return { kind: "ForStmt", variable, iterable, body, loc };
243
254
  }
255
+ parseWhile() {
256
+ const loc = this.loc();
257
+ this.expect(TokenType.While);
258
+ const condition = this.parseExpr(0);
259
+ const body = this.parseBlock();
260
+ return { kind: "WhileStmt", condition, body, loc };
261
+ }
262
+ parseTryCatch() {
263
+ const loc = this.loc();
264
+ this.expect(TokenType.Try);
265
+ const body = this.parseBlock();
266
+ this.expect(TokenType.Catch);
267
+ const catchVar = this.expect(TokenType.Ident).value;
268
+ const catchBody = this.parseBlock();
269
+ return { kind: "TryCatchStmt", body, catchVar, catchBody, loc };
270
+ }
244
271
  parseDo() {
245
272
  const loc = this.loc();
246
273
  this.expect(TokenType.Do);
@@ -473,14 +500,30 @@ export class Parser {
473
500
  // Postfix: member access
474
501
  if (t.type === TokenType.Dot) {
475
502
  this.advance();
476
- const prop = this.expect(TokenType.Ident).value;
503
+ // Allow keywords (like 'match') as property names after dot
504
+ const propToken = this.peek();
505
+ let prop;
506
+ if (propToken.type === TokenType.Ident || propToken.type === TokenType.Match || propToken.type === TokenType.Fn || propToken.type === TokenType.Let || propToken.type === TokenType.If || propToken.type === TokenType.For || propToken.type === TokenType.In || propToken.type === TokenType.Do || propToken.type === TokenType.While || propToken.type === TokenType.Until || propToken.type === TokenType.Use || propToken.type === TokenType.Pub || propToken.type === TokenType.Type || propToken.type === TokenType.Ret || propToken.type === TokenType.Where || propToken.type === TokenType.Matching || propToken.type === TokenType.Fetch || propToken.type === TokenType.Async || propToken.type === TokenType.Await || propToken.type === TokenType.Try || propToken.type === TokenType.Catch) {
507
+ prop = this.advance().value;
508
+ }
509
+ else {
510
+ prop = this.expect(TokenType.Ident).value;
511
+ }
477
512
  left = { kind: "MemberExpr", object: left, property: prop, loc: left.loc };
478
513
  continue;
479
514
  }
480
515
  // Postfix: optional chaining ?.
481
516
  if (t.type === TokenType.QuestionDot) {
482
517
  this.advance();
483
- const prop = this.expect(TokenType.Ident).value;
518
+ // Allow keywords as property names after ?.
519
+ const propToken = this.peek();
520
+ let prop;
521
+ if (propToken.type === TokenType.Ident || propToken.type === TokenType.Match || propToken.type === TokenType.Fn || propToken.type === TokenType.Let || propToken.type === TokenType.If || propToken.type === TokenType.For || propToken.type === TokenType.In || propToken.type === TokenType.Do || propToken.type === TokenType.While || propToken.type === TokenType.Until || propToken.type === TokenType.Use || propToken.type === TokenType.Pub || propToken.type === TokenType.Type || propToken.type === TokenType.Ret || propToken.type === TokenType.Where || propToken.type === TokenType.Matching || propToken.type === TokenType.Fetch || propToken.type === TokenType.Async || propToken.type === TokenType.Await || propToken.type === TokenType.Try || propToken.type === TokenType.Catch) {
522
+ prop = this.advance().value;
523
+ }
524
+ else {
525
+ prop = this.expect(TokenType.Ident).value;
526
+ }
484
527
  left = { kind: "OptionalMemberExpr", object: left, property: prop, loc: left.loc };
485
528
  continue;
486
529
  }
@@ -660,24 +703,61 @@ export class Parser {
660
703
  const body = this.parseBlock();
661
704
  return { kind: "AsyncExpr", body, loc };
662
705
  }
706
+ // Anonymous function expression: fn(params) { body } or fn(params) => expr
707
+ if (t.type === TokenType.Fn) {
708
+ this.advance();
709
+ this.expect(TokenType.LParen);
710
+ const params = [];
711
+ while (!this.at(TokenType.RParen)) {
712
+ params.push(this.expect(TokenType.Ident).value);
713
+ if (this.at(TokenType.Comma))
714
+ this.advance();
715
+ }
716
+ this.expect(TokenType.RParen);
717
+ if (this.at(TokenType.FatArrow)) {
718
+ this.advance();
719
+ const body = this.parseExpr();
720
+ return { kind: "LambdaExpr", params, body, loc };
721
+ }
722
+ const body = this.parseBlock();
723
+ return { kind: "LambdaExpr", params, body, loc };
724
+ }
725
+ // Try/catch expression: try { body } catch e { handler }
726
+ if (t.type === TokenType.Try) {
727
+ this.advance();
728
+ const body = this.parseBlock();
729
+ if (this.at(TokenType.Catch)) {
730
+ this.expect(TokenType.Catch);
731
+ const catchVar = this.expect(TokenType.Ident).value;
732
+ const catchBody = this.parseBlock();
733
+ return { kind: "TryCatchExpr", body, catchVar, catchBody, loc };
734
+ }
735
+ // Plain try expression (wraps in Result)
736
+ return { kind: "TryExpr", expr: body, loc };
737
+ }
663
738
  // Await expression: await expr
664
739
  if (t.type === TokenType.Await) {
665
740
  this.advance();
666
741
  const expr = this.parseExpr(8); // high precedence
667
742
  return { kind: "AwaitExpr", expr, loc };
668
743
  }
669
- // Fetch expression: fetch [expr1, expr2, ...]
744
+ // Fetch expression: fetch @GET "url" or fetch [expr1, expr2, ...]
670
745
  if (t.type === TokenType.Fetch) {
671
746
  this.advance();
672
- this.expect(TokenType.LBracket);
673
- const targets = [];
674
- while (!this.at(TokenType.RBracket)) {
675
- targets.push(this.parseExpr());
676
- if (this.at(TokenType.Comma))
677
- this.advance();
747
+ if (this.at(TokenType.LBracket)) {
748
+ this.advance();
749
+ const targets = [];
750
+ while (!this.at(TokenType.RBracket)) {
751
+ targets.push(this.parseExpr());
752
+ if (this.at(TokenType.Comma))
753
+ this.advance();
754
+ }
755
+ this.expect(TokenType.RBracket);
756
+ return { kind: "FetchExpr", targets, loc };
678
757
  }
679
- this.expect(TokenType.RBracket);
680
- return { kind: "FetchExpr", targets, loc };
758
+ // Single target: fetch @GET "url"
759
+ const target = this.parseExpr();
760
+ return { kind: "FetchExpr", targets: [target], loc };
681
761
  }
682
762
  // Tool call: @GET "url" or @ident(args)
683
763
  if (t.type === TokenType.At) {