diff --git a/examples/spec/formdata.js b/examples/spec/formdata.js
new file mode 100644
index 0000000..0398408
--- /dev/null
+++ b/examples/spec/formdata.js
@@ -0,0 +1,120 @@
+import { test, testThrows, testDeep, summary } from './helpers.js';
+
+console.log('FormData constructor\n');
+
+const fd0 = new FormData();
+test('empty constructor', fd0.has('x'), false);
+
+testThrows('requires new', () => FormData());
+testThrows('rejects form element', () => new FormData(document));
+
+console.log('\nappend / get / has / delete / set\n');
+
+const fd = new FormData();
+
+fd.append('name', 'Alice');
+test('append then get', fd.get('name'), 'Alice');
+test('has returns true', fd.has('name'), true);
+test('has missing returns false', fd.has('other'), false);
+
+fd.append('name', 'Bob');
+const all = fd.getAll('name');
+test('getAll length after two appends', all.length, 2);
+test('getAll first', all[0], 'Alice');
+test('getAll second', all[1], 'Bob');
+
+fd.set('name', 'Carol');
+test('set replaces all', fd.getAll('name').length, 1);
+test('set value', fd.get('name'), 'Carol');
+
+fd.delete('name');
+test('delete removes all', fd.has('name'), false);
+test('get after delete returns null', fd.get('name'), null);
+
+console.log('\nBlob values\n');
+
+const blob = new Blob(['hello'], { type: 'text/plain' });
+const fdb = new FormData();
+fdb.append('file', blob);
+
+const got = fdb.get('file');
+test('blob entry is File instance', got instanceof File, true);
+test('blob entry filename default', got.name, 'blob');
+test('blob entry type', got.type, 'text/plain');
+
+fdb.append('file2', blob, 'custom.txt');
+const got2 = fdb.get('file2');
+test('blob with filename', got2.name, 'custom.txt');
+
+console.log('\nFile values\n');
+
+const file = new File(['world'], 'test.txt', { type: 'text/html' });
+const fdf = new FormData();
+fdf.append('upload', file);
+
+const gotf = fdf.get('upload');
+test('file entry is File instance', gotf instanceof File, true);
+test('file entry name from File', gotf.name, 'test.txt');
+test('file entry type', gotf.type, 'text/html');
+
+fdf.append('upload2', file, 'override.txt');
+test('file entry name overridden', fdf.get('upload2').name, 'override.txt');
+
+console.log('\ngetAll with mixed types\n');
+
+const fdm = new FormData();
+fdm.append('x', 'str');
+fdm.append('x', new Blob(['data']));
+const mixed = fdm.getAll('x');
+test('mixed getAll length', mixed.length, 2);
+test('mixed first is string', mixed[0], 'str');
+test('mixed second is File', mixed[1] instanceof File, true);
+
+console.log('\nforEach\n');
+
+const fdfe = new FormData();
+fdfe.append('b', '2');
+fdfe.append('a', '1');
+const seen = [];
+fdfe.forEach((val, name) => seen.push(`${name}=${val}`));
+test('forEach visits all entries', seen.length, 2);
+test('forEach insertion order first', seen[0], 'b=2');
+test('forEach insertion order second', seen[1], 'a=1');
+
+console.log('\nentries / keys / values\n');
+
+const fdi = new FormData();
+fdi.append('x', '1');
+fdi.append('y', '2');
+fdi.append('x', '3');
+
+const keys = [...fdi.keys()];
+test('keys length', keys.length, 3);
+test('keys first', keys[0], 'x');
+test('keys second', keys[1], 'y');
+test('keys third', keys[2], 'x');
+
+const vals = [...fdi.values()];
+test('values first', vals[0], '1');
+test('values second', vals[1], '2');
+test('values third', vals[2], '3');
+
+const entries = [...fdi.entries()];
+test('entries length', entries.length, 3);
+test('entries first name', entries[0][0], 'x');
+test('entries first value', entries[0][1], '1');
+
+console.log('\nSymbol.iterator\n');
+
+const fds = new FormData();
+fds.append('k', 'v');
+const iter_result = [...fds];
+test('Symbol.iterator yields entries', iter_result.length, 1);
+test('Symbol.iterator entry name', iter_result[0][0], 'k');
+test('Symbol.iterator entry value', iter_result[0][1], 'v');
+
+console.log('\ntoStringTag\n');
+
+test('toStringTag', Object.prototype.toString.call(new FormData()), '[object FormData]');
+
+summary();
diff --git a/include/modules/formdata.h b/include/modules/formdata.h
new file mode 100644
index 0000000..f92d4a7
--- /dev/null
+++ b/include/modules/formdata.h
@@ -0,0 +1,6 @@
+#ifndef FORMDATA_H
+#define FORMDATA_H
+
+void init_formdata_module(void);
+
+#endif
diff --git a/src/cli/misc.c b/src/cli/misc.c
index f878544..6b1d9d1 100644
--- a/src/cli/misc.c
+++ b/src/cli/misc.c
@@ -16,9 +16,9 @@ void print_flag(FILE *fp, flag_help_t f) {
s && l ? ", " : "",
l ? "--" : "",
l ? l : "",
- d && l && opt ? "[=" :
- d && l ? "=" :
- d && s ? " " : "",
+ d && l && opt ? "[=" :
+ d && l ? "=" :
+ d && s ? " " : "",
d ? d : "");
crfprintf(fp, "%s %s>\n", syn, g);
diff --git a/src/highlight/theme.toml b/src/highlight/theme.toml
index 663a593..3889c0a 100644
--- a/src/highlight/theme.toml
+++ b/src/highlight/theme.toml
@@ -1,43 +1,43 @@
-# Highlight theme — token class → crprintf color/style string
-# Styles: hex colors (#RRGGBB), with optional prefix modifiers (bold+, italic+)
+# token class → crprintf color/style string
+# hex colors (#RRGGBB), with optional prefix modifiers (bold+, italic+)
[colors]
-number = "#E8CD7C"
-number_prefix = "#EADBAD"
-boolean = "#65B2FF"
-literal_null = "#65B2FF"
+number = "#E8CD7C"
+number_prefix = "#EADBAD"
+boolean = "#65B2FF"
+literal_null = "#65B2FF"
-string = "#FF8A7F"
+string = "#FF8A7F"
string_delimiter = "#FF7265"
-string_escape = "#F4AAA3"
-string_key = "#CCA3F4"
-string_template = "#FFB265"
-
-regex = "#FFB265"
-regex_escape = "#FFCC99"
-regex_delimiter = "#FF9932"
-regex_cdata = "#65B2FF"
-
-keyword = "#65B2FF"
-keyword_delete = "#F43D3D"
-keyword_italic = "italic+#65B2FF"
-keyword_extends = "italic+#59D8F1"
-
-type = "#59D8F1"
-type_string = "#30E8AA"
-type_boolean = "#30E8AA"
-
-comment = "#758CA3"
-
-function_name = "#30E8AA"
-function = "#30E8AA"
-argument = "#CCA3F4"
-property = "#CCA3F4"
-
-operator = "#8CB2D8"
-optional_chain = "#8CB2D8"
-bracket = "#8CB2D8"
-semicolon = "#B2CCE5"
-
-class_name = "bold+#F7B76D"
-parent_class = "bold+#59D8F1"
+string_escape = "#F4AAA3"
+string_key = "#CCA3F4"
+string_template = "#FFB265"
+
+regex = "#FFB265"
+regex_escape = "#FFCC99"
+regex_delimiter = "#FF9932"
+regex_cdata = "#65B2FF"
+
+keyword = "#65B2FF"
+keyword_delete = "#F43D3D"
+keyword_italic = "italic+#65B2FF"
+keyword_extends = "italic+#59D8F1"
+
+type = "#59D8F1"
+type_string = "#30E8AA"
+type_boolean = "#30E8AA"
+
+comment = "#758CA3"
+
+function_name = "#30E8AA"
+function = "#30E8AA"
+argument = "#CCA3F4"
+property = "#CCA3F4"
+
+operator = "#8CB2D8"
+optional_chain = "#8CB2D8"
+bracket = "#8CB2D8"
+semicolon = "#B2CCE5"
+
+class_name = "bold+#F7B76D"
+parent_class = "bold+#59D8F1"
diff --git a/src/main.c b/src/main.c
index 0907e41..e645464 100644
--- a/src/main.c
+++ b/src/main.c
@@ -77,6 +77,7 @@
#include "modules/worker_threads.h"
#include "modules/headers.h"
#include "modules/blob.h"
+#include "modules/formdata.h"
int js_result = EXIT_SUCCESS;
typedef int (*cmd_fn)(int argc, char **argv);
@@ -586,6 +587,7 @@ int main(int argc, char *argv[]) {
init_abort_module();
init_headers_module();
init_blob_module();
+ init_formdata_module();
init_math_module();
init_bigint_module();
init_date_module();
diff --git a/src/modules/formdata.c b/src/modules/formdata.c
new file mode 100644
index 0000000..6ba29d5
--- /dev/null
+++ b/src/modules/formdata.c
@@ -0,0 +1,457 @@
+#include
+#include
+#include
+#include
+
+#include "ant.h"
+#include "errors.h"
+#include "runtime.h"
+#include "internal.h"
+#include "descriptors.h"
+
+#include "silver/engine.h"
+#include "modules/formdata.h"
+#include "modules/blob.h"
+#include "modules/symbol.h"
+
+typedef struct fd_entry {
+ char *name;
+ bool is_file;
+ char *str_value;
+ size_t val_idx;
+ struct fd_entry *next;
+} fd_entry_t;
+
+typedef struct {
+ fd_entry_t *head;
+ fd_entry_t **tail;
+ size_t count;
+} fd_data_t;
+
+typedef struct {
+ size_t index;
+ int kind;
+} fd_iter_t;
+
+enum {
+ FD_ITER_ENTRIES = 0,
+ FD_ITER_KEYS = 1,
+ FD_ITER_VALUES = 2
+};
+
+static ant_value_t g_formdata_proto = 0;
+static ant_value_t g_formdata_iter_proto = 0;
+
+static fd_data_t *fd_data_new(void) {
+ fd_data_t *d = calloc(1, sizeof(fd_data_t));
+ if (!d) return NULL;
+ d->tail = &d->head;
+ return d;
+}
+
+static void fd_entry_free(fd_entry_t *e) {
+ if (!e) return;
+ free(e->name);
+ free(e->str_value);
+ free(e);
+}
+
+static void fd_data_free(fd_data_t *d) {
+ if (!d) return;
+ for (fd_entry_t *e = d->head; e; ) {
+ fd_entry_t *n = e->next;
+ fd_entry_free(e);
+ e = n;
+ }
+ free(d);
+}
+
+static fd_data_t *get_fd_data(ant_value_t obj) {
+ ant_value_t slot = js_get_slot(obj, SLOT_DATA);
+ if (vtype(slot) != T_NUM) return NULL;
+ return (fd_data_t *)(uintptr_t)(size_t)js_getnum(slot);
+}
+
+static ant_value_t get_fd_values(ant_value_t obj) {
+ return js_get_slot(obj, SLOT_ENTRIES);
+}
+
+static void formdata_finalize(ant_t *js, ant_object_t *obj) {
+ if (!obj->extra_slots) return;
+ ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
+
+ for (uint8_t i = 0; i < obj->extra_count; i++) {
+ if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
+ fd_data_t *d = (fd_data_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
+ fd_data_free(d);
+ return;
+ }}
+}
+
+static bool fd_append_str(fd_data_t *d, const char *name, const char *value) {
+ fd_entry_t *e = calloc(1, sizeof(fd_entry_t));
+ if (!e) return false;
+
+ e->name = strdup(name);
+ e->str_value = strdup(value);
+
+ if (!e->name || !e->str_value) { fd_entry_free(e); return false; }
+
+ *d->tail = e;
+ d->tail = &e->next;
+ d->count++;
+ return true;
+}
+
+static bool fd_append_file(fd_data_t *d, const char *name, size_t val_idx) {
+ fd_entry_t *e = calloc(1, sizeof(fd_entry_t));
+ if (!e) return false;
+
+ e->is_file = true;
+ e->name = strdup(name);
+ e->val_idx = val_idx;
+
+ if (!e->name) { free(e); return false; }
+
+ *d->tail = e;
+ d->tail = &e->next;
+ d->count++;
+ return true;
+}
+
+static void fd_delete_name(fd_data_t *d, const char *name) {
+ fd_entry_t **pp = &d->head;
+ d->tail = &d->head;
+
+ while (*pp) {
+ if (strcmp((*pp)->name, name) == 0) {
+ fd_entry_t *dead = *pp;
+ *pp = dead->next;
+ d->count--;
+ fd_entry_free(dead);
+ } else {
+ d->tail = &(*pp)->next;
+ pp = &(*pp)->next;
+ }}
+}
+
+static ant_value_t entry_to_js_value(ant_t *js, ant_value_t values_arr, fd_entry_t *e) {
+ if (!e->is_file)
+ return js_mkstr(js, e->str_value ? e->str_value : "", strlen(e->str_value ? e->str_value : ""));
+ return js_arr_get(js, values_arr, (ant_offset_t)e->val_idx);
+}
+
+static const char *resolve_name(ant_t *js, ant_value_t *name_v) {
+ if (vtype(*name_v) != T_STR) {
+ *name_v = js_tostring_val(js, *name_v);
+ if (is_err(*name_v)) return NULL;
+ }
+ return js_getstr(js, *name_v, NULL);
+}
+
+static ant_value_t extract_file_entry(
+ ant_t *js, fd_data_t *d, ant_value_t values_arr,
+ const char *name, ant_value_t val, ant_value_t filename_v, bool is_set
+) {
+ blob_data_t *bd = blob_get_data(val);
+ if (!bd) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData value must be a string, Blob, or File");
+
+ const char *fname = NULL;
+ char *fname_owned = NULL;
+
+ if (vtype(filename_v) != T_UNDEF) {
+ ant_value_t fv = filename_v;
+ if (vtype(fv) != T_STR) { fv = js_tostring_val(js, fv); if (is_err(fv)) return fv; }
+ fname_owned = strdup(js_getstr(js, fv, NULL));
+ fname = fname_owned;
+ }
+
+ else if (bd->name && bd->name[0]) fname = bd->name;
+ else fname = "blob";
+
+ int64_t last_modified = bd->last_modified;
+ if (!last_modified) {
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000);
+ }
+
+ ant_value_t file_obj = blob_create(js, bd->data, bd->size, bd->type);
+ if (is_err(file_obj)) { free(fname_owned); return file_obj; }
+
+ blob_data_t *nbd = blob_get_data(file_obj);
+ if (nbd) {
+ nbd->name = strdup(fname);
+ nbd->last_modified = last_modified;
+ } free(fname_owned);
+
+ js_set_proto_init(file_obj, g_file_proto);
+ if (is_set) fd_delete_name(d, name);
+
+ size_t idx = (size_t)js_arr_len(js, values_arr);
+ js_arr_push(js, values_arr, file_obj);
+
+ return fd_append_file(d, name, idx) ? js_mkundef() : js_mkerr(js, "out of memory");
+}
+
+static ant_value_t js_formdata_append(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.append requires 2 arguments");
+ fd_data_t *d = get_fd_data(js->this_val);
+ if (!d) return js_mkerr(js, "Invalid FormData object");
+
+ ant_value_t name_v = args[0];
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+
+ ant_value_t val = args[1];
+ if (is_object_type(val) && blob_get_data(val)) {
+ ant_value_t fname = (nargs >= 3) ? args[2] : js_mkundef();
+ ant_value_t values_arr = get_fd_values(js->this_val);
+ return extract_file_entry(js, d, values_arr, name, val, fname, false);
+ }
+
+ if (vtype(val) != T_STR) { val = js_tostring_val(js, val); if (is_err(val)) return val; }
+ return fd_append_str(d, name, js_getstr(js, val, NULL)) ? js_mkundef() : js_mkerr(js, "out of memory");
+}
+
+static ant_value_t js_formdata_set(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.set requires 2 arguments");
+ fd_data_t *d = get_fd_data(js->this_val);
+ if (!d) return js_mkerr(js, "Invalid FormData object");
+
+ ant_value_t name_v = args[0];
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+
+ ant_value_t val = args[1];
+ if (is_object_type(val) && blob_get_data(val)) {
+ ant_value_t fname = (nargs >= 3) ? args[2] : js_mkundef();
+ ant_value_t values_arr = get_fd_values(js->this_val);
+ return extract_file_entry(js, d, values_arr, name, val, fname, true);
+ }
+
+ if (vtype(val) != T_STR) { val = js_tostring_val(js, val); if (is_err(val)) return val; }
+ fd_delete_name(d, name);
+ return fd_append_str(d, name, js_getstr(js, val, NULL)) ? js_mkundef() : js_mkerr(js, "out of memory");
+}
+
+static ant_value_t js_formdata_get(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 1) return js_mknull();
+ fd_data_t *d = get_fd_data(js->this_val);
+ if (!d) return js_mknull();
+
+ ant_value_t name_v = args[0];
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+
+ ant_value_t values_arr = get_fd_values(js->this_val);
+ for (fd_entry_t *e = d->head; e; e = e->next) {
+ if (strcmp(e->name, name) == 0)
+ return entry_to_js_value(js, values_arr, e);
+ }
+ return js_mknull();
+}
+
+static ant_value_t js_formdata_get_all(ant_t *js, ant_value_t *args, int nargs) {
+ ant_value_t result = js_mkarr(js);
+ if (nargs < 1) return result;
+ fd_data_t *d = get_fd_data(js->this_val);
+ if (!d) return result;
+
+ ant_value_t name_v = args[0];
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+
+ ant_value_t values_arr = get_fd_values(js->this_val);
+ for (fd_entry_t *e = d->head; e; e = e->next) {
+ if (strcmp(e->name, name) == 0) {
+ ant_value_t v = entry_to_js_value(js, values_arr, e);
+ if (is_err(v)) return v;
+ js_arr_push(js, result, v);
+ }}
+ return result;
+}
+
+static ant_value_t js_formdata_has(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 1) return js_false;
+ fd_data_t *d = get_fd_data(js->this_val);
+ if (!d) return js_false;
+
+ ant_value_t name_v = args[0];
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+
+ for (fd_entry_t *e = d->head; e; e = e->next) {
+ if (strcmp(e->name, name) == 0) return js_true;
+ }
+ return js_false;
+}
+
+static ant_value_t js_formdata_delete(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 1) return js_mkundef();
+ fd_data_t *d = get_fd_data(js->this_val);
+ if (!d) return js_mkundef();
+
+ ant_value_t name_v = args[0];
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+
+ fd_delete_name(d, name);
+ return js_mkundef();
+}
+
+static ant_value_t js_formdata_foreach(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 1 || !is_callable(args[0]))
+ return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.forEach requires a function");
+ fd_data_t *d = get_fd_data(js->this_val);
+ if (!d) return js_mkundef();
+
+ ant_value_t fn = args[0];
+ ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
+ ant_value_t self = js->this_val;
+ ant_value_t values_arr = get_fd_values(self);
+
+ for (fd_entry_t *e = d->head; e; e = e->next) {
+ ant_value_t val = entry_to_js_value(js, values_arr, e);
+ if (is_err(val)) return val;
+ ant_value_t name = js_mkstr(js, e->name, strlen(e->name));
+ ant_value_t cb_args[3] = { val, name, self };
+ ant_value_t r = sv_vm_call(js->vm, js, fn, this_arg, cb_args, 3, NULL, false);
+ if (is_err(r)) return r;
+ }
+ return js_mkundef();
+}
+
+static ant_value_t formdata_iter_next(ant_t *js, ant_value_t *args, int nargs) {
+ ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE);
+ if (vtype(state_v) != T_NUM) return js_iter_result(js, false, js_mkundef());
+
+ fd_iter_t *st = (fd_iter_t *)(uintptr_t)(size_t)js_getnum(state_v);
+ ant_value_t fd_obj = js_get_slot(js->this_val, SLOT_DATA);
+ fd_data_t *d = get_fd_data(fd_obj);
+ if (!d) return js_iter_result(js, false, js_mkundef());
+
+ ant_value_t values_arr = get_fd_values(fd_obj);
+
+ size_t idx = 0;
+ for (fd_entry_t *e = d->head; e; e = e->next, idx++) {
+ if (idx == st->index) {
+ st->index++;
+ ant_value_t out;
+ switch (st->kind) {
+ case FD_ITER_KEYS:
+ out = js_mkstr(js, e->name, strlen(e->name));
+ break;
+ case FD_ITER_VALUES:
+ out = entry_to_js_value(js, values_arr, e);
+ break;
+ default: {
+ ant_value_t v = entry_to_js_value(js, values_arr, e);
+ if (is_err(v)) return v;
+ out = js_mkarr(js);
+ js_arr_push(js, out, js_mkstr(js, e->name, strlen(e->name)));
+ js_arr_push(js, out, v);
+ break;
+ }}
+ if (is_err(out)) return out;
+ return js_iter_result(js, true, out);
+ }}
+
+ return js_iter_result(js, false, js_mkundef());
+}
+
+static void formdata_iter_finalize(ant_t *js, ant_object_t *obj) {
+ if (!obj->extra_slots) return;
+ ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
+
+ for (uint8_t i = 0; i < obj->extra_count; i++) {
+ if (entries[i].slot == SLOT_ITER_STATE && vtype(entries[i].value) == T_NUM) {
+ fd_iter_t *st = (fd_iter_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
+ free(st);
+ return;
+ }}
+}
+
+static ant_value_t make_formdata_iter(ant_t *js, ant_value_t fd_obj, int kind) {
+ fd_iter_t *st = calloc(1, sizeof(fd_iter_t));
+ if (!st) return js_mkerr(js, "out of memory");
+ st->kind = kind;
+
+ ant_value_t iter = js_mkobj(js);
+ js_set_proto_init(iter, g_formdata_iter_proto);
+ js_set_slot(iter, SLOT_ITER_STATE, ANT_PTR(st));
+ js_set_slot_wb(js, iter, SLOT_DATA, fd_obj);
+ js_set_finalizer(iter, formdata_iter_finalize);
+ return iter;
+}
+
+static ant_value_t js_formdata_entries(ant_t *js, ant_value_t *args, int nargs) {
+ return make_formdata_iter(js, js->this_val, FD_ITER_ENTRIES);
+}
+
+static ant_value_t js_formdata_keys(ant_t *js, ant_value_t *args, int nargs) {
+ return make_formdata_iter(js, js->this_val, FD_ITER_KEYS);
+}
+
+static ant_value_t js_formdata_values(ant_t *js, ant_value_t *args, int nargs) {
+ return make_formdata_iter(js, js->this_val, FD_ITER_VALUES);
+}
+
+static ant_value_t js_formdata_ctor(ant_t *js, ant_value_t *args, int nargs) {
+ if (vtype(js->new_target) == T_UNDEF)
+ return js_mkerr_typed(js, JS_ERR_TYPE, "FormData constructor requires 'new'");
+ if (nargs >= 1 && vtype(args[0]) != T_UNDEF && vtype(args[0]) != T_NULL)
+ return js_mkerr_typed(js, JS_ERR_TYPE, "FormData does not support a form element argument");
+
+ fd_data_t *d = fd_data_new();
+ if (!d) return js_mkerr(js, "out of memory");
+
+ ant_value_t obj = js_mkobj(js);
+ ant_value_t proto = js_instance_proto_from_new_target(js, g_formdata_proto);
+ if (is_object_type(proto)) js_set_proto_init(obj, proto);
+
+ js_set_slot(obj, SLOT_DATA, ANT_PTR(d));
+ ant_value_t vals = js_mkarr(js);
+ js_set_slot_wb(js, obj, SLOT_ENTRIES, vals);
+ js_set_finalizer(obj, formdata_finalize);
+ return obj;
+}
+
+void init_formdata_module(void) {
+ ant_t *js = rt->js;
+ ant_value_t g = js_glob(js);
+ g_formdata_proto = js_mkobj(js);
+
+ js_set(js, g_formdata_proto, "append", js_mkfun(js_formdata_append));
+ js_set(js, g_formdata_proto, "set", js_mkfun(js_formdata_set));
+ js_set(js, g_formdata_proto, "get", js_mkfun(js_formdata_get));
+ js_set(js, g_formdata_proto, "getAll", js_mkfun(js_formdata_get_all));
+ js_set(js, g_formdata_proto, "has", js_mkfun(js_formdata_has));
+ js_set(js, g_formdata_proto, "delete", js_mkfun(js_formdata_delete));
+ js_set(js, g_formdata_proto, "forEach", js_mkfun(js_formdata_foreach));
+ js_set(js, g_formdata_proto, "entries", js_mkfun(js_formdata_entries));
+ js_set(js, g_formdata_proto, "keys", js_mkfun(js_formdata_keys));
+ js_set(js, g_formdata_proto, "values", js_mkfun(js_formdata_values));
+
+ js_set_sym(js, g_formdata_proto, get_iterator_sym(), js_mkfun(js_formdata_entries));
+ js_set_sym(js, g_formdata_proto, get_toStringTag_sym(), js_mkstr(js, "FormData", 8));
+
+ ant_value_t ctor_obj = js_mkobj(js);
+ js_set_slot(ctor_obj, SLOT_CFUNC, js_mkfun(js_formdata_ctor));
+ js_mkprop_fast(js, ctor_obj, "prototype", 9, g_formdata_proto);
+ js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, "FormData", 8));
+ js_set_descriptor(js, ctor_obj, "name", 4, 0);
+
+ ant_value_t ctor = js_obj_to_func(ctor_obj);
+ js_set(js, g_formdata_proto, "constructor", ctor);
+ js_set_descriptor(js, g_formdata_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
+
+ js_set(js, g, "FormData", ctor);
+ js_set_descriptor(js, g, "FormData", 8, JS_DESC_W | JS_DESC_C);
+
+ g_formdata_iter_proto = js_mkobj(js);
+ js_set_proto_init(g_formdata_iter_proto, js->sym.iterator_proto);
+ js_set(js, g_formdata_iter_proto, "next", js_mkfun(formdata_iter_next));
+ js_set_descriptor(js, g_formdata_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
+ js_set_sym(js, g_formdata_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
+}