diff --git a/c_bridges/yyjson-bridge.c b/c_bridges/yyjson-bridge.c index 0c8b4fb1..6e6d4623 100644 --- a/c_bridges/yyjson-bridge.c +++ b/c_bridges/yyjson-bridge.c @@ -118,6 +118,16 @@ void *csyyjson_mut_arr_add_obj(void *doc, void *arr) { return (void *)yyjson_mut_arr_add_obj((yyjson_mut_doc *)doc, (yyjson_mut_val *)arr); } +void csyyjson_arr_add_str(void *doc, void *arr, const char *val) { + if (!doc || !arr) return; + yyjson_mut_arr_add_str((yyjson_mut_doc *)doc, (yyjson_mut_val *)arr, val ? val : ""); +} + +void csyyjson_arr_add_num(void *doc, void *arr, double val) { + if (!doc || !arr) return; + yyjson_mut_arr_add_real((yyjson_mut_doc *)doc, (yyjson_mut_val *)arr, val); +} + void *csyyjson_mut_get_root(void *doc) { if (!doc) return NULL; return (void *)yyjson_mut_doc_get_root((yyjson_mut_doc *)doc); diff --git a/src/codegen/runtime/runtime.ts b/src/codegen/runtime/runtime.ts index 5381e31d..ab839975 100644 --- a/src/codegen/runtime/runtime.ts +++ b/src/codegen/runtime/runtime.ts @@ -266,6 +266,8 @@ export class RuntimeGenerator { ir += "declare i8* @csyyjson_create_arr()\n"; ir += "declare i8* @csyyjson_mut_arr_add_obj(i8*, i8*)\n"; ir += "declare i8* @csyyjson_obj_add_obj(i8*, i8*, i8*)\n"; + ir += "declare void @csyyjson_arr_add_str(i8*, i8*, i8*)\n"; + ir += "declare void @csyyjson_arr_add_num(i8*, i8*, double)\n"; ir += "\n"; ir += "define i32 @csyyjson_get_num_as_int(i8* %item) {\n"; diff --git a/src/codegen/stdlib/json.ts b/src/codegen/stdlib/json.ts index 992e0f11..7af76c3b 100644 --- a/src/codegen/stdlib/json.ts +++ b/src/codegen/stdlib/json.ts @@ -530,6 +530,16 @@ export class JsonGenerator { if (elementType) { return this.stringifyObjectArray(arg, params, elementType, spaces); } + if (this.ctx.symbolTable.isStringArray(varNode.name)) { + return this.stringifyStringArray(arg, params, spaces); + } + if (this.ctx.symbolTable.isNumberArray(varNode.name)) { + return this.stringifyNumberArray(arg, params, spaces); + } + } + + if (this.ctx.isStringArrayExpression(arg)) { + return this.stringifyStringArray(arg, params, spaces); } const interfaceType = this.resolveInterfaceType(arg); @@ -537,7 +547,25 @@ export class JsonGenerator { return this.stringifyInterface(arg, params, interfaceType, spaces); } - return this.stringifyNumber(arg, params); + if (arg.type === "number" || arg.type === "boolean") { + return this.stringifyNumber(arg, params); + } + if (arg.type === "variable") { + const varNode = arg as { type: string; name: string }; + if ( + this.ctx.symbolTable.isNumber(varNode.name) || + this.ctx.symbolTable.isBoolean(varNode.name) + ) { + return this.stringifyNumber(arg, params); + } + return this.ctx.emitError( + `JSON.stringify: unsupported type for variable '${varNode.name}' — only string, number, boolean, interface, string[], number[], and object[] are supported`, + ); + } + + return this.ctx.emitError( + "JSON.stringify: unsupported argument type — only string, number, boolean, interface, string[], number[], and object[] are supported", + ); } private resolveInterfaceType(arg: Expression): string | null { @@ -730,6 +758,91 @@ export class JsonGenerator { return result; } + private stringifyStringArray(arg: Expression, params: string[], spaces: number): string { + this.ctx.setUsesJson(true); + const arrPtr = this.ctx.generateExpression(arg, params); + const jsonDoc = this.ctx.emitCall("i8*", "@csyyjson_create_arr", ""); + const jsonArr = this.ctx.emitCall("i8*", "@csyyjson_mut_get_root", `i8* ${jsonDoc}`); + + // Load length (field 1) and data pointer (field 0) from %StringArray + const lenPtr = this.ctx.emitGep("%StringArray", arrPtr, "i32 0, i32 1"); + const len = this.ctx.emitLoad("i32", lenPtr); + const dataPtr = this.ctx.emitGep("%StringArray", arrPtr, "i32 0, i32 0"); + const dataRaw = this.ctx.emitLoad("i8**", dataPtr); + + const counterAlloca = this.ctx.nextTemp(); + this.ctx.emit(`${counterAlloca} = alloca i32`); + this.ctx.emitStore("i32", "0", counterAlloca); + + const loopCond = this.ctx.nextLabel("json_str_arr_cond"); + const loopBody = this.ctx.nextLabel("json_str_arr_body"); + const loopEnd = this.ctx.nextLabel("json_str_arr_end"); + + this.ctx.emitBr(loopCond); + this.ctx.emitLabel(loopCond); + const i = this.ctx.emitLoad("i32", counterAlloca); + const cond = this.ctx.emitIcmp("slt", "i32", i, len); + this.ctx.emitBrCond(cond, loopBody, loopEnd); + + this.ctx.emitLabel(loopBody); + const elemSlot = this.ctx.emitGep("i8*", dataRaw, `i32 ${i}`); + const elem = this.ctx.emitLoad("i8*", elemSlot); + this.ctx.emitCallVoid("@csyyjson_arr_add_str", `i8* ${jsonDoc}, i8* ${jsonArr}, i8* ${elem}`); + const iNext = this.ctx.nextTemp(); + this.ctx.emit(`${iNext} = add i32 ${i}, 1`); + this.ctx.emitStore("i32", iNext, counterAlloca); + this.ctx.emitBr(loopCond); + + this.ctx.emitLabel(loopEnd); + const result = this.emitStringify(jsonDoc, spaces); + this.ctx.setVariableType(result, "i8*"); + return result; + } + + private stringifyNumberArray(arg: Expression, params: string[], spaces: number): string { + this.ctx.setUsesJson(true); + const arrPtr = this.ctx.generateExpression(arg, params); + const jsonDoc = this.ctx.emitCall("i8*", "@csyyjson_create_arr", ""); + const jsonArr = this.ctx.emitCall("i8*", "@csyyjson_mut_get_root", `i8* ${jsonDoc}`); + + // Load length (field 1) and data pointer (field 0) from %Array + const lenPtr = this.ctx.emitGep("%Array", arrPtr, "i32 0, i32 1"); + const len = this.ctx.emitLoad("i32", lenPtr); + const dataPtr = this.ctx.emitGep("%Array", arrPtr, "i32 0, i32 0"); + const dataRaw = this.ctx.emitLoad("double*", dataPtr); + + const counterAlloca = this.ctx.nextTemp(); + this.ctx.emit(`${counterAlloca} = alloca i32`); + this.ctx.emitStore("i32", "0", counterAlloca); + + const loopCond = this.ctx.nextLabel("json_num_arr_cond"); + const loopBody = this.ctx.nextLabel("json_num_arr_body"); + const loopEnd = this.ctx.nextLabel("json_num_arr_end"); + + this.ctx.emitBr(loopCond); + this.ctx.emitLabel(loopCond); + const i = this.ctx.emitLoad("i32", counterAlloca); + const cond = this.ctx.emitIcmp("slt", "i32", i, len); + this.ctx.emitBrCond(cond, loopBody, loopEnd); + + this.ctx.emitLabel(loopBody); + const elemSlot = this.ctx.emitGep("double", dataRaw, `i32 ${i}`); + const elem = this.ctx.emitLoad("double", elemSlot); + this.ctx.emitCallVoid( + "@csyyjson_arr_add_num", + `i8* ${jsonDoc}, i8* ${jsonArr}, double ${elem}`, + ); + const iNext = this.ctx.nextTemp(); + this.ctx.emit(`${iNext} = add i32 ${i}, 1`); + this.ctx.emitStore("i32", iNext, counterAlloca); + this.ctx.emitBr(loopCond); + + this.ctx.emitLabel(loopEnd); + const result = this.emitStringify(jsonDoc, spaces); + this.ctx.setVariableType(result, "i8*"); + return result; + } + private stringifyString(arg: Expression, params: string[]): string { const strPtr = this.ctx.generateExpression(arg, params); diff --git a/tests/fixtures/builtins/json-stringify-number-array.ts b/tests/fixtures/builtins/json-stringify-number-array.ts new file mode 100644 index 00000000..74103121 --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-number-array.ts @@ -0,0 +1,6 @@ +// @test-description: json stringify number array +const nums: number[] = [1, 2, 3]; +const result = JSON.stringify(nums); +if (result.includes("1") && result.includes("2") && result.includes("3")) { + console.log("TEST_PASSED"); +} diff --git a/tests/fixtures/builtins/json-stringify-string-array.ts b/tests/fixtures/builtins/json-stringify-string-array.ts new file mode 100644 index 00000000..7ed68cf7 --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-string-array.ts @@ -0,0 +1,6 @@ +// @test-description: json stringify string array +const tags: string[] = ["alpha", "beta", "gamma"]; +const result = JSON.stringify(tags); +if (result.includes("alpha") && result.includes("beta") && result.includes("gamma")) { + console.log("TEST_PASSED"); +} diff --git a/tests/fixtures/builtins/json-stringify-unsupported-map.ts b/tests/fixtures/builtins/json-stringify-unsupported-map.ts new file mode 100644 index 00000000..6edee76d --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-unsupported-map.ts @@ -0,0 +1,4 @@ +// @test-compile-error: unsupported type — only string, number, boolean, interface, string[], number[], and object[] are supported +const m: Map = new Map(); +m.set("key", 42); +JSON.stringify(m); diff --git a/tests/fixtures/builtins/json-stringify-unsupported-set.ts b/tests/fixtures/builtins/json-stringify-unsupported-set.ts new file mode 100644 index 00000000..588e430c --- /dev/null +++ b/tests/fixtures/builtins/json-stringify-unsupported-set.ts @@ -0,0 +1,4 @@ +// @test-compile-error: unsupported type — only string, number, boolean, interface, string[], number[], and object[] are supported +const s: Set = new Set(); +s.add(1); +JSON.stringify(s);