arc-lang 0.6.9 → 0.6.11

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.
@@ -2562,10 +2562,11 @@ function evalExpr(expr, env) {
2562
2562
  const right = evalExpr(expr.right, env);
2563
2563
  switch (expr.op) {
2564
2564
  case "+": {
2565
- if (typeof left === "string" || typeof right === "string") {
2566
- if (left === null || right === null)
2567
- throw new ArcRuntimeError(`TypeError: cannot add nil`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2568
- return toStr(left) + toStr(right);
2565
+ if (typeof left === "string" && typeof right === "string") {
2566
+ return left + right;
2567
+ }
2568
+ if ((typeof left === "string" && typeof right !== "string") || (typeof left !== "string" && typeof right === "string")) {
2569
+ throw new ArcRuntimeError(`TypeError: cannot add ${typeof left} and ${typeof right} — use str() to convert, or ++ for string concatenation`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
2569
2570
  }
2570
2571
  if (left === null || right === null)
2571
2572
  throw new ArcRuntimeError(`TypeError: cannot add nil`, { code: ErrorCode.INVALID_OPERATOR, loc: expr.loc });
package/dist/lexer.js CHANGED
@@ -338,6 +338,19 @@ export function lex(source) {
338
338
  }
339
339
  num += advance();
340
340
  }
341
+ // Scientific notation: e.g. 1e10, 1.5e-3, 2E+6
342
+ if (i < source.length && (peek() === "e" || peek() === "E")) {
343
+ const next = peek(1);
344
+ if (next >= "0" && next <= "9" || next === "+" || next === "-") {
345
+ isFloat = true;
346
+ num += advance(); // consume e/E
347
+ if (peek() === "+" || peek() === "-")
348
+ num += advance(); // consume sign
349
+ while (i < source.length && peek() >= "0" && peek() <= "9") {
350
+ num += advance();
351
+ }
352
+ }
353
+ }
341
354
  tokens.push(tok(isFloat ? TokenType.Float : TokenType.Int, num, sl, sc));
342
355
  continue;
343
356
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arc-lang",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
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": {
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/json.arc CHANGED
@@ -198,6 +198,13 @@ pub fn get_path(obj, path) {
198
198
  for part in parts {
199
199
  if type_of(current) == "map" {
200
200
  current = current[part]
201
+ } el if type_of(current) == "list" {
202
+ let idx = int(part)
203
+ if idx != nil {
204
+ current = current[idx]
205
+ } el {
206
+ current = nil
207
+ }
201
208
  } el {
202
209
  current = nil
203
210
  }
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)
package/stdlib/regex.arc CHANGED
@@ -21,19 +21,19 @@ pub fn test(pattern, text) {
21
21
 
22
22
  # Replaces the first occurrence of `pattern` with `replacement` in `text`
23
23
  # (For global replacement, use replace_all)
24
- pub fn replace(pattern, replacement, text) {
24
+ pub fn replace(pattern, text, replacement) {
25
25
  let re = regex_new(pattern)
26
26
  regex_replace(re, replacement, text)
27
27
  }
28
28
 
29
29
  # Alias for replace — replaces only the first occurrence
30
- pub fn replace_first(pattern, replacement, text) {
30
+ pub fn replace_first(pattern, text, replacement) {
31
31
  let re = regex_new(pattern)
32
32
  regex_replace(re, replacement, text)
33
33
  }
34
34
 
35
35
  # Replaces all occurrences of `pattern` with `replacement` in `text`
36
- pub fn replace_all(pattern, replacement, text) {
36
+ pub fn replace_all(pattern, text, replacement) {
37
37
  let re = regex_new(pattern)
38
38
  regex_replace_all(re, replacement, text)
39
39
  }
@@ -56,6 +56,12 @@ pub fn capture_all(pattern, text) {
56
56
  regex_captures_all(re, text)
57
57
  }
58
58
 
59
+ # Returns a list of all matches of `pattern` in `text` (alias for find_all)
60
+ pub fn match_all(pattern, text) {
61
+ let re = regex_new(pattern)
62
+ regex_find_all(re, text)
63
+ }
64
+
59
65
  # Escapes all regex special characters in `text`
60
66
  pub fn escape(text) => __native("regex.escape", text)
61
67