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 +7 -1
- package/dist/interpreter.js +17 -0
- package/dist/parser.js +14 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/stdlib/crypto.arc +62 -62
- package/stdlib/datetime.arc +75 -75
- package/stdlib/error.arc +42 -42
- package/stdlib/llm.arc +193 -193
- package/stdlib/math.arc +133 -133
- package/stdlib/net.arc +17 -17
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";
|
package/dist/interpreter.js
CHANGED
|
@@ -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
|
-
|
|
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
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arc-lang",
|
|
3
|
-
"version": "0.6.
|
|
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
|
+
}
|
package/stdlib/datetime.arc
CHANGED
|
@@ -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)
|