Skip to content

Commit d15d7bc

Browse files
authored
add url and urlsearchparams classes with c bridge (#107)
1 parent 0d69479 commit d15d7bc

18 files changed

Lines changed: 659 additions & 11 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
- name: Verify vendor libraries
6666
run: |
6767
fail=0
68-
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
68+
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
6969
if [ ! -f "$lib" ]; then
7070
echo "MISSING: $lib"
7171
fail=1
@@ -132,7 +132,7 @@ jobs:
132132
cp c_bridges/child-process-bridge.o release/lib/
133133
cp c_bridges/child-process-spawn.o release/lib/
134134
cp c_bridges/os-bridge.o release/lib/
135-
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o release/lib/
135+
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o release/lib/
136136
cp c_bridges/dotenv-bridge.o release/lib/
137137
cp c_bridges/watch-bridge.o release/lib/
138138
tar -czf chadscript-linux-x64.tar.gz -C release chad lib
@@ -186,7 +186,7 @@ jobs:
186186
- name: Verify vendor libraries
187187
run: |
188188
fail=0
189-
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
189+
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
190190
if [ ! -f "$lib" ]; then
191191
echo "MISSING: $lib"
192192
fail=1
@@ -257,7 +257,7 @@ jobs:
257257
cp c_bridges/child-process-bridge.o release/lib/
258258
cp c_bridges/child-process-spawn.o release/lib/
259259
cp c_bridges/os-bridge.o release/lib/
260-
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o release/lib/
260+
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o release/lib/
261261
cp c_bridges/dotenv-bridge.o release/lib/
262262
cp c_bridges/watch-bridge.o release/lib/
263263
tar -czf chadscript-macos-arm64.tar.gz -C release chad lib

c_bridges/url-bridge.c

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
#include <string.h>
2+
#include <stdlib.h>
3+
#include <stdio.h>
4+
5+
extern void* GC_malloc_atomic(size_t sz);
6+
extern void* GC_malloc(size_t sz);
7+
8+
static char* cs_strdup_gc(const char* s) {
9+
if (!s) return NULL;
10+
size_t len = strlen(s);
11+
char* out = (char*)GC_malloc_atomic(len + 1);
12+
memcpy(out, s, len + 1);
13+
return out;
14+
}
15+
16+
static char* cs_strndup_gc(const char* s, size_t n) {
17+
char* out = (char*)GC_malloc_atomic(n + 1);
18+
memcpy(out, s, n);
19+
out[n] = '\0';
20+
return out;
21+
}
22+
23+
const char* cs_url_parse_protocol(const char* href) {
24+
if (!href) return cs_strdup_gc("");
25+
const char* colon = strstr(href, "://");
26+
if (!colon) return cs_strdup_gc("");
27+
size_t len = colon - href + 1;
28+
char* out = (char*)GC_malloc_atomic(len + 1);
29+
memcpy(out, href, len);
30+
out[len] = '\0';
31+
return out;
32+
}
33+
34+
const char* cs_url_parse_hostname(const char* href) {
35+
if (!href) return cs_strdup_gc("");
36+
const char* after = strstr(href, "://");
37+
if (!after) return cs_strdup_gc("");
38+
after += 3;
39+
const char* end = after;
40+
while (*end && *end != '/' && *end != '?' && *end != '#' && *end != ':') end++;
41+
return cs_strndup_gc(after, end - after);
42+
}
43+
44+
const char* cs_url_parse_port(const char* href) {
45+
if (!href) return cs_strdup_gc("");
46+
const char* after = strstr(href, "://");
47+
if (!after) return cs_strdup_gc("");
48+
after += 3;
49+
const char* colon = NULL;
50+
const char* p = after;
51+
while (*p && *p != '/' && *p != '?' && *p != '#') {
52+
if (*p == ':') { colon = p; break; }
53+
p++;
54+
}
55+
if (!colon) return cs_strdup_gc("");
56+
colon++;
57+
const char* end = colon;
58+
while (*end && *end != '/' && *end != '?' && *end != '#') end++;
59+
return cs_strndup_gc(colon, end - colon);
60+
}
61+
62+
const char* cs_url_parse_host(const char* href) {
63+
if (!href) return cs_strdup_gc("");
64+
const char* after = strstr(href, "://");
65+
if (!after) return cs_strdup_gc("");
66+
after += 3;
67+
const char* end = after;
68+
while (*end && *end != '/' && *end != '?' && *end != '#') end++;
69+
return cs_strndup_gc(after, end - after);
70+
}
71+
72+
const char* cs_url_parse_pathname(const char* href) {
73+
if (!href) return cs_strdup_gc("/");
74+
const char* after = strstr(href, "://");
75+
if (!after) return cs_strdup_gc("/");
76+
after += 3;
77+
while (*after && *after != '/' && *after != '?' && *after != '#') after++;
78+
if (!*after || *after == '?' || *after == '#') return cs_strdup_gc("/");
79+
const char* end = after;
80+
while (*end && *end != '?' && *end != '#') end++;
81+
return cs_strndup_gc(after, end - after);
82+
}
83+
84+
const char* cs_url_parse_search(const char* href) {
85+
if (!href) return cs_strdup_gc("");
86+
const char* q = strchr(href, '?');
87+
if (!q) return cs_strdup_gc("");
88+
const char* end = q;
89+
while (*end && *end != '#') end++;
90+
return cs_strndup_gc(q, end - q);
91+
}
92+
93+
const char* cs_url_parse_hash(const char* href) {
94+
if (!href) return cs_strdup_gc("");
95+
const char* h = strchr(href, '#');
96+
if (!h) return cs_strdup_gc("");
97+
return cs_strdup_gc(h);
98+
}
99+
100+
const char* cs_url_parse_origin(const char* href) {
101+
if (!href) return cs_strdup_gc("");
102+
const char* after = strstr(href, "://");
103+
if (!after) return cs_strdup_gc("");
104+
after += 3;
105+
const char* end = after;
106+
while (*end && *end != '/' && *end != '?' && *end != '#') end++;
107+
size_t proto_len = (after - 3) - href;
108+
size_t host_len = end - after;
109+
size_t total = proto_len + 3 + host_len;
110+
char* out = (char*)GC_malloc_atomic(total + 1);
111+
memcpy(out, href, proto_len + 3 + host_len);
112+
out[total] = '\0';
113+
return out;
114+
}
115+
116+
static const char* skip_qmark(const char* q) {
117+
if (q && *q == '?') return q + 1;
118+
return q ? q : "";
119+
}
120+
121+
const char* cs_urlsearch_get(const char* query, const char* key) {
122+
if (!query || !key) return NULL;
123+
const char* q = skip_qmark(query);
124+
size_t klen = strlen(key);
125+
const char* p = q;
126+
while (*p) {
127+
const char* eq = strchr(p, '=');
128+
if (!eq) break;
129+
size_t nlen = eq - p;
130+
if (nlen == klen && strncmp(p, key, klen) == 0) {
131+
eq++;
132+
const char* vend = strchr(eq, '&');
133+
if (!vend) vend = eq + strlen(eq);
134+
return cs_strndup_gc(eq, vend - eq);
135+
}
136+
const char* amp = strchr(eq, '&');
137+
if (!amp) break;
138+
p = amp + 1;
139+
}
140+
return NULL;
141+
}
142+
143+
int cs_urlsearch_has(const char* query, const char* key) {
144+
if (!query || !key) return 0;
145+
const char* q = skip_qmark(query);
146+
size_t klen = strlen(key);
147+
const char* p = q;
148+
while (*p) {
149+
const char* eq = strchr(p, '=');
150+
if (!eq) {
151+
if (strlen(p) == klen && strncmp(p, key, klen) == 0) return 1;
152+
break;
153+
}
154+
size_t nlen = eq - p;
155+
if (nlen == klen && strncmp(p, key, klen) == 0) return 1;
156+
const char* amp = strchr(eq, '&');
157+
if (!amp) break;
158+
p = amp + 1;
159+
}
160+
return 0;
161+
}
162+
163+
const char* cs_urlsearch_set(const char* query, const char* key, const char* value) {
164+
if (!key || !value) return query ? cs_strdup_gc(query) : cs_strdup_gc("");
165+
const char* q = skip_qmark(query ? query : "");
166+
size_t klen = strlen(key);
167+
size_t buf_size = strlen(q) + strlen(key) + strlen(value) + 64;
168+
char* out = (char*)GC_malloc_atomic(buf_size);
169+
out[0] = '\0';
170+
int found = 0;
171+
const char* p = q;
172+
int first = 1;
173+
while (*p) {
174+
const char* eq = strchr(p, '=');
175+
if (!eq) break;
176+
size_t nlen = eq - p;
177+
const char* amp = strchr(eq, '&');
178+
const char* seg_end = amp ? amp : eq + strlen(eq);
179+
if (!first) strcat(out, "&");
180+
first = 0;
181+
if (nlen == klen && strncmp(p, key, klen) == 0) {
182+
strncat(out, key, klen);
183+
strcat(out, "=");
184+
strcat(out, value);
185+
found = 1;
186+
} else {
187+
strncat(out, p, seg_end - p);
188+
}
189+
if (!amp) break;
190+
p = amp + 1;
191+
}
192+
if (!found) {
193+
if (!first) strcat(out, "&");
194+
strcat(out, key);
195+
strcat(out, "=");
196+
strcat(out, value);
197+
}
198+
return out;
199+
}
200+
201+
const char* cs_urlsearch_append(const char* query, const char* key, const char* value) {
202+
if (!key || !value) return query ? cs_strdup_gc(query) : cs_strdup_gc("");
203+
const char* q = skip_qmark(query ? query : "");
204+
size_t qlen = strlen(q);
205+
size_t klen = strlen(key);
206+
size_t vlen = strlen(value);
207+
size_t total = qlen + klen + vlen + 4;
208+
char* out = (char*)GC_malloc_atomic(total);
209+
if (qlen > 0) {
210+
memcpy(out, q, qlen);
211+
out[qlen] = '&';
212+
memcpy(out + qlen + 1, key, klen);
213+
out[qlen + 1 + klen] = '=';
214+
memcpy(out + qlen + 1 + klen + 1, value, vlen);
215+
out[qlen + 1 + klen + 1 + vlen] = '\0';
216+
} else {
217+
memcpy(out, key, klen);
218+
out[klen] = '=';
219+
memcpy(out + klen + 1, value, vlen);
220+
out[klen + 1 + vlen] = '\0';
221+
}
222+
return out;
223+
}
224+
225+
const char* cs_urlsearch_delete(const char* query, const char* key) {
226+
if (!query || !key) return cs_strdup_gc("");
227+
const char* q = skip_qmark(query);
228+
size_t klen = strlen(key);
229+
size_t buf_size = strlen(q) + 2;
230+
char* out = (char*)GC_malloc_atomic(buf_size);
231+
out[0] = '\0';
232+
const char* p = q;
233+
int first = 1;
234+
while (*p) {
235+
const char* eq = strchr(p, '=');
236+
if (!eq) break;
237+
size_t nlen = eq - p;
238+
const char* amp = strchr(eq, '&');
239+
const char* seg_end = amp ? amp : eq + strlen(eq);
240+
if (!(nlen == klen && strncmp(p, key, klen) == 0)) {
241+
if (!first) strcat(out, "&");
242+
first = 0;
243+
strncat(out, p, seg_end - p);
244+
}
245+
if (!amp) break;
246+
p = amp + 1;
247+
}
248+
return out;
249+
}
250+
251+
const char* cs_urlsearch_tostring(const char* query) {
252+
if (!query) return cs_strdup_gc("");
253+
return cs_strdup_gc(skip_qmark(query));
254+
}

scripts/build-target-sdk.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ fi
6969

7070
# Copy C bridge object files
7171
echo " Copying bridge objects..."
72-
for bridge in child-process-bridge.o os-bridge.o time-bridge.o base64-bridge.o regex-bridge.o dotenv-bridge.o watch-bridge.o lws-bridge.o multipart-bridge.o child-process-spawn.o; do
72+
for bridge in child-process-bridge.o os-bridge.o time-bridge.o base64-bridge.o url-bridge.o regex-bridge.o dotenv-bridge.o watch-bridge.o lws-bridge.o multipart-bridge.o child-process-spawn.o; do
7373
if [ -f "$C_BRIDGES_DIR/$bridge" ]; then
7474
cp "$C_BRIDGES_DIR/$bridge" "$SDK_DIR/bridges/"
7575
fi

scripts/build-vendor.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,17 @@ else
221221
echo "==> base64-bridge already built, skipping"
222222
fi
223223

224+
# --- url-bridge ---
225+
URL_BRIDGE_SRC="$C_BRIDGES_DIR/url-bridge.c"
226+
URL_BRIDGE_OBJ="$C_BRIDGES_DIR/url-bridge.o"
227+
if [ ! -f "$URL_BRIDGE_OBJ" ] || [ "$URL_BRIDGE_SRC" -nt "$URL_BRIDGE_OBJ" ]; then
228+
echo "==> Building url-bridge..."
229+
cc -c -O2 -fPIC "$URL_BRIDGE_SRC" -o "$URL_BRIDGE_OBJ"
230+
echo " -> $URL_BRIDGE_OBJ"
231+
else
232+
echo "==> url-bridge already built, skipping"
233+
fi
234+
224235
# --- child-process-spawn (async, requires libuv) ---
225236
CP_SPAWN_SRC="$C_BRIDGES_DIR/child-process-spawn.c"
226237
CP_SPAWN_OBJ="$C_BRIDGES_DIR/child-process-spawn.o"

src/codegen/expressions/access/member.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
getArrayLengthFromPtr,
6161
getStringLength,
6262
handleSpawnSyncResultProperty,
63+
handleUrlProperty,
6364
} from "./property-handlers.js";
6465
import {
6566
parseInlineObjectTypeForAssertion,
@@ -362,6 +363,9 @@ export class MemberAccessGenerator {
362363
result = this.handleSpawnSyncResultProperty(expr);
363364
if (result !== null) return result;
364365

366+
result = this.handleUrlProperty(expr);
367+
if (result !== null) return result;
368+
365369
return null;
366370
}
367371

@@ -2420,6 +2424,10 @@ export class MemberAccessGenerator {
24202424
return handleSpawnSyncResultProperty(this.ctx, expr);
24212425
}
24222426

2427+
private handleUrlProperty(expr: MemberAccessNode): string | null {
2428+
return handleUrlProperty(this.ctx, expr);
2429+
}
2430+
24232431
private handleParameterPropertyAccess(expr: MemberAccessNode, params: string[]): string {
24242432
const prop = expr.property;
24252433
if (!prop) {

src/codegen/expressions/access/property-handlers.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,47 @@
11
import { Expression, MemberAccessNode, VariableNode } from "../../../ast/types.js";
22
import type { MemberAccessGeneratorContext } from "./member.js";
33

4+
export function handleUrlProperty(
5+
ctx: MemberAccessGeneratorContext,
6+
expr: MemberAccessNode,
7+
): string | null {
8+
const exprObjBase = expr.object as ExprBase;
9+
if (exprObjBase.type !== "variable") return null;
10+
const varName = (expr.object as VariableNode).name;
11+
if (!ctx.symbolTable.isUrl(varName)) return null;
12+
const prop = expr.property;
13+
const varAlloca = ctx.symbolTable.getAlloca(varName);
14+
if (!varAlloca) return null;
15+
const urlPtr = ctx.emitLoad("i8*", varAlloca);
16+
if (prop === "href") {
17+
ctx.setVariableType(urlPtr, "i8*");
18+
return urlPtr;
19+
}
20+
let urlFn = "";
21+
if (prop === "protocol") {
22+
urlFn = "@cs_url_parse_protocol";
23+
} else if (prop === "hostname") {
24+
urlFn = "@cs_url_parse_hostname";
25+
} else if (prop === "port") {
26+
urlFn = "@cs_url_parse_port";
27+
} else if (prop === "host") {
28+
urlFn = "@cs_url_parse_host";
29+
} else if (prop === "pathname") {
30+
urlFn = "@cs_url_parse_pathname";
31+
} else if (prop === "search" || prop === "searchParams") {
32+
urlFn = "@cs_url_parse_search";
33+
} else if (prop === "hash") {
34+
urlFn = "@cs_url_parse_hash";
35+
} else if (prop === "origin") {
36+
urlFn = "@cs_url_parse_origin";
37+
} else {
38+
return null;
39+
}
40+
const result = ctx.emitCall("i8*", urlFn, `i8* ${urlPtr}`);
41+
ctx.setVariableType(result, "i8*");
42+
return result;
43+
}
44+
445
interface ExprBase {
546
type: string;
647
}

0 commit comments

Comments
 (0)