summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-26 00:55:08 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-26 00:55:08 +0000
commit4a20a08c80d1170561db9a268e8116ad311714b9 (patch)
tree093344f67a28d86809de89eda8b067f17665844a
parentca08979910e5a234a2423e4448ad0e284bf4f508 (diff)
Fix for #126
-rw-r--r--src/codegen/codegen_decl.c13
-rw-r--r--src/parser/parser.h2
-rw-r--r--src/parser/parser_core.c72
-rw-r--r--src/parser/parser_decl.c4
-rw-r--r--src/parser/parser_struct.c2
-rw-r--r--src/parser/parser_utils.c59
-rw-r--r--tests/generics/test_generic_string_literal.zc41
7 files changed, 130 insertions, 63 deletions
diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c
index e5d73a0..d59511d 100644
--- a/src/codegen/codegen_decl.c
+++ b/src/codegen/codegen_decl.c
@@ -22,7 +22,8 @@ static void emit_freestanding_preamble(FILE *out)
"uint64_t\n",
out);
fputs("#define F32 float\n#define F64 double\n", out);
- fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n", out);
+ fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n",
+ out);
fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", "
"signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", "
"unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", "
@@ -64,9 +65,11 @@ void emit_preamble(ParserContext *ctx, FILE *out)
fputs("#define ZC_AUTO auto\n", out);
fputs("#define ZC_CAST(T, x) static_cast<T>(x)\n", out);
// C++ _z_str via overloads
- fputs("inline const char* _z_bool_str(bool b) { return b ? \"true\" : \"false\"; }\n", out);
+ fputs("inline const char* _z_bool_str(bool b) { return b ? \"true\" : \"false\"; }\n",
+ out);
fputs("inline const char* _z_str(bool) { return \"%s\"; }\n", out);
- fputs("inline const char* _z_arg(bool b) { return _z_bool_str(b); }\n", out);
+ fputs("inline const char* _z_arg(bool b) { return _z_bool_str(b); }\n",
+ out);
fputs("template<typename T> inline T _z_arg(T x) { return x; }\n", out);
fputs("inline const char* _z_str(char) { return \"%c\"; }\n", out);
fputs("inline const char* _z_str(int) { return \"%d\"; }\n", out);
@@ -87,7 +90,9 @@ void emit_preamble(ParserContext *ctx, FILE *out)
fputs("#define ZC_AUTO __auto_type\n", out);
fputs("#define ZC_CAST(T, x) ((T)(x))\n", out);
fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out);
- fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n", out);
+ fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : "
+ "\"false\"; }\n",
+ out);
fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", "
"signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", "
"unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", "
diff --git a/src/parser/parser.h b/src/parser/parser.h
index 7353570..eb8682d 100644
--- a/src/parser/parser.h
+++ b/src/parser/parser.h
@@ -309,7 +309,7 @@ Type *find_symbol_type_info(ParserContext *ctx, const char *n);
char *find_symbol_type(ParserContext *ctx, const char *n);
ZenSymbol *find_symbol_entry(ParserContext *ctx, const char *n);
ZenSymbol *find_symbol_in_all(ParserContext *ctx,
- const char *n); // LSP flat lookup
+ const char *n);
char *find_similar_symbol(ParserContext *ctx, const char *name);
// Function registry
diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c
index 1245a55..d575693 100644
--- a/src/parser/parser_core.c
+++ b/src/parser/parser_core.c
@@ -689,19 +689,25 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
}
else if (strcmp(ft, "double") == 0)
{
- sprintf(assign, "let _f_%s = (*j).get_float(\"%s\").unwrap_or(0.0);\n", fn, fn);
+ sprintf(assign, "let _f_%s = (*j).get_float(\"%s\").unwrap_or(0.0);\n", fn,
+ fn);
}
else if (strcmp(ft, "bool") == 0)
{
- sprintf(assign, "let _f_%s = (*j).get_bool(\"%s\").unwrap_or(false);\n", fn, fn);
+ sprintf(assign, "let _f_%s = (*j).get_bool(\"%s\").unwrap_or(false);\n", fn,
+ fn);
}
else if (strcmp(ft, "char*") == 0)
{
- sprintf(assign, "let _f_%s = (*j).get_string(\"%s\").unwrap_or(\"\");\n", fn, fn);
+ sprintf(assign, "let _f_%s = (*j).get_string(\"%s\").unwrap_or(\"\");\n",
+ fn, fn);
}
else if (strcmp(ft, "String") == 0)
{
- sprintf(assign, "let _f_%s = String::new((*j).get_string(\"%s\").unwrap_or(\"\"));\n", fn, fn);
+ sprintf(
+ assign,
+ "let _f_%s = String::new((*j).get_string(\"%s\").unwrap_or(\"\"));\n",
+ fn, fn);
}
else if (ft && strstr(ft, "Vec") && strstr(ft, "String"))
{
@@ -710,7 +716,8 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
{
vec_fields[vec_field_count++] = fn;
}
- sprintf(assign,
+ sprintf(
+ assign,
"let _f_%s = Vec<String>::new();\n"
"let _arr_%s = (*j).get_array(\"%s\");\n"
"if _arr_%s.is_some() {\n"
@@ -726,16 +733,18 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
" }\n"
" }\n"
"}\n",
- fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn);
+ fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn,
+ fn, fn, fn, fn, fn);
}
else
{
// Nested struct: call NestedType::from_json recursively
sprintf(assign,
- "let _opt_%s = (*j).get(\"%s\");\n"
- "let _f_%s: %s;\n"
- "if _opt_%s.is_some() { _f_%s = %s::from_json(_opt_%s.unwrap()).unwrap(); }\n",
- fn, fn, fn, ft, fn, fn, ft, fn);
+ "let _opt_%s = (*j).get(\"%s\");\n"
+ "let _f_%s: %s;\n"
+ "if _opt_%s.is_some() { _f_%s = "
+ "%s::from_json(_opt_%s.unwrap()).unwrap(); }\n",
+ fn, fn, fn, ft, fn, fn, ft, fn);
}
strcat(body, assign);
}
@@ -755,7 +764,10 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
{
if (f->type == NODE_FIELD)
{
- if (!first) strcat(body, ", ");
+ if (!first)
+ {
+ strcat(body, ", ");
+ }
char init[128];
// Check if this is a Vec<String> field - clone it to avoid double-free
int is_vec_field = 0;
@@ -783,9 +795,8 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
strcat(body, " }); ");
code = xmalloc(8192 + 1024);
- sprintf(code,
- "impl %s { fn from_json(j: JsonValue*) -> Result<%s> { %s } }",
- name, name, body);
+ sprintf(code, "impl %s { fn from_json(j: JsonValue*) -> Result<%s> { %s } }", name,
+ name, body);
}
else if (0 == strcmp(trait, "ToJson"))
{
@@ -817,11 +828,13 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
if (strcmp(ft, "int") == 0)
{
- sprintf(set_call, "_obj.set(\"%s\", JsonValue::number((double)self.%s));\n", fn, fn);
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::number((double)self.%s));\n",
+ fn, fn);
}
else if (strcmp(ft, "double") == 0)
{
- sprintf(set_call, "_obj.set(\"%s\", JsonValue::number(self.%s));\n", fn, fn);
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::number(self.%s));\n", fn,
+ fn);
}
else if (strcmp(ft, "bool") == 0)
{
@@ -829,28 +842,29 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
}
else if (strcmp(ft, "char*") == 0)
{
- sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s));\n", fn, fn);
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s));\n", fn,
+ fn);
}
else if (strcmp(ft, "String") == 0)
{
- sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s.c_str()));\n", fn, fn);
+ sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s.c_str()));\n",
+ fn, fn);
}
else if (ft && strstr(ft, "Vec") && strstr(ft, "String"))
{
sprintf(set_call,
- "let _arr_%s = JsonValue::array();\n"
- "for let _i_%s: usize = 0; _i_%s < self.%s.length(); _i_%s = _i_%s + 1 {\n"
- " _arr_%s.push(JsonValue::string(self.%s.get(_i_%s).c_str()));\n"
- "}\n"
- "_obj.set(\"%s\", _arr_%s);\n",
- fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn);
+ "let _arr_%s = JsonValue::array();\n"
+ "for let _i_%s: usize = 0; _i_%s < self.%s.length(); _i_%s = _i_%s "
+ "+ 1 {\n"
+ " _arr_%s.push(JsonValue::string(self.%s.get(_i_%s).c_str()));\n"
+ "}\n"
+ "_obj.set(\"%s\", _arr_%s);\n",
+ fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn);
}
else
{
// Nested struct: call to_json recursively
- sprintf(set_call,
- "_obj.set(\"%s\", self.%s.to_json());\n",
- fn, fn);
+ sprintf(set_call, "_obj.set(\"%s\", self.%s.to_json());\n", fn, fn);
}
strcat(body, set_call);
}
@@ -860,9 +874,7 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char *
strcat(body, "return _obj;");
code = xmalloc(8192 + 1024);
- sprintf(code,
- "impl %s { fn to_json(self) -> JsonValue { %s } }",
- name, body);
+ sprintf(code, "impl %s { fn to_json(self) -> JsonValue { %s } }", name, body);
}
if (code)
diff --git a/src/parser/parser_decl.c b/src/parser/parser_decl.c
index 48077e1..ab1516e 100644
--- a/src/parser/parser_decl.c
+++ b/src/parser/parser_decl.c
@@ -56,13 +56,13 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async)
strcat(buf, ",");
}
strcat(buf, s);
-
+
// Check for shadowing
if (is_known_generic(ctx, s))
{
zpanic_at(gt, "Generic parameter '%s' shadows an existing generic parameter", s);
}
-
+
free(s);
if (lexer_peek(l).type == TOK_COMMA)
diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c
index b7dec32..84450ba 100644
--- a/src/parser/parser_struct.c
+++ b/src/parser/parser_struct.c
@@ -142,7 +142,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l)
n_node->trait.methods = methods;
n_node->trait.generic_params = generic_params;
n_node->trait.generic_param_count = generic_count;
-
+
if (generic_count > 0)
{
ctx->known_generics_count -= generic_count;
diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c
index 86e1b50..f889561 100644
--- a/src/parser/parser_utils.c
+++ b/src/parser/parser_utils.c
@@ -858,6 +858,7 @@ ASTNode *copy_fields(ASTNode *fields)
n->next = copy_fields(fields->next);
return n;
}
+
char *replace_in_string(const char *src, const char *old_w, const char *new_w)
{
if (!src || !old_w || !new_w)
@@ -921,20 +922,23 @@ char *replace_in_string(const char *src, const char *old_w, const char *new_w)
int newWlen = strlen(new_w);
int oldWlen = strlen(old_w);
+ // Pass 1: Count replacements
+ int in_string = 0;
for (i = 0; src[i] != '\0'; i++)
{
- if (strstr(&src[i], old_w) == &src[i])
+ if (src[i] == '"' && (i == 0 || src[i - 1] != '\\'))
{
- // Check boundaries to ensure we match whole words only
- int valid = 1;
+ in_string = !in_string;
+ }
- // Check preceding character
+ if (!in_string && strstr(&src[i], old_w) == &src[i])
+ {
+ // Check boundaries
+ int valid = 1;
if (i > 0 && is_ident_char(src[i - 1]))
{
valid = 0;
}
-
- // Check following character
if (valid && is_ident_char(src[i + oldWlen]))
{
valid = 0;
@@ -951,42 +955,47 @@ char *replace_in_string(const char *src, const char *old_w, const char *new_w)
// Allocate result buffer
result = (char *)xmalloc(i + cnt * (newWlen - oldWlen) + 1);
- i = 0;
- while (*src)
+ // Pass 2: Perform replacement
+ int j = 0;
+ in_string = 0;
+
+ int src_idx = 0;
+
+ while (src[src_idx] != '\0')
{
- if (strstr(src, old_w) == src)
+ if (src[src_idx] == '"' && (src_idx == 0 || src[src_idx - 1] != '\\'))
{
- int valid = 1;
+ in_string = !in_string;
+ }
- // Check boundary relative to the *new* result buffer built so far
- if (i > 0 && is_ident_char(result[i - 1]))
+ int replaced = 0;
+ if (!in_string && strstr(&src[src_idx], old_w) == &src[src_idx])
+ {
+ int valid = 1;
+ if (src_idx > 0 && is_ident_char(src[src_idx - 1]))
{
valid = 0;
}
-
- // Check boundary relative to the *original* source string
- if (valid && is_ident_char(src[oldWlen]))
+ if (valid && is_ident_char(src[src_idx + oldWlen]))
{
valid = 0;
}
if (valid)
{
- strcpy(&result[i], new_w);
- i += newWlen;
- src += oldWlen;
- }
- else
- {
- result[i++] = *src++;
+ strcpy(&result[j], new_w);
+ j += newWlen;
+ src_idx += oldWlen;
+ replaced = 1;
}
}
- else
+
+ if (!replaced)
{
- result[i++] = *src++;
+ result[j++] = src[src_idx++];
}
}
- result[i] = '\0';
+ result[j] = '\0';
return result;
}
diff --git a/tests/generics/test_generic_string_literal.zc b/tests/generics/test_generic_string_literal.zc
new file mode 100644
index 0000000..5ad2cc3
--- /dev/null
+++ b/tests/generics/test_generic_string_literal.zc
@@ -0,0 +1,41 @@
+
+struct StringChecker<T> {
+ dummy: T;
+}
+
+impl StringChecker<T> {
+ fn check(self) -> bool {
+ // The generic parameter T should NOT be replaced inside these string literals
+ // If T is int, this should NOT become "int is a generic param"
+ let s1: char* = "T is a generic param";
+ if (strcmp(s1, "T is a generic param") != 0) {
+ printf("Failed s1: '%s' != 'T is a generic param'\n", s1);
+ return false;
+ }
+
+ // Check for T inside words
+ let s2: char* = "This contains T inside";
+ if (strcmp(s2, "This contains T inside") != 0) {
+ printf("Failed s2: '%s' != 'This contains T inside'\n", s2);
+ return false;
+ }
+
+ // Check for T at end
+ let s3: char* = "Ends with T";
+ if (strcmp(s3, "Ends with T") != 0) {
+ printf("Failed s3: '%s' != 'Ends with T'\n", s3);
+ return false;
+ }
+
+ return true;
+ }
+}
+
+test "generic_string_substitution" {
+ // Instantiate with 'int' so that if substitution happened, 'T' would become 'int'
+ let checker = StringChecker<int>{ dummy: 0 };
+
+ if (!checker.check()) {
+ exit(1);
+ }
+}