arc-lang 0.6.14 → 0.6.16

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/ast.d.ts CHANGED
@@ -182,7 +182,7 @@ export interface GroupExpr {
182
182
  expr: Expr;
183
183
  loc: Loc;
184
184
  }
185
- export type Pattern = WildcardPattern | LiteralPattern | BindingPattern | ArrayPattern | OrPattern | ConstructorPattern;
185
+ export type Pattern = WildcardPattern | LiteralPattern | BindingPattern | ArrayPattern | OrPattern | ConstructorPattern | RangePattern;
186
186
  export interface WildcardPattern {
187
187
  kind: "WildcardPattern";
188
188
  loc: Loc;
@@ -213,6 +213,12 @@ export interface ConstructorPattern {
213
213
  args: Pattern[];
214
214
  loc: Loc;
215
215
  }
216
+ export interface RangePattern {
217
+ kind: "RangePattern";
218
+ from: number;
219
+ to: number;
220
+ loc: Loc;
221
+ }
216
222
  export type Stmt = LetStmt | FnStmt | ForStmt | DoStmt | WhileStmt | ExprStmt | UseStmt | TypeStmt | AssignStmt | MemberAssignStmt | IndexAssignStmt | RetStmt | BreakStmt | ContinueStmt | TryCatchStmt;
217
223
  export interface AssignStmt {
218
224
  kind: "AssignStmt";
@@ -2458,6 +2458,14 @@ function makePrelude(env) {
2458
2458
  }
2459
2459
  function bindParams(fn, args, fnEnv, evalExprFn) {
2460
2460
  if (fn.richParams) {
2461
+ const hasRest = fn.richParams.some(p => p.rest);
2462
+ if (!hasRest && args.length > fn.richParams.length) {
2463
+ const name = fn.name || "<anonymous>";
2464
+ throw new ArcRuntimeError(`${name}() takes ${fn.richParams.length} argument(s) but ${args.length} were given`, {
2465
+ code: ErrorCode.WRONG_ARITY,
2466
+ category: "TypeError",
2467
+ });
2468
+ }
2461
2469
  for (let i = 0; i < fn.richParams.length; i++) {
2462
2470
  const p = fn.richParams[i];
2463
2471
  if (p.rest) {
@@ -2475,6 +2483,13 @@ function bindParams(fn, args, fnEnv, evalExprFn) {
2475
2483
  }
2476
2484
  }
2477
2485
  else {
2486
+ if (fn.params.length > 0 && args.length > fn.params.length) {
2487
+ const name = fn.name || "<anonymous>";
2488
+ throw new ArcRuntimeError(`${name}() takes ${fn.params.length} argument(s) but ${args.length} were given`, {
2489
+ code: ErrorCode.WRONG_ARITY,
2490
+ category: "TypeError",
2491
+ });
2492
+ }
2478
2493
  fn.params.forEach((p, i) => fnEnv.set(p, args[i] ?? null));
2479
2494
  }
2480
2495
  }
@@ -3005,6 +3020,8 @@ function matchPattern(pattern, value, env) {
3005
3020
  return false;
3006
3021
  return pattern.elements.every((p, i) => matchPattern(p, value[i], env));
3007
3022
  }
3023
+ case "RangePattern":
3024
+ return typeof value === "number" && value >= pattern.from && value <= pattern.to;
3008
3025
  case "OrPattern":
3009
3026
  return pattern.patterns.some(p => matchPattern(p, value, env));
3010
3027
  case "ConstructorPattern": {
package/dist/parser.js CHANGED
@@ -269,7 +269,7 @@ export class Parser {
269
269
  this.expect(TokenType.Try);
270
270
  const body = this.parseBlock();
271
271
  this.expect(TokenType.Catch);
272
- const catchVar = this.expect(TokenType.Ident).value;
272
+ const catchVar = this.expect(TokenType.Ident, "catch requires a variable name, e.g. catch err { ... }").value;
273
273
  const catchBody = this.parseBlock();
274
274
  return { kind: "TryCatchStmt", body, catchVar, catchBody, loc };
275
275
  }
@@ -281,7 +281,7 @@ export class Parser {
281
281
  const body = this.parseBlock();
282
282
  if (this.at(TokenType.Catch)) {
283
283
  this.advance();
284
- const catchVar = this.expect(TokenType.Ident).value;
284
+ const catchVar = this.expect(TokenType.Ident, "catch requires a variable name, e.g. catch err { ... }").value;
285
285
  const catchBody = this.parseBlock();
286
286
  return { kind: "TryCatchStmt", body, catchVar, catchBody, loc };
287
287
  }
@@ -749,7 +749,7 @@ export class Parser {
749
749
  const body = this.parseBlock();
750
750
  if (this.at(TokenType.Catch)) {
751
751
  this.expect(TokenType.Catch);
752
- const catchVar = this.expect(TokenType.Ident).value;
752
+ const catchVar = this.expect(TokenType.Ident, "catch requires a variable name, e.g. catch err { ... }").value;
753
753
  const catchBody = this.parseBlock();
754
754
  return { kind: "TryCatchExpr", body, catchVar, catchBody, loc };
755
755
  }
@@ -1026,7 +1026,17 @@ export class Parser {
1026
1026
  }
1027
1027
  if (t.type === TokenType.Int || t.type === TokenType.Float) {
1028
1028
  this.advance();
1029
- return { kind: "LiteralPattern", value: parseFloat(t.value), loc };
1029
+ const fromVal = parseFloat(t.value);
1030
+ if (this.at(TokenType.Range)) {
1031
+ this.advance();
1032
+ const toTok = this.peek();
1033
+ if (toTok.type !== TokenType.Int && toTok.type !== TokenType.Float) {
1034
+ throw new ParseError(`Expected number after '..' in range pattern`, this.loc());
1035
+ }
1036
+ this.advance();
1037
+ return { kind: "RangePattern", from: fromVal, to: parseFloat(toTok.value), loc };
1038
+ }
1039
+ return { kind: "LiteralPattern", value: fromVal, loc };
1030
1040
  }
1031
1041
  if (t.type === TokenType.String) {
1032
1042
  this.advance();
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const ARC_VERSION = "0.6.14";
1
+ export declare const ARC_VERSION = "0.6.16";
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.14";
2
+ export const ARC_VERSION = "0.6.16";
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.14",
3
+ "version": "0.6.16",
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": {
@@ -16,6 +16,7 @@
16
16
  "scripts": {
17
17
  "build": "tsc",
18
18
  "copy-stdlib": "node -e \"const fs=require('fs');const p=require('path');function cp(s,d){fs.mkdirSync(d,{recursive:true});for(const f of fs.readdirSync(s)){const sp=p.join(s,f),dp=p.join(d,f);fs.statSync(sp).isDirectory()?cp(sp,dp):fs.copyFileSync(sp,dp)}}cp(p.join(__dirname,'..','stdlib'),p.join(__dirname,'stdlib'))\"",
19
+ "postinstall": "node -e \"console.log('\\n ⚡ Arc installed! Try: arc repl\\n Docs: https://docs.arclang.dev\\n Playground: https://play.arclang.dev\\n')\"",
19
20
  "prepublishOnly": "npm run copy-stdlib && tsc",
20
21
  "run": "tsx src/index.ts run",
21
22
  "parse": "tsx src/index.ts parse"
package/stdlib/crypto.arc CHANGED
@@ -1,62 +1,62 @@
1
- # Arc Standard Library: crypto module
2
- # Hashing, encryption, and cryptographic utilities
3
-
4
- # --- Hashing ---
5
-
6
- pub fn md5(text) => crypto_hash("md5", text)
7
- pub fn sha1(text) => crypto_hash("sha1", text)
8
- pub fn sha256(text) => crypto_hash("sha256", text)
9
- pub fn sha512(text) => crypto_hash("sha512", text)
10
-
11
- # --- HMAC ---
12
-
13
- pub fn hmac_sha256(key, message) => crypto_hmac("sha256", key, message)
14
- pub fn hmac_sha512(key, message) => crypto_hmac("sha512", key, message)
15
-
16
- # --- Random ---
17
-
18
- pub fn random_bytes(n) {
19
- if n < 0 { panic("random_bytes: n must be non-negative") }
20
- el { crypto_random_bytes(n) }
21
- }
22
-
23
- pub fn random_int(min, max) {
24
- if min > max { panic("random_int: min must be <= max") }
25
- el { crypto_random_int(min, max) }
26
- }
27
-
28
- pub fn uuid() => crypto_uuid()
29
-
30
- # --- Base64 ---
31
-
32
- pub fn base64_encode(text) => crypto_encode_base64(text)
33
- pub fn base64_decode(text) => crypto_decode_base64(text)
34
-
35
- # --- Password Hashing ---
36
-
37
- pub fn hash_password(password, salt) {
38
- let input = salt ++ ":" ++ password
39
- let mut hash = sha256(input)
40
- for i in 0..100 {
41
- hash = sha256(hash ++ salt)
42
- }
43
- hash
44
- }
45
-
46
- pub fn verify_password(password, salt, hash) {
47
- let computed = hash_password(password, salt)
48
- computed == hash
49
- }
50
-
51
- # --- Utility ---
52
-
53
- pub fn constant_time_eq(a, b) {
54
- if len(a) != len(b) { false }
55
- el {
56
- let mut result = true
57
- for i in 0..len(a) {
58
- if char_at(a, i) != char_at(b, i) { result = false }
59
- }
60
- result
61
- }
62
- }
1
+ # Arc Standard Library: crypto module
2
+ # Hashing, encryption, and cryptographic utilities
3
+
4
+ # --- Hashing ---
5
+
6
+ pub fn md5(text) => crypto_hash("md5", text)
7
+ pub fn sha1(text) => crypto_hash("sha1", text)
8
+ pub fn sha256(text) => crypto_hash("sha256", text)
9
+ pub fn sha512(text) => crypto_hash("sha512", text)
10
+
11
+ # --- HMAC ---
12
+
13
+ pub fn hmac_sha256(key, message) => crypto_hmac("sha256", key, message)
14
+ pub fn hmac_sha512(key, message) => crypto_hmac("sha512", key, message)
15
+
16
+ # --- Random ---
17
+
18
+ pub fn random_bytes(n) {
19
+ if n < 0 { panic("random_bytes: n must be non-negative") }
20
+ el { crypto_random_bytes(n) }
21
+ }
22
+
23
+ pub fn random_int(min, max) {
24
+ if min > max { panic("random_int: min must be <= max") }
25
+ el { crypto_random_int(min, max) }
26
+ }
27
+
28
+ pub fn uuid() => crypto_uuid()
29
+
30
+ # --- Base64 ---
31
+
32
+ pub fn base64_encode(text) => crypto_encode_base64(text)
33
+ pub fn base64_decode(text) => crypto_decode_base64(text)
34
+
35
+ # --- Password Hashing ---
36
+
37
+ pub fn hash_password(password, salt) {
38
+ let input = salt ++ ":" ++ password
39
+ let mut hash = sha256(input)
40
+ for i in 0..100 {
41
+ hash = sha256(hash ++ salt)
42
+ }
43
+ hash
44
+ }
45
+
46
+ pub fn verify_password(password, salt, hash) {
47
+ let computed = hash_password(password, salt)
48
+ computed == hash
49
+ }
50
+
51
+ # --- Utility ---
52
+
53
+ pub fn constant_time_eq(a, b) {
54
+ if len(a) != len(b) { false }
55
+ el {
56
+ let mut result = true
57
+ for i in 0..len(a) {
58
+ if char_at(a, i) != char_at(b, i) { result = false }
59
+ }
60
+ result
61
+ }
62
+ }
@@ -1,75 +1,75 @@
1
- # Arc Standard Library: datetime module
2
- # Comprehensive date and time utilities
3
-
4
- # Constants
5
- let MS_PER_MINUTE = 60000
6
- let MS_PER_HOUR = 3600000
7
- let MS_PER_DAY = 86400000
8
-
9
- # Returns the current timestamp in milliseconds since Unix epoch
10
- pub fn now() => __builtin_now()
11
-
12
- # Returns today's date as a map {year, month, day}
13
- pub fn today() {
14
- let ts = now()
15
- let days = int(ts / MS_PER_DAY)
16
- __builtin_date_from_ts(ts)
17
- }
18
-
19
- # Parse a date string with the given format to a timestamp
20
- # Format tokens: YYYY, MM, DD, hh, mm, ss
21
- pub fn parse(date_string, format) => __builtin_date_parse(date_string, format)
22
-
23
- # Format a timestamp to a string using the given format
24
- # Format tokens: YYYY, MM, DD, hh, mm, ss
25
- pub fn format(timestamp, format_string) => __builtin_date_format(timestamp, format_string)
26
-
27
- # Add days to a timestamp, returns new timestamp
28
- pub fn add_days(timestamp, days) => timestamp + days * MS_PER_DAY
29
-
30
- # Add hours to a timestamp, returns new timestamp
31
- pub fn add_hours(timestamp, hours) => timestamp + hours * MS_PER_HOUR
32
-
33
- # Add minutes to a timestamp, returns new timestamp
34
- pub fn add_minutes(timestamp, minutes) => timestamp + minutes * MS_PER_MINUTE
35
-
36
- # Difference in days between two timestamps (absolute value)
37
- pub fn diff_days(ts1, ts2) {
38
- let diff = ts1 - ts2
39
- let abs_diff = if diff < 0 { 0 - diff } el { diff }
40
- int(abs_diff / MS_PER_DAY)
41
- }
42
-
43
- # Difference in hours between two timestamps (absolute value)
44
- pub fn diff_hours(ts1, ts2) {
45
- let diff = ts1 - ts2
46
- let abs_diff = if diff < 0 { 0 - diff } el { diff }
47
- int(abs_diff / MS_PER_HOUR)
48
- }
49
-
50
- # Difference in minutes between two timestamps (absolute value)
51
- pub fn diff_minutes(ts1, ts2) {
52
- let diff = ts1 - ts2
53
- let abs_diff = if diff < 0 { 0 - diff } el { diff }
54
- int(abs_diff / MS_PER_MINUTE)
55
- }
56
-
57
- # Returns the day of the week (0 = Sunday, 6 = Saturday)
58
- pub fn day_of_week(timestamp) {
59
- # Jan 1 1970 was a Thursday (4)
60
- let days = int(timestamp / MS_PER_DAY)
61
- let result = ((days + 4) % 7 + 7) % 7
62
- result
63
- }
64
-
65
- # Returns true if ts1 is before ts2
66
- pub fn is_before(ts1, ts2) => ts1 < ts2
67
-
68
- # Returns true if ts1 is after ts2
69
- pub fn is_after(ts1, ts2) => ts1 > ts2
70
-
71
- # Convert a timestamp to an ISO 8601 string
72
- pub fn to_iso(timestamp) => __builtin_date_to_iso(timestamp)
73
-
74
- # Parse an ISO 8601 string to a timestamp
75
- pub fn from_iso(iso_string) => __builtin_date_from_iso(iso_string)
1
+ # Arc Standard Library: datetime module
2
+ # Comprehensive date and time utilities
3
+
4
+ # Constants
5
+ let MS_PER_MINUTE = 60000
6
+ let MS_PER_HOUR = 3600000
7
+ let MS_PER_DAY = 86400000
8
+
9
+ # Returns the current timestamp in milliseconds since Unix epoch
10
+ pub fn now() => __builtin_now()
11
+
12
+ # Returns today's date as a map {year, month, day}
13
+ pub fn today() {
14
+ let ts = now()
15
+ let days = int(ts / MS_PER_DAY)
16
+ __builtin_date_from_ts(ts)
17
+ }
18
+
19
+ # Parse a date string with the given format to a timestamp
20
+ # Format tokens: YYYY, MM, DD, hh, mm, ss
21
+ pub fn parse(date_string, format) => __builtin_date_parse(date_string, format)
22
+
23
+ # Format a timestamp to a string using the given format
24
+ # Format tokens: YYYY, MM, DD, hh, mm, ss
25
+ pub fn format(timestamp, format_string) => __builtin_date_format(timestamp, format_string)
26
+
27
+ # Add days to a timestamp, returns new timestamp
28
+ pub fn add_days(timestamp, days) => timestamp + days * MS_PER_DAY
29
+
30
+ # Add hours to a timestamp, returns new timestamp
31
+ pub fn add_hours(timestamp, hours) => timestamp + hours * MS_PER_HOUR
32
+
33
+ # Add minutes to a timestamp, returns new timestamp
34
+ pub fn add_minutes(timestamp, minutes) => timestamp + minutes * MS_PER_MINUTE
35
+
36
+ # Difference in days between two timestamps (absolute value)
37
+ pub fn diff_days(ts1, ts2) {
38
+ let diff = ts1 - ts2
39
+ let abs_diff = if diff < 0 { 0 - diff } el { diff }
40
+ int(abs_diff / MS_PER_DAY)
41
+ }
42
+
43
+ # Difference in hours between two timestamps (absolute value)
44
+ pub fn diff_hours(ts1, ts2) {
45
+ let diff = ts1 - ts2
46
+ let abs_diff = if diff < 0 { 0 - diff } el { diff }
47
+ int(abs_diff / MS_PER_HOUR)
48
+ }
49
+
50
+ # Difference in minutes between two timestamps (absolute value)
51
+ pub fn diff_minutes(ts1, ts2) {
52
+ let diff = ts1 - ts2
53
+ let abs_diff = if diff < 0 { 0 - diff } el { diff }
54
+ int(abs_diff / MS_PER_MINUTE)
55
+ }
56
+
57
+ # Returns the day of the week (0 = Sunday, 6 = Saturday)
58
+ pub fn day_of_week(timestamp) {
59
+ # Jan 1 1970 was a Thursday (4)
60
+ let days = int(timestamp / MS_PER_DAY)
61
+ let result = ((days + 4) % 7 + 7) % 7
62
+ result
63
+ }
64
+
65
+ # Returns true if ts1 is before ts2
66
+ pub fn is_before(ts1, ts2) => ts1 < ts2
67
+
68
+ # Returns true if ts1 is after ts2
69
+ pub fn is_after(ts1, ts2) => ts1 > ts2
70
+
71
+ # Convert a timestamp to an ISO 8601 string
72
+ pub fn to_iso(timestamp) => __builtin_date_to_iso(timestamp)
73
+
74
+ # Parse an ISO 8601 string to a timestamp
75
+ pub fn from_iso(iso_string) => __builtin_date_from_iso(iso_string)
package/stdlib/error.arc CHANGED
@@ -1,42 +1,42 @@
1
- # Arc Standard Library: error module
2
- # Structured error handling
3
-
4
- # Create a structured error
5
- pub fn error(code, message) => error_new(code, message)
6
-
7
- # Check if a value is an error
8
- pub fn is_error(value) => error_is_error(value)
9
-
10
- # Wrap an error with additional context
11
- pub fn wrap_error(err, context) => error_wrap(err, context)
12
-
13
- # Try executing a function, return Ok/Err result
14
- pub fn try_fn(f) => error_try(f)
15
-
16
- # Execute fn, call handler if result is an error
17
- pub fn try_catch(f, handler) {
18
- let result = f()
19
- if is_error(result) { handler(result) }
20
- el { result }
21
- }
22
-
23
- # Execute fn, always run cleanup
24
- pub fn try_finally(f, cleanup) {
25
- let result = f()
26
- cleanup()
27
- result
28
- }
29
-
30
- # Full try/catch/finally
31
- pub fn try_catch_finally(f, handler, cleanup) {
32
- let result = f()
33
- let handled = if is_error(result) { handler(result) } el { result }
34
- cleanup()
35
- handled
36
- }
37
-
38
- # Throw an error (raises a runtime exception)
39
- pub fn throw(code, message) => error_throw(code, message)
40
-
41
- # Retry a function up to n times, returning the first success or last error
42
- pub fn retry(f, times) => error_retry(f, times)
1
+ # Arc Standard Library: error module
2
+ # Structured error handling
3
+
4
+ # Create a structured error
5
+ pub fn error(code, message) => error_new(code, message)
6
+
7
+ # Check if a value is an error
8
+ pub fn is_error(value) => error_is_error(value)
9
+
10
+ # Wrap an error with additional context
11
+ pub fn wrap_error(err, context) => error_wrap(err, context)
12
+
13
+ # Try executing a function, return Ok/Err result
14
+ pub fn try_fn(f) => error_try(f)
15
+
16
+ # Execute fn, call handler if result is an error
17
+ pub fn try_catch(f, handler) {
18
+ let result = f()
19
+ if is_error(result) { handler(result) }
20
+ el { result }
21
+ }
22
+
23
+ # Execute fn, always run cleanup
24
+ pub fn try_finally(f, cleanup) {
25
+ let result = f()
26
+ cleanup()
27
+ result
28
+ }
29
+
30
+ # Full try/catch/finally
31
+ pub fn try_catch_finally(f, handler, cleanup) {
32
+ let result = f()
33
+ let handled = if is_error(result) { handler(result) } el { result }
34
+ cleanup()
35
+ handled
36
+ }
37
+
38
+ # Throw an error (raises a runtime exception)
39
+ pub fn throw(code, message) => error_throw(code, message)
40
+
41
+ # Retry a function up to n times, returning the first success or last error
42
+ pub fn retry(f, times) => error_retry(f, times)
package/stdlib/llm.arc CHANGED
@@ -1,193 +1,193 @@
1
- # Arc Standard Library: llm module
2
- # Multi-provider LLM API integration
3
- # Makes it natural for AI agents to call other AI agents.
4
-
5
- use json
6
- use os
7
-
8
- # --- Provider registry ---
9
-
10
- pub fn providers() {
11
- [
12
- {name: "openai", base_url: "https://api.openai.com/v1", env_key: "OPENAI_API_KEY"},
13
- {name: "anthropic", base_url: "https://api.anthropic.com/v1", env_key: "ANTHROPIC_API_KEY"}
14
- ]
15
- }
16
-
17
- fn _provider_config(provider) {
18
- let all = providers()
19
- let mut found = nil
20
- for p in all {
21
- if p.name == provider { found = p }
22
- }
23
- if found == nil { error_new("llm", "Unknown LLM provider: " ++ provider) }
24
- found
25
- }
26
-
27
- fn _api_key(provider) {
28
- let config = _provider_config(provider)
29
- let key = os.get_env(config.env_key)
30
- if key == nil { error_new("llm", "Missing API key: set " ++ config.env_key ++ " environment variable") }
31
- key
32
- }
33
-
34
- # --- OpenAI payload construction ---
35
-
36
- fn _openai_chat_payload(model, messages, options) {
37
- let mut payload = {}
38
- payload["model"] = model
39
- payload["messages"] = messages
40
- if options != nil {
41
- let ks = keys(options)
42
- for k in ks {
43
- payload[k] = options[k]
44
- }
45
- }
46
- payload
47
- }
48
-
49
- fn _openai_headers(api_key) {
50
- let mut h = {}
51
- h["Authorization"] = "Bearer " ++ api_key
52
- h["Content-Type"] = "application/json"
53
- h
54
- }
55
-
56
- # --- Anthropic payload construction ---
57
-
58
- fn _anthropic_chat_payload(model, messages, options) {
59
- # Anthropic expects system message separate from messages
60
- let mut system_msg = nil
61
- let mut user_msgs = []
62
- for m in messages {
63
- if m.role == "system" {
64
- system_msg = m.content
65
- } el {
66
- user_msgs = push(user_msgs, m)
67
- }
68
- }
69
- let mut payload = {}
70
- payload["model"] = model
71
- payload["messages"] = user_msgs
72
- if system_msg != nil { payload["system"] = system_msg }
73
- payload["max_tokens"] = if options != nil and options["max_tokens"] != nil { options["max_tokens"] } el { 1024 }
74
- if options != nil {
75
- let ks = keys(options)
76
- for k in ks {
77
- if k != "max_tokens" { payload[k] = options[k] }
78
- }
79
- }
80
- payload
81
- }
82
-
83
- fn _anthropic_headers(api_key) {
84
- let mut h = {}
85
- h["x-api-key"] = api_key
86
- h["anthropic-version"] = "2023-06-01"
87
- h["Content-Type"] = "application/json"
88
- h
89
- }
90
-
91
- # --- Core API ---
92
-
93
- # Send a chat completion request
94
- # provider: "openai" | "anthropic"
95
- # model: e.g. "gpt-4o", "claude-sonnet-4-20250514"
96
- # messages: [{role: "user", content: "Hello"}]
97
- # options: {temperature: 0.7, max_tokens: 1024} (optional)
98
- pub fn chat(provider, model, messages, options) {
99
- let key = _api_key(provider)
100
-
101
- if provider == "openai" {
102
- let payload = _openai_chat_payload(model, messages, options)
103
- let body = json.to_json(payload)
104
- let headers = _openai_headers(key)
105
- let resp = @POST "https://api.openai.com/v1/chat/completions" {data: body, headers: headers}
106
- if resp.ok and resp.status == 200 {
107
- let data = resp.data
108
- {ok: true, content: data.choices[0].message.content, usage: data.usage, raw: data}
109
- } el {
110
- {ok: false, error: resp.data, status: resp.status}
111
- }
112
- } el if provider == "anthropic" {
113
- let payload = _anthropic_chat_payload(model, messages, options)
114
- let body = json.to_json(payload)
115
- let headers = _anthropic_headers(key)
116
- let resp = @POST "https://api.anthropic.com/v1/messages" {data: body, headers: headers}
117
- if resp.ok and resp.status == 200 {
118
- let data = resp.data
119
- let content = if type_of(data.content) == "list" and len(data.content) > 0 {
120
- data.content[0].text
121
- } el { nil }
122
- {ok: true, content: content, usage: data.usage, raw: data}
123
- } el {
124
- {ok: false, error: resp.data, status: resp.status}
125
- }
126
- } el {
127
- error_new("llm", "Unsupported provider: " ++ provider)
128
- }
129
- }
130
-
131
- # Simple text completion (wraps chat with a single user message)
132
- pub fn complete(provider, model, prompt, options) {
133
- let messages = [{role: "user", content: prompt}]
134
- chat(provider, model, messages, options)
135
- }
136
-
137
- # Streaming chat — calls callback with each chunk
138
- # Note: true streaming requires SSE parsing; this simulates via full response
139
- pub fn stream(provider, model, messages, callback) {
140
- # For now, do a regular call and invoke callback with the full response
141
- # True SSE streaming would require async I/O support in the runtime
142
- let result = chat(provider, model, messages, nil)
143
- if result.ok {
144
- callback(result.content)
145
- }
146
- result
147
- }
148
-
149
- # List available models for a provider
150
- pub fn models(provider) {
151
- let key = _api_key(provider)
152
-
153
- if provider == "openai" {
154
- let headers = _openai_headers(key)
155
- let resp = @GET "https://api.openai.com/v1/models" {headers: headers}
156
- if resp.ok and resp.status == 200 {
157
- map(resp.data.data, m => m.id)
158
- } el {
159
- []
160
- }
161
- } el if provider == "anthropic" {
162
- # Anthropic doesn't have a public models endpoint; return known models
163
- [
164
- "claude-sonnet-4-20250514",
165
- "claude-opus-4-20250514",
166
- "claude-haiku-35-20241022",
167
- "claude-sonnet-3-5-20241022"
168
- ]
169
- } el {
170
- []
171
- }
172
- }
173
-
174
- # Estimate API cost in USD
175
- # Pricing approximate as of 2025
176
- pub fn estimate_cost(model, input_tokens, output_tokens) {
177
- let pricing = _model_pricing(model)
178
- let input_cost = (input_tokens / 1000000.0) * pricing.input
179
- let output_cost = (output_tokens / 1000000.0) * pricing.output
180
- {input_cost: input_cost, output_cost: output_cost, total: input_cost + output_cost, currency: "USD"}
181
- }
182
-
183
- fn _model_pricing(model) {
184
- # Prices per million tokens (input, output)
185
- if starts(model, "gpt-4o-mini") { {input: 0.15, output: 0.60} }
186
- el if starts(model, "gpt-4o") { {input: 2.50, output: 10.00} }
187
- el if starts(model, "gpt-4-turbo") { {input: 10.00, output: 30.00} }
188
- el if starts(model, "gpt-3.5") { {input: 0.50, output: 1.50} }
189
- el if contains(model, "opus") { {input: 15.00, output: 75.00} }
190
- el if contains(model, "sonnet") { {input: 3.00, output: 15.00} }
191
- el if contains(model, "haiku") { {input: 0.80, output: 4.00} }
192
- el { {input: 1.00, output: 3.00} } # fallback estimate
193
- }
1
+ # Arc Standard Library: llm module
2
+ # Multi-provider LLM API integration
3
+ # Makes it natural for AI agents to call other AI agents.
4
+
5
+ use json
6
+ use os
7
+
8
+ # --- Provider registry ---
9
+
10
+ pub fn providers() {
11
+ [
12
+ {name: "openai", base_url: "https://api.openai.com/v1", env_key: "OPENAI_API_KEY"},
13
+ {name: "anthropic", base_url: "https://api.anthropic.com/v1", env_key: "ANTHROPIC_API_KEY"}
14
+ ]
15
+ }
16
+
17
+ fn _provider_config(provider) {
18
+ let all = providers()
19
+ let mut found = nil
20
+ for p in all {
21
+ if p.name == provider { found = p }
22
+ }
23
+ if found == nil { error_new("llm", "Unknown LLM provider: " ++ provider) }
24
+ found
25
+ }
26
+
27
+ fn _api_key(provider) {
28
+ let config = _provider_config(provider)
29
+ let key = os.get_env(config.env_key)
30
+ if key == nil { error_new("llm", "Missing API key: set " ++ config.env_key ++ " environment variable") }
31
+ key
32
+ }
33
+
34
+ # --- OpenAI payload construction ---
35
+
36
+ fn _openai_chat_payload(model, messages, options) {
37
+ let mut payload = {}
38
+ payload["model"] = model
39
+ payload["messages"] = messages
40
+ if options != nil {
41
+ let ks = keys(options)
42
+ for k in ks {
43
+ payload[k] = options[k]
44
+ }
45
+ }
46
+ payload
47
+ }
48
+
49
+ fn _openai_headers(api_key) {
50
+ let mut h = {}
51
+ h["Authorization"] = "Bearer " ++ api_key
52
+ h["Content-Type"] = "application/json"
53
+ h
54
+ }
55
+
56
+ # --- Anthropic payload construction ---
57
+
58
+ fn _anthropic_chat_payload(model, messages, options) {
59
+ # Anthropic expects system message separate from messages
60
+ let mut system_msg = nil
61
+ let mut user_msgs = []
62
+ for m in messages {
63
+ if m.role == "system" {
64
+ system_msg = m.content
65
+ } el {
66
+ user_msgs = push(user_msgs, m)
67
+ }
68
+ }
69
+ let mut payload = {}
70
+ payload["model"] = model
71
+ payload["messages"] = user_msgs
72
+ if system_msg != nil { payload["system"] = system_msg }
73
+ payload["max_tokens"] = if options != nil and options["max_tokens"] != nil { options["max_tokens"] } el { 1024 }
74
+ if options != nil {
75
+ let ks = keys(options)
76
+ for k in ks {
77
+ if k != "max_tokens" { payload[k] = options[k] }
78
+ }
79
+ }
80
+ payload
81
+ }
82
+
83
+ fn _anthropic_headers(api_key) {
84
+ let mut h = {}
85
+ h["x-api-key"] = api_key
86
+ h["anthropic-version"] = "2023-06-01"
87
+ h["Content-Type"] = "application/json"
88
+ h
89
+ }
90
+
91
+ # --- Core API ---
92
+
93
+ # Send a chat completion request
94
+ # provider: "openai" | "anthropic"
95
+ # model: e.g. "gpt-4o", "claude-sonnet-4-20250514"
96
+ # messages: [{role: "user", content: "Hello"}]
97
+ # options: {temperature: 0.7, max_tokens: 1024} (optional)
98
+ pub fn chat(provider, model, messages, options) {
99
+ let key = _api_key(provider)
100
+
101
+ if provider == "openai" {
102
+ let payload = _openai_chat_payload(model, messages, options)
103
+ let body = json.to_json(payload)
104
+ let headers = _openai_headers(key)
105
+ let resp = @POST "https://api.openai.com/v1/chat/completions" {data: body, headers: headers}
106
+ if resp.ok and resp.status == 200 {
107
+ let data = resp.data
108
+ {ok: true, content: data.choices[0].message.content, usage: data.usage, raw: data}
109
+ } el {
110
+ {ok: false, error: resp.data, status: resp.status}
111
+ }
112
+ } el if provider == "anthropic" {
113
+ let payload = _anthropic_chat_payload(model, messages, options)
114
+ let body = json.to_json(payload)
115
+ let headers = _anthropic_headers(key)
116
+ let resp = @POST "https://api.anthropic.com/v1/messages" {data: body, headers: headers}
117
+ if resp.ok and resp.status == 200 {
118
+ let data = resp.data
119
+ let content = if type_of(data.content) == "list" and len(data.content) > 0 {
120
+ data.content[0].text
121
+ } el { nil }
122
+ {ok: true, content: content, usage: data.usage, raw: data}
123
+ } el {
124
+ {ok: false, error: resp.data, status: resp.status}
125
+ }
126
+ } el {
127
+ error_new("llm", "Unsupported provider: " ++ provider)
128
+ }
129
+ }
130
+
131
+ # Simple text completion (wraps chat with a single user message)
132
+ pub fn complete(provider, model, prompt, options) {
133
+ let messages = [{role: "user", content: prompt}]
134
+ chat(provider, model, messages, options)
135
+ }
136
+
137
+ # Streaming chat — calls callback with each chunk
138
+ # Note: true streaming requires SSE parsing; this simulates via full response
139
+ pub fn stream(provider, model, messages, callback) {
140
+ # For now, do a regular call and invoke callback with the full response
141
+ # True SSE streaming would require async I/O support in the runtime
142
+ let result = chat(provider, model, messages, nil)
143
+ if result.ok {
144
+ callback(result.content)
145
+ }
146
+ result
147
+ }
148
+
149
+ # List available models for a provider
150
+ pub fn models(provider) {
151
+ let key = _api_key(provider)
152
+
153
+ if provider == "openai" {
154
+ let headers = _openai_headers(key)
155
+ let resp = @GET "https://api.openai.com/v1/models" {headers: headers}
156
+ if resp.ok and resp.status == 200 {
157
+ map(resp.data.data, m => m.id)
158
+ } el {
159
+ []
160
+ }
161
+ } el if provider == "anthropic" {
162
+ # Anthropic doesn't have a public models endpoint; return known models
163
+ [
164
+ "claude-sonnet-4-20250514",
165
+ "claude-opus-4-20250514",
166
+ "claude-haiku-35-20241022",
167
+ "claude-sonnet-3-5-20241022"
168
+ ]
169
+ } el {
170
+ []
171
+ }
172
+ }
173
+
174
+ # Estimate API cost in USD
175
+ # Pricing approximate as of 2025
176
+ pub fn estimate_cost(model, input_tokens, output_tokens) {
177
+ let pricing = _model_pricing(model)
178
+ let input_cost = (input_tokens / 1000000.0) * pricing.input
179
+ let output_cost = (output_tokens / 1000000.0) * pricing.output
180
+ {input_cost: input_cost, output_cost: output_cost, total: input_cost + output_cost, currency: "USD"}
181
+ }
182
+
183
+ fn _model_pricing(model) {
184
+ # Prices per million tokens (input, output)
185
+ if starts(model, "gpt-4o-mini") { {input: 0.15, output: 0.60} }
186
+ el if starts(model, "gpt-4o") { {input: 2.50, output: 10.00} }
187
+ el if starts(model, "gpt-4-turbo") { {input: 10.00, output: 30.00} }
188
+ el if starts(model, "gpt-3.5") { {input: 0.50, output: 1.50} }
189
+ el if contains(model, "opus") { {input: 15.00, output: 75.00} }
190
+ el if contains(model, "sonnet") { {input: 3.00, output: 15.00} }
191
+ el if contains(model, "haiku") { {input: 0.80, output: 4.00} }
192
+ el { {input: 1.00, output: 3.00} } # fallback estimate
193
+ }
package/stdlib/math.arc CHANGED
@@ -1,133 +1,133 @@
1
- # Arc Standard Library: math module
2
-
3
- # === Constants ===
4
- pub let PI = 3.141592653589793
5
- pub let E = 2.718281828459045
6
- pub let TAU = 6.283185307179586
7
- # INF and NAN available via native runtime
8
-
9
- # === Basic ===
10
- pub fn abs(x) => if x < 0 { 0 - x } el { x }
11
-
12
- pub fn sign(x) {
13
- if x > 0 { 1 }
14
- el if x < 0 { -1 }
15
- el { 0 }
16
- }
17
-
18
- pub fn clamp(x, lo, hi) {
19
- if x < lo { lo }
20
- el if x > hi { hi }
21
- el { x }
22
- }
23
-
24
- # === Rounding ===
25
- pub fn ceil(x) => __native("math.ceil", x)
26
-
27
- pub fn floor(x) => int(x)
28
-
29
- pub fn round(x) => int(x + 0.5)
30
-
31
- # === Powers & Roots ===
32
- pub fn pow(base, exp) {
33
- if exp == 0 { 1 }
34
- el if exp < 0 and base == 0 { nil }
35
- el if type_of(exp) == "float" { __native("math.pow", base, exp) }
36
- el if exp < 0 { 1.0 / pow(base, 0 - exp) }
37
- el {
38
- let mut result = 1
39
- let mut b = base
40
- let mut e = exp
41
- do {
42
- if e % 2 == 1 { result = result * b }
43
- e = int(e / 2)
44
- b = b * b
45
- } until e == 0
46
- result
47
- }
48
- }
49
-
50
- pub fn sqrt(x) {
51
- if x == 0 { 0.0 }
52
- el if x < 0 { nil }
53
- el {
54
- let mut guess = x / 2.0
55
- for i in 0..25 {
56
- guess = (guess + x / guess) / 2.0
57
- }
58
- guess
59
- }
60
- }
61
-
62
- pub fn cbrt(x) => __native("math.cbrt", x)
63
-
64
- pub fn hypot(x, y) => __native("math.hypot", x, y)
65
-
66
- # === Trigonometry ===
67
- pub fn sin(x) => __native("math.sin", x)
68
- pub fn cos(x) => __native("math.cos", x)
69
- pub fn tan(x) => __native("math.tan", x)
70
- pub fn asin(x) => __native("math.asin", x)
71
- pub fn acos(x) => __native("math.acos", x)
72
- pub fn atan(x) => __native("math.atan", x)
73
- pub fn atan2(y, x) => __native("math.atan2", y, x)
74
-
75
- pub fn degrees(rad) => rad * 180.0 / PI
76
- pub fn radians(deg) => deg * PI / 180.0
77
-
78
- # === Logarithmic & Exponential ===
79
- pub fn log(x) => __native("math.log", x)
80
- pub fn log2(x) => __native("math.log2", x)
81
- pub fn log10(x) => __native("math.log10", x)
82
- pub fn exp(x) => __native("math.exp", x)
83
-
84
- # === Combinatorics ===
85
- pub fn factorial(n) {
86
- if n < 0 { nil }
87
- el if n <= 1 { 1 }
88
- el {
89
- let mut result = 1
90
- for i in 2..n + 1 {
91
- result = result * i
92
- }
93
- if str(result) == "Infinity" { nil }
94
- el { result }
95
- }
96
- }
97
-
98
- pub fn gcd(a, b) {
99
- let mut x = abs(a)
100
- let mut y = abs(b)
101
- if x == 0 and y == 0 { 0 }
102
- el {
103
- do {
104
- let t = y
105
- y = x % y
106
- x = t
107
- } until y == 0
108
- x
109
- }
110
- }
111
-
112
- pub fn lcm(a, b) {
113
- let d = gcd(a, b)
114
- if d == 0 { 0 }
115
- el { abs(a / d) * b }
116
- }
117
-
118
- # === Aggregation ===
119
- pub fn sum(lst) {
120
- let mut total = 0
121
- for x in lst {
122
- if type_of(x) == "int" or type_of(x) == "float" {
123
- total = total + x
124
- }
125
- }
126
- total
127
- }
128
-
129
- pub fn product(lst) {
130
- let mut total = 1
131
- for x in lst { total = total * x }
132
- total
133
- }
1
+ # Arc Standard Library: math module
2
+
3
+ # === Constants ===
4
+ pub let PI = 3.141592653589793
5
+ pub let E = 2.718281828459045
6
+ pub let TAU = 6.283185307179586
7
+ # INF and NAN available via native runtime
8
+
9
+ # === Basic ===
10
+ pub fn abs(x) => if x < 0 { 0 - x } el { x }
11
+
12
+ pub fn sign(x) {
13
+ if x > 0 { 1 }
14
+ el if x < 0 { -1 }
15
+ el { 0 }
16
+ }
17
+
18
+ pub fn clamp(x, lo, hi) {
19
+ if x < lo { lo }
20
+ el if x > hi { hi }
21
+ el { x }
22
+ }
23
+
24
+ # === Rounding ===
25
+ pub fn ceil(x) => __native("math.ceil", x)
26
+
27
+ pub fn floor(x) => int(x)
28
+
29
+ pub fn round(x) => int(x + 0.5)
30
+
31
+ # === Powers & Roots ===
32
+ pub fn pow(base, exp) {
33
+ if exp == 0 { 1 }
34
+ el if exp < 0 and base == 0 { nil }
35
+ el if type_of(exp) == "float" { __native("math.pow", base, exp) }
36
+ el if exp < 0 { 1.0 / pow(base, 0 - exp) }
37
+ el {
38
+ let mut result = 1
39
+ let mut b = base
40
+ let mut e = exp
41
+ do {
42
+ if e % 2 == 1 { result = result * b }
43
+ e = int(e / 2)
44
+ b = b * b
45
+ } until e == 0
46
+ result
47
+ }
48
+ }
49
+
50
+ pub fn sqrt(x) {
51
+ if x == 0 { 0.0 }
52
+ el if x < 0 { nil }
53
+ el {
54
+ let mut guess = x / 2.0
55
+ for i in 0..25 {
56
+ guess = (guess + x / guess) / 2.0
57
+ }
58
+ guess
59
+ }
60
+ }
61
+
62
+ pub fn cbrt(x) => __native("math.cbrt", x)
63
+
64
+ pub fn hypot(x, y) => __native("math.hypot", x, y)
65
+
66
+ # === Trigonometry ===
67
+ pub fn sin(x) => __native("math.sin", x)
68
+ pub fn cos(x) => __native("math.cos", x)
69
+ pub fn tan(x) => __native("math.tan", x)
70
+ pub fn asin(x) => __native("math.asin", x)
71
+ pub fn acos(x) => __native("math.acos", x)
72
+ pub fn atan(x) => __native("math.atan", x)
73
+ pub fn atan2(y, x) => __native("math.atan2", y, x)
74
+
75
+ pub fn degrees(rad) => rad * 180.0 / PI
76
+ pub fn radians(deg) => deg * PI / 180.0
77
+
78
+ # === Logarithmic & Exponential ===
79
+ pub fn log(x) => __native("math.log", x)
80
+ pub fn log2(x) => __native("math.log2", x)
81
+ pub fn log10(x) => __native("math.log10", x)
82
+ pub fn exp(x) => __native("math.exp", x)
83
+
84
+ # === Combinatorics ===
85
+ pub fn factorial(n) {
86
+ if n < 0 { nil }
87
+ el if n <= 1 { 1 }
88
+ el {
89
+ let mut result = 1
90
+ for i in 2..n + 1 {
91
+ result = result * i
92
+ }
93
+ if str(result) == "Infinity" { nil }
94
+ el { result }
95
+ }
96
+ }
97
+
98
+ pub fn gcd(a, b) {
99
+ let mut x = abs(a)
100
+ let mut y = abs(b)
101
+ if x == 0 and y == 0 { 0 }
102
+ el {
103
+ do {
104
+ let t = y
105
+ y = x % y
106
+ x = t
107
+ } until y == 0
108
+ x
109
+ }
110
+ }
111
+
112
+ pub fn lcm(a, b) {
113
+ let d = gcd(a, b)
114
+ if d == 0 { 0 }
115
+ el { abs(a / d) * b }
116
+ }
117
+
118
+ # === Aggregation ===
119
+ pub fn sum(lst) {
120
+ let mut total = 0
121
+ for x in lst {
122
+ if type_of(x) == "int" or type_of(x) == "float" {
123
+ total = total + x
124
+ }
125
+ }
126
+ total
127
+ }
128
+
129
+ pub fn product(lst) {
130
+ let mut total = 1
131
+ for x in lst { total = total * x }
132
+ total
133
+ }
package/stdlib/net.arc CHANGED
@@ -1,17 +1,17 @@
1
- # Arc Standard Library: net module
2
- # Networking utilities
3
-
4
- # --- URL ---
5
-
6
- pub fn url_parse(url) => net_url_parse(url)
7
- pub fn url_encode(text) => net_url_encode(text)
8
- pub fn url_decode(text) => net_url_decode(text)
9
-
10
- # --- Query String ---
11
-
12
- pub fn parse_query(query_string) => net_query_parse(query_string)
13
- pub fn build_query(params_map) => net_query_stringify(params_map)
14
-
15
- # --- IP ---
16
-
17
- pub fn ip_is_valid(addr) => net_ip_is_valid(addr)
1
+ # Arc Standard Library: net module
2
+ # Networking utilities
3
+
4
+ # --- URL ---
5
+
6
+ pub fn url_parse(url) => net_url_parse(url)
7
+ pub fn url_encode(text) => net_url_encode(text)
8
+ pub fn url_decode(text) => net_url_decode(text)
9
+
10
+ # --- Query String ---
11
+
12
+ pub fn parse_query(query_string) => net_query_parse(query_string)
13
+ pub fn build_query(params_map) => net_query_stringify(params_map)
14
+
15
+ # --- IP ---
16
+
17
+ pub fn ip_is_valid(addr) => net_ip_is_valid(addr)