arc-lang 0.5.6 → 0.5.8
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/interpreter.js +105 -18
- package/dist/lexer.js +3 -2
- package/dist/modules.js +8 -1
- package/dist/parser.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/interpreter.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Arc Language Tree-Walking Interpreter
|
|
2
2
|
import * as nodeCrypto from "crypto";
|
|
3
3
|
import * as nodeOs from "os";
|
|
4
|
+
import * as nodeFs from "fs";
|
|
5
|
+
import { execSync } from "child_process";
|
|
4
6
|
class Env {
|
|
5
7
|
parent;
|
|
6
8
|
vars = new Map();
|
|
@@ -71,6 +73,67 @@ function toStr(v) {
|
|
|
71
73
|
return `<async>`;
|
|
72
74
|
return String(v);
|
|
73
75
|
}
|
|
76
|
+
function syncFetch(method, url, body) {
|
|
77
|
+
// Build a small Node script that does fetch and prints JSON result
|
|
78
|
+
// Extract body string: if it's a map with a "data" field, use that; otherwise stringify
|
|
79
|
+
let bodyStr = null;
|
|
80
|
+
if (body != null) {
|
|
81
|
+
if (typeof body === "object" && "__map" in body) {
|
|
82
|
+
const m = body.entries;
|
|
83
|
+
const d = m.get("data");
|
|
84
|
+
bodyStr = d != null ? toStr(d) : toStr(body);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
bodyStr = toStr(body);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const bodyJson = bodyStr != null ? JSON.stringify(bodyStr) : "null";
|
|
91
|
+
// Pass config via env to avoid shell escaping issues
|
|
92
|
+
const fetchConfig = JSON.stringify({ method, url, body: bodyStr });
|
|
93
|
+
const script = `const c=JSON.parse(process.env.ARC_FETCH);(async()=>{const o={method:c.method};if(c.body!==null){o.body=c.body;o.headers={"Content-Type":"application/json"};}try{const r=await fetch(c.url,o);const t=await r.text();let d;try{d=JSON.parse(t)}catch{d=t}console.log(JSON.stringify({ok:true,status:r.status,data:d}))}catch(e){console.log(JSON.stringify({ok:false,status:0,data:e.message}))}})()`;
|
|
94
|
+
try {
|
|
95
|
+
const raw = execSync(`node -e "${script.replace(/"/g, '\\"')}"`, {
|
|
96
|
+
timeout: 30000,
|
|
97
|
+
encoding: "utf-8",
|
|
98
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
99
|
+
env: { ...process.env, ARC_FETCH: fetchConfig },
|
|
100
|
+
}).trim();
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
const entries = new Map();
|
|
103
|
+
entries.set("ok", parsed.ok);
|
|
104
|
+
entries.set("status", parsed.status);
|
|
105
|
+
// Convert nested objects/arrays to Arc values
|
|
106
|
+
entries.set("data", jsToArc(parsed.data));
|
|
107
|
+
entries.set("method", method);
|
|
108
|
+
entries.set("url", url);
|
|
109
|
+
return { __map: true, entries };
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
const entries = new Map();
|
|
113
|
+
entries.set("ok", false);
|
|
114
|
+
entries.set("status", 0);
|
|
115
|
+
entries.set("data", e.message || "fetch error");
|
|
116
|
+
entries.set("method", method);
|
|
117
|
+
entries.set("url", url);
|
|
118
|
+
return { __map: true, entries };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function jsToArc(v) {
|
|
122
|
+
if (v === null || v === undefined)
|
|
123
|
+
return null;
|
|
124
|
+
if (typeof v === "number" || typeof v === "string" || typeof v === "boolean")
|
|
125
|
+
return v;
|
|
126
|
+
if (Array.isArray(v))
|
|
127
|
+
return v.map(jsToArc);
|
|
128
|
+
if (typeof v === "object") {
|
|
129
|
+
const entries = new Map();
|
|
130
|
+
for (const [k, val] of Object.entries(v)) {
|
|
131
|
+
entries.set(k, jsToArc(val));
|
|
132
|
+
}
|
|
133
|
+
return { __map: true, entries };
|
|
134
|
+
}
|
|
135
|
+
return String(v);
|
|
136
|
+
}
|
|
74
137
|
function resolveAsync(v) {
|
|
75
138
|
if (v && typeof v === "object" && "__async" in v) {
|
|
76
139
|
return v.thunk();
|
|
@@ -644,7 +707,7 @@ function makePrelude(env) {
|
|
|
644
707
|
case "os.list_dir": {
|
|
645
708
|
try {
|
|
646
709
|
const fs = require("fs");
|
|
647
|
-
return
|
|
710
|
+
return nodeFs.readdirSync(args[0]);
|
|
648
711
|
}
|
|
649
712
|
catch {
|
|
650
713
|
return [];
|
|
@@ -653,7 +716,7 @@ function makePrelude(env) {
|
|
|
653
716
|
case "os.is_file": {
|
|
654
717
|
try {
|
|
655
718
|
const fs = require("fs");
|
|
656
|
-
return
|
|
719
|
+
return nodeFs.statSync(args[0]).isFile();
|
|
657
720
|
}
|
|
658
721
|
catch {
|
|
659
722
|
return false;
|
|
@@ -662,7 +725,7 @@ function makePrelude(env) {
|
|
|
662
725
|
case "os.is_dir": {
|
|
663
726
|
try {
|
|
664
727
|
const fs = require("fs");
|
|
665
|
-
return
|
|
728
|
+
return nodeFs.statSync(args[0]).isDirectory();
|
|
666
729
|
}
|
|
667
730
|
catch {
|
|
668
731
|
return false;
|
|
@@ -671,7 +734,7 @@ function makePrelude(env) {
|
|
|
671
734
|
case "os.mkdir": {
|
|
672
735
|
try {
|
|
673
736
|
const fs = require("fs");
|
|
674
|
-
|
|
737
|
+
nodeFs.mkdirSync(args[0], { recursive: true });
|
|
675
738
|
return true;
|
|
676
739
|
}
|
|
677
740
|
catch {
|
|
@@ -681,7 +744,7 @@ function makePrelude(env) {
|
|
|
681
744
|
case "os.rmdir": {
|
|
682
745
|
try {
|
|
683
746
|
const fs = require("fs");
|
|
684
|
-
|
|
747
|
+
nodeFs.rmdirSync(args[0]);
|
|
685
748
|
return true;
|
|
686
749
|
}
|
|
687
750
|
catch {
|
|
@@ -691,7 +754,7 @@ function makePrelude(env) {
|
|
|
691
754
|
case "os.remove": {
|
|
692
755
|
try {
|
|
693
756
|
const fs = require("fs");
|
|
694
|
-
|
|
757
|
+
nodeFs.unlinkSync(args[0]);
|
|
695
758
|
return true;
|
|
696
759
|
}
|
|
697
760
|
catch {
|
|
@@ -701,7 +764,7 @@ function makePrelude(env) {
|
|
|
701
764
|
case "os.rename": {
|
|
702
765
|
try {
|
|
703
766
|
const fs = require("fs");
|
|
704
|
-
|
|
767
|
+
nodeFs.renameSync(args[0], args[1]);
|
|
705
768
|
return true;
|
|
706
769
|
}
|
|
707
770
|
catch {
|
|
@@ -711,7 +774,7 @@ function makePrelude(env) {
|
|
|
711
774
|
case "os.copy": {
|
|
712
775
|
try {
|
|
713
776
|
const fs = require("fs");
|
|
714
|
-
|
|
777
|
+
nodeFs.copyFileSync(args[0], args[1]);
|
|
715
778
|
return true;
|
|
716
779
|
}
|
|
717
780
|
catch {
|
|
@@ -721,7 +784,7 @@ function makePrelude(env) {
|
|
|
721
784
|
case "os.file_size": {
|
|
722
785
|
try {
|
|
723
786
|
const fs = require("fs");
|
|
724
|
-
return
|
|
787
|
+
return nodeFs.statSync(args[0]).size;
|
|
725
788
|
}
|
|
726
789
|
catch {
|
|
727
790
|
return null;
|
|
@@ -736,7 +799,7 @@ function makePrelude(env) {
|
|
|
736
799
|
throw new Error(`Potentially unsafe command (injection risk): ${cmd}`);
|
|
737
800
|
}
|
|
738
801
|
const cp = require("child_process");
|
|
739
|
-
return
|
|
802
|
+
return execSync(cmd, { encoding: "utf-8", timeout: 10000 }).trim();
|
|
740
803
|
}
|
|
741
804
|
catch (e) {
|
|
742
805
|
if (e.message?.includes("injection risk"))
|
|
@@ -747,13 +810,38 @@ function makePrelude(env) {
|
|
|
747
810
|
default: return null;
|
|
748
811
|
}
|
|
749
812
|
},
|
|
813
|
+
// --- file I/O (used by stdlib/io.arc) ---
|
|
814
|
+
read: (path) => {
|
|
815
|
+
try {
|
|
816
|
+
return nodeFs.readFileSync(path, "utf-8");
|
|
817
|
+
}
|
|
818
|
+
catch {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
write: (path, content) => {
|
|
823
|
+
try {
|
|
824
|
+
nodeFs.writeFileSync(path, content, "utf-8");
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
catch {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
},
|
|
750
831
|
};
|
|
751
832
|
function callFn(fn, args) {
|
|
752
833
|
if (fn && typeof fn === "object" && "__fn" in fn) {
|
|
753
834
|
const f = fn;
|
|
754
835
|
const fnEnv = new Env(f.closure);
|
|
755
836
|
bindParams(f, args, fnEnv, evalExpr);
|
|
756
|
-
|
|
837
|
+
try {
|
|
838
|
+
return evalExpr(f.body, fnEnv);
|
|
839
|
+
}
|
|
840
|
+
catch (e) {
|
|
841
|
+
if (e instanceof ReturnSignal)
|
|
842
|
+
return e.value;
|
|
843
|
+
throw e;
|
|
844
|
+
}
|
|
757
845
|
}
|
|
758
846
|
// It might be a native function stored as a special wrapper
|
|
759
847
|
if (typeof fn === "function")
|
|
@@ -1127,18 +1215,17 @@ function evalExpr(expr, env) {
|
|
|
1127
1215
|
const method = expr.method.toUpperCase();
|
|
1128
1216
|
const arg = evalExpr(expr.arg, env);
|
|
1129
1217
|
const url = toStr(arg);
|
|
1130
|
-
//
|
|
1218
|
+
// Real HTTP tool calls via synchronous fetch
|
|
1131
1219
|
if (["GET", "POST", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
1132
|
-
|
|
1220
|
+
let bodyArg = null;
|
|
1133
1221
|
if (expr.body) {
|
|
1134
|
-
|
|
1135
|
-
return { __map: true, entries: new Map([["status", 200], ["method", method], ["url", url], ["body", body]]) };
|
|
1222
|
+
bodyArg = evalExpr(expr.body, env);
|
|
1136
1223
|
}
|
|
1137
|
-
return
|
|
1224
|
+
return syncFetch(method, url, bodyArg);
|
|
1138
1225
|
}
|
|
1139
1226
|
// Custom tool call
|
|
1140
|
-
console.log(`[
|
|
1141
|
-
return `
|
|
1227
|
+
console.log(`[tool @${expr.method}(${url})]`);
|
|
1228
|
+
return `result-from-${expr.method}`;
|
|
1142
1229
|
}
|
|
1143
1230
|
case "AsyncExpr": {
|
|
1144
1231
|
const capturedEnv = env;
|
package/dist/lexer.js
CHANGED
|
@@ -138,8 +138,9 @@ export function lex(source) {
|
|
|
138
138
|
let hasInterp = false;
|
|
139
139
|
while (i < source.length && peek() !== '"') {
|
|
140
140
|
if (peek() === "\n") {
|
|
141
|
-
//
|
|
142
|
-
|
|
141
|
+
// Allow multiline strings
|
|
142
|
+
str += advance();
|
|
143
|
+
continue;
|
|
143
144
|
}
|
|
144
145
|
if (peek() === "{") {
|
|
145
146
|
hasInterp = true;
|
package/dist/modules.js
CHANGED
|
@@ -109,11 +109,18 @@ export function handleUse(stmt, env, currentFile) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
else {
|
|
112
|
-
// No selective imports: bind all exports
|
|
112
|
+
// No selective imports: bind all exports flat
|
|
113
113
|
for (const [name, value] of Object.entries(exports)) {
|
|
114
114
|
env.set(name, value);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
// Also create a namespace object so `module.fn()` style access works
|
|
118
|
+
const nsName = stmt.path[stmt.path.length - 1];
|
|
119
|
+
const entries = new Map();
|
|
120
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
121
|
+
entries.set(name, value);
|
|
122
|
+
}
|
|
123
|
+
env.set(nsName, { __map: true, entries });
|
|
117
124
|
}
|
|
118
125
|
/**
|
|
119
126
|
* Create a UseHandler bound to a specific file path.
|
package/dist/parser.js
CHANGED
|
@@ -518,7 +518,7 @@ export class Parser {
|
|
|
518
518
|
case TokenType.Slash:
|
|
519
519
|
case TokenType.Percent: return 6;
|
|
520
520
|
case TokenType.Power: return 7;
|
|
521
|
-
case TokenType.Range: return
|
|
521
|
+
case TokenType.Range: return 4;
|
|
522
522
|
case TokenType.Dot:
|
|
523
523
|
case TokenType.LBracket:
|
|
524
524
|
case TokenType.LParen:
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED