Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Verify vendor libraries
run: |
fail=0
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
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
if [ ! -f "$lib" ]; then
echo "MISSING: $lib"
fail=1
Expand Down Expand Up @@ -132,7 +132,7 @@ jobs:
cp c_bridges/child-process-bridge.o release/lib/
cp c_bridges/child-process-spawn.o release/lib/
cp c_bridges/os-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o release/lib/
cp c_bridges/dotenv-bridge.o release/lib/
cp c_bridges/watch-bridge.o release/lib/
tar -czf chadscript-linux-x64.tar.gz -C release chad lib
Expand Down Expand Up @@ -186,7 +186,7 @@ jobs:
- name: Verify vendor libraries
run: |
fail=0
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
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
if [ ! -f "$lib" ]; then
echo "MISSING: $lib"
fail=1
Expand Down Expand Up @@ -257,7 +257,7 @@ jobs:
cp c_bridges/child-process-bridge.o release/lib/
cp c_bridges/child-process-spawn.o release/lib/
cp c_bridges/os-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o release/lib/
cp c_bridges/dotenv-bridge.o release/lib/
cp c_bridges/watch-bridge.o release/lib/
tar -czf chadscript-macos-arm64.tar.gz -C release chad lib
Expand Down
254 changes: 254 additions & 0 deletions c_bridges/url-bridge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

extern void* GC_malloc_atomic(size_t sz);
extern void* GC_malloc(size_t sz);

static char* cs_strdup_gc(const char* s) {
if (!s) return NULL;
size_t len = strlen(s);
char* out = (char*)GC_malloc_atomic(len + 1);
memcpy(out, s, len + 1);
return out;
}

static char* cs_strndup_gc(const char* s, size_t n) {
char* out = (char*)GC_malloc_atomic(n + 1);
memcpy(out, s, n);
out[n] = '\0';
return out;
}

const char* cs_url_parse_protocol(const char* href) {
if (!href) return cs_strdup_gc("");
const char* colon = strstr(href, "://");
if (!colon) return cs_strdup_gc("");
size_t len = colon - href + 1;
char* out = (char*)GC_malloc_atomic(len + 1);
memcpy(out, href, len);
out[len] = '\0';
return out;
}

const char* cs_url_parse_hostname(const char* href) {
if (!href) return cs_strdup_gc("");
const char* after = strstr(href, "://");
if (!after) return cs_strdup_gc("");
after += 3;
const char* end = after;
while (*end && *end != '/' && *end != '?' && *end != '#' && *end != ':') end++;
return cs_strndup_gc(after, end - after);
}

const char* cs_url_parse_port(const char* href) {
if (!href) return cs_strdup_gc("");
const char* after = strstr(href, "://");
if (!after) return cs_strdup_gc("");
after += 3;
const char* colon = NULL;
const char* p = after;
while (*p && *p != '/' && *p != '?' && *p != '#') {
if (*p == ':') { colon = p; break; }
p++;
}
if (!colon) return cs_strdup_gc("");
colon++;
const char* end = colon;
while (*end && *end != '/' && *end != '?' && *end != '#') end++;
return cs_strndup_gc(colon, end - colon);
}

const char* cs_url_parse_host(const char* href) {
if (!href) return cs_strdup_gc("");
const char* after = strstr(href, "://");
if (!after) return cs_strdup_gc("");
after += 3;
const char* end = after;
while (*end && *end != '/' && *end != '?' && *end != '#') end++;
return cs_strndup_gc(after, end - after);
}

const char* cs_url_parse_pathname(const char* href) {
if (!href) return cs_strdup_gc("/");
const char* after = strstr(href, "://");
if (!after) return cs_strdup_gc("/");
after += 3;
while (*after && *after != '/' && *after != '?' && *after != '#') after++;
if (!*after || *after == '?' || *after == '#') return cs_strdup_gc("/");
const char* end = after;
while (*end && *end != '?' && *end != '#') end++;
return cs_strndup_gc(after, end - after);
}

const char* cs_url_parse_search(const char* href) {
if (!href) return cs_strdup_gc("");
const char* q = strchr(href, '?');
if (!q) return cs_strdup_gc("");
const char* end = q;
while (*end && *end != '#') end++;
return cs_strndup_gc(q, end - q);
}

const char* cs_url_parse_hash(const char* href) {
if (!href) return cs_strdup_gc("");
const char* h = strchr(href, '#');
if (!h) return cs_strdup_gc("");
return cs_strdup_gc(h);
}

const char* cs_url_parse_origin(const char* href) {
if (!href) return cs_strdup_gc("");
const char* after = strstr(href, "://");
if (!after) return cs_strdup_gc("");
after += 3;
const char* end = after;
while (*end && *end != '/' && *end != '?' && *end != '#') end++;
size_t proto_len = (after - 3) - href;
size_t host_len = end - after;
size_t total = proto_len + 3 + host_len;
char* out = (char*)GC_malloc_atomic(total + 1);
memcpy(out, href, proto_len + 3 + host_len);
out[total] = '\0';
return out;
}

static const char* skip_qmark(const char* q) {
if (q && *q == '?') return q + 1;
return q ? q : "";
}

const char* cs_urlsearch_get(const char* query, const char* key) {
if (!query || !key) return NULL;
const char* q = skip_qmark(query);
size_t klen = strlen(key);
const char* p = q;
while (*p) {
const char* eq = strchr(p, '=');
if (!eq) break;
size_t nlen = eq - p;
if (nlen == klen && strncmp(p, key, klen) == 0) {
eq++;
const char* vend = strchr(eq, '&');
if (!vend) vend = eq + strlen(eq);
return cs_strndup_gc(eq, vend - eq);
}
const char* amp = strchr(eq, '&');
if (!amp) break;
p = amp + 1;
}
return NULL;
}

int cs_urlsearch_has(const char* query, const char* key) {
if (!query || !key) return 0;
const char* q = skip_qmark(query);
size_t klen = strlen(key);
const char* p = q;
while (*p) {
const char* eq = strchr(p, '=');
if (!eq) {
if (strlen(p) == klen && strncmp(p, key, klen) == 0) return 1;
break;
}
size_t nlen = eq - p;
if (nlen == klen && strncmp(p, key, klen) == 0) return 1;
const char* amp = strchr(eq, '&');
if (!amp) break;
p = amp + 1;
}
return 0;
}

const char* cs_urlsearch_set(const char* query, const char* key, const char* value) {
if (!key || !value) return query ? cs_strdup_gc(query) : cs_strdup_gc("");
const char* q = skip_qmark(query ? query : "");
size_t klen = strlen(key);
size_t buf_size = strlen(q) + strlen(key) + strlen(value) + 64;
char* out = (char*)GC_malloc_atomic(buf_size);
out[0] = '\0';
int found = 0;
const char* p = q;
int first = 1;
while (*p) {
const char* eq = strchr(p, '=');
if (!eq) break;
size_t nlen = eq - p;
const char* amp = strchr(eq, '&');
const char* seg_end = amp ? amp : eq + strlen(eq);
if (!first) strcat(out, "&");
first = 0;
if (nlen == klen && strncmp(p, key, klen) == 0) {
strncat(out, key, klen);
strcat(out, "=");
strcat(out, value);
found = 1;
} else {
strncat(out, p, seg_end - p);
}
if (!amp) break;
p = amp + 1;
}
if (!found) {
if (!first) strcat(out, "&");
strcat(out, key);
strcat(out, "=");
strcat(out, value);
}
return out;
}

const char* cs_urlsearch_append(const char* query, const char* key, const char* value) {
if (!key || !value) return query ? cs_strdup_gc(query) : cs_strdup_gc("");
const char* q = skip_qmark(query ? query : "");
size_t qlen = strlen(q);
size_t klen = strlen(key);
size_t vlen = strlen(value);
size_t total = qlen + klen + vlen + 4;
char* out = (char*)GC_malloc_atomic(total);
if (qlen > 0) {
memcpy(out, q, qlen);
out[qlen] = '&';
memcpy(out + qlen + 1, key, klen);
out[qlen + 1 + klen] = '=';
memcpy(out + qlen + 1 + klen + 1, value, vlen);
out[qlen + 1 + klen + 1 + vlen] = '\0';
} else {
memcpy(out, key, klen);
out[klen] = '=';
memcpy(out + klen + 1, value, vlen);
out[klen + 1 + vlen] = '\0';
}
return out;
}

const char* cs_urlsearch_delete(const char* query, const char* key) {
if (!query || !key) return cs_strdup_gc("");
const char* q = skip_qmark(query);
size_t klen = strlen(key);
size_t buf_size = strlen(q) + 2;
char* out = (char*)GC_malloc_atomic(buf_size);
out[0] = '\0';
const char* p = q;
int first = 1;
while (*p) {
const char* eq = strchr(p, '=');
if (!eq) break;
size_t nlen = eq - p;
const char* amp = strchr(eq, '&');
const char* seg_end = amp ? amp : eq + strlen(eq);
if (!(nlen == klen && strncmp(p, key, klen) == 0)) {
if (!first) strcat(out, "&");
first = 0;
strncat(out, p, seg_end - p);
}
if (!amp) break;
p = amp + 1;
}
return out;
}

const char* cs_urlsearch_tostring(const char* query) {
if (!query) return cs_strdup_gc("");
return cs_strdup_gc(skip_qmark(query));
}
2 changes: 1 addition & 1 deletion scripts/build-target-sdk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fi

# Copy C bridge object files
echo " Copying bridge objects..."
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
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
if [ -f "$C_BRIDGES_DIR/$bridge" ]; then
cp "$C_BRIDGES_DIR/$bridge" "$SDK_DIR/bridges/"
fi
Expand Down
11 changes: 11 additions & 0 deletions scripts/build-vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,17 @@ else
echo "==> base64-bridge already built, skipping"
fi

# --- url-bridge ---
URL_BRIDGE_SRC="$C_BRIDGES_DIR/url-bridge.c"
URL_BRIDGE_OBJ="$C_BRIDGES_DIR/url-bridge.o"
if [ ! -f "$URL_BRIDGE_OBJ" ] || [ "$URL_BRIDGE_SRC" -nt "$URL_BRIDGE_OBJ" ]; then
echo "==> Building url-bridge..."
cc -c -O2 -fPIC "$URL_BRIDGE_SRC" -o "$URL_BRIDGE_OBJ"
echo " -> $URL_BRIDGE_OBJ"
else
echo "==> url-bridge already built, skipping"
fi

# --- child-process-spawn (async, requires libuv) ---
CP_SPAWN_SRC="$C_BRIDGES_DIR/child-process-spawn.c"
CP_SPAWN_OBJ="$C_BRIDGES_DIR/child-process-spawn.o"
Expand Down
8 changes: 8 additions & 0 deletions src/codegen/expressions/access/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
getArrayLengthFromPtr,
getStringLength,
handleSpawnSyncResultProperty,
handleUrlProperty,
} from "./property-handlers.js";
import {
parseInlineObjectTypeForAssertion,
Expand Down Expand Up @@ -362,6 +363,9 @@ export class MemberAccessGenerator {
result = this.handleSpawnSyncResultProperty(expr);
if (result !== null) return result;

result = this.handleUrlProperty(expr);
if (result !== null) return result;

return null;
}

Expand Down Expand Up @@ -2420,6 +2424,10 @@ export class MemberAccessGenerator {
return handleSpawnSyncResultProperty(this.ctx, expr);
}

private handleUrlProperty(expr: MemberAccessNode): string | null {
return handleUrlProperty(this.ctx, expr);
}

private handleParameterPropertyAccess(expr: MemberAccessNode, params: string[]): string {
const prop = expr.property;
if (!prop) {
Expand Down
41 changes: 41 additions & 0 deletions src/codegen/expressions/access/property-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
import { Expression, MemberAccessNode, VariableNode } from "../../../ast/types.js";
import type { MemberAccessGeneratorContext } from "./member.js";

export function handleUrlProperty(
ctx: MemberAccessGeneratorContext,
expr: MemberAccessNode,
): string | null {
const exprObjBase = expr.object as ExprBase;
if (exprObjBase.type !== "variable") return null;
const varName = (expr.object as VariableNode).name;
if (!ctx.symbolTable.isUrl(varName)) return null;
const prop = expr.property;
const varAlloca = ctx.symbolTable.getAlloca(varName);
if (!varAlloca) return null;
const urlPtr = ctx.emitLoad("i8*", varAlloca);
if (prop === "href") {
ctx.setVariableType(urlPtr, "i8*");
return urlPtr;
}
let urlFn = "";
if (prop === "protocol") {
urlFn = "@cs_url_parse_protocol";
} else if (prop === "hostname") {
urlFn = "@cs_url_parse_hostname";
} else if (prop === "port") {
urlFn = "@cs_url_parse_port";
} else if (prop === "host") {
urlFn = "@cs_url_parse_host";
} else if (prop === "pathname") {
urlFn = "@cs_url_parse_pathname";
} else if (prop === "search" || prop === "searchParams") {
urlFn = "@cs_url_parse_search";
} else if (prop === "hash") {
urlFn = "@cs_url_parse_hash";
} else if (prop === "origin") {
urlFn = "@cs_url_parse_origin";
} else {
return null;
}
const result = ctx.emitCall("i8*", urlFn, `i8* ${urlPtr}`);
ctx.setVariableType(result, "i8*");
return result;
}

interface ExprBase {
type: string;
}
Expand Down
Loading
Loading