From 88450f54c326bd43fa4188426c77e19aeccf6cfa Mon Sep 17 00:00:00 2001 From: viastolfi Date: Wed, 10 Jun 2026 14:01:06 +0200 Subject: [PATCH 1/4] feat: add base char workflow in lexer and ast parser, add tests --- cleaf_backlog.txt | 1 + src/frontend/ast.c | 18 ++++++++++++++++++ src/frontend/ast.h | 1 + src/frontend/ast_definition.h | 2 ++ src/frontend/ast_printer.c | 4 ++++ src/frontend/lexer.h | 1 + src/frontend/types.h | 14 ++++++++------ src/middleend/hir.c | 2 +- test/ast_test.c | 15 +++++++++++++++ 9 files changed, 51 insertions(+), 7 deletions(-) diff --git a/cleaf_backlog.txt b/cleaf_backlog.txt index fe9fc45..e9a6276 100644 --- a/cleaf_backlog.txt +++ b/cleaf_backlog.txt @@ -11,3 +11,4 @@ x 2026-06-10 2026-06-10 Choose between mutable or const for var by default and c (A) Enhance error reporting @thirdparty (B) choose if we pass struct var by value or pointer and implement both methods (C) make language documentation @docs +(D) add defer keyword @global diff --git a/src/frontend/ast.c b/src/frontend/ast.c index afbbea4..6e1d8d0 100644 --- a/src/frontend/ast.c +++ b/src/frontend/ast.c @@ -723,11 +723,29 @@ expression_t* parse_primary(parser_t* p) return NULL; } +expression_t* ast_parse_expr_char_lit(parser_t* p) +{ + expression_t* expr = calloc(1, sizeof(expression_t)); + if (!expr) { + error_report_general(ERROR_SEVERITY_ERROR, "out of memory"); + return NULL; + } + expr->type = EXPRESSION_CHAR_LIT; + + token_t* char_tok = advance(p); + + expr->char_lit.value = (unsigned char) char_tok->int_value; + return expr; +} + expression_t* parse_expression(parser_t* p) { if (check(p, '{')) return ast_parse_expr_composite_literal(p); + if (check(p, LEXER_token_charlit)) + return ast_parse_expr_char_lit(p); + if (check(p, LEXER_token_plusplus) || check(p, LEXER_token_minusminus) || check(p, '-') || diff --git a/src/frontend/ast.h b/src/frontend/ast.h index 5b698e6..db6be2d 100644 --- a/src/frontend/ast.h +++ b/src/frontend/ast.h @@ -71,6 +71,7 @@ expression_t* ast_parse_expr_comparison_binary(parser_t* p); expression_t* ast_parse_expr_call(parser_t* p); expression_t* ast_parse_expr_unary(parser_t* p); expression_t* ast_parse_expr_composite_literal(parser_t* p); +expression_t* ast_parse_expr_char_lit(parser_t* p); void populate_parser_known_type(known_type_array* types); diff --git a/src/frontend/ast_definition.h b/src/frontend/ast_definition.h index 70894f4..5b2aece 100644 --- a/src/frontend/ast_definition.h +++ b/src/frontend/ast_definition.h @@ -29,6 +29,7 @@ typedef enum typedef enum { EXPRESSION_INT_LIT, + EXPRESSION_CHAR_LIT, EXPRESSION_VAR, EXPRESSION_BINARY, EXPRESSION_CALL, @@ -193,6 +194,7 @@ struct expression_t union { struct { int value; } int_lit; + struct { char value; } char_lit; struct { typed_identifier_t ident; expression_t* member; diff --git a/src/frontend/ast_printer.c b/src/frontend/ast_printer.c index fbf8697..28df559 100644 --- a/src/frontend/ast_printer.c +++ b/src/frontend/ast_printer.c @@ -99,6 +99,10 @@ static void print_expression(expression_t* e, const char* prefix, bool is_last) printf(CLR_LIT "IntegerLiteral" CLR_RESET " %d\n", e->int_lit.value); break; + case EXPRESSION_CHAR_LIT: + printf(CLR_LIT "CharLiteral" CLR_RESET " %c\n", e->char_lit.value); + break; + case EXPRESSION_VAR: printf(CLR_LIT "VarRef" CLR_RESET " '%s': ", e->var.ident.ident_name ? diff --git a/src/frontend/lexer.h b/src/frontend/lexer.h index 4603f89..01a8385 100644 --- a/src/frontend/lexer.h +++ b/src/frontend/lexer.h @@ -172,6 +172,7 @@ static token_t lexer_copy_token(lexer_t* lex) } break; case LEXER_token_intlit: + case LEXER_token_charlit: token.int_value = lex->int_value; break; default: diff --git a/src/frontend/types.h b/src/frontend/types.h index d89862e..77811a5 100644 --- a/src/frontend/types.h +++ b/src/frontend/types.h @@ -3,6 +3,7 @@ typedef enum { TYPE_U8 = 0, + TYPE_CHAR, TYPE_U16, TYPE_U32, TYPE_INT, @@ -22,12 +23,13 @@ typedef struct { // we store sizes as bytes while the syntax use bits for convinience static const types_ident types_description[TYPE_COUNT] = { - {"u8", 1}, - {"u16", 2}, - {"u32", 4}, - {"int", 4}, - {"u64", 8}, - {"var", -1}, + {"u8", 1}, + {"char", 1}, + {"u16", 2}, + {"u32", 4}, + {"int", 4}, + {"u64", 8}, + {"var", -1}, {"untype", -1}, }; diff --git a/src/middleend/hir.c b/src/middleend/hir.c index b87cf79..9c06ec7 100644 --- a/src/middleend/hir.c +++ b/src/middleend/hir.c @@ -167,7 +167,7 @@ int IR_lower_composite_literal_expression( mov_offset->kind = IR_MOV_OFFSET; mov_offset->offset.timing = IR_PRE_OFFSET; mov_offset->dest.id = save; - mov_offset->dest.size = decl->var_decl.ident.type.kind; + mov_offset->dest.size = decl->var_decl.ident.type.size; mov_offset->src.id = func->next_temp_id; size_t j = 0; diff --git a/test/ast_test.c b/test/ast_test.c index 522e770..ffbc8c6 100644 --- a/test/ast_test.c +++ b/test/ast_test.c @@ -588,6 +588,21 @@ ct_test(ast, const_in_func_parameter, "fn main(int! a) { }") { ct_assert_eq(decl->func.params.count, 1, "function should have one parameter"); ct_assert_eq(decl->func.params.items[0].is_constant, true, "first function parameter should be marked as constant"); + free_declaration(decl); + da_free(&parser); +} + +ct_test(ast, char_var_decl, "char a = 'a';") { + declaration_t* decl = parse_declaration(&parser); + ct_assert_not_null(decl, "char var declaration should not be null"); + ct_assert_eq(decl->var_decl.ident.type.name, "char", "var type should be char"); + ct_assert_eq(decl->var_decl.ident.ident_name, "a", "Variable name should be 'a'"); + ct_assert_eq(decl->var_decl.ident.type.kind, TYPE_CHAR, "Variable type should be TYPE_CHAR"); + ct_assert_not_null(decl->var_decl.init, "Variable init expression should not be NULL"); + ct_assert_eq(decl->var_decl.init->type, EXPRESSION_CHAR_LIT, "Init expression should be char literal"); + ct_assert_eq(decl->var_decl.init->char_lit.value, 'a', "Init char value should be 'a'"); + + free_declaration(decl); da_free(&parser); } From 7c2390ce173c308c92e71933bb08fd89498f438f Mon Sep 17 00:00:00 2001 From: viastolfi Date: Thu, 11 Jun 2026 10:41:02 +0200 Subject: [PATCH 2/4] feat: add char var declaration to semantic analyze --- cleaf_backlog.txt | 1 + src/frontend/ast.c | 20 ++++++++++++-------- src/frontend/semantic.c | 7 ++++++- test/semantic_case/char_declaration.clf | 11 +++++++++++ test/semantic_test.c | 5 +++++ 5 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 test/semantic_case/char_declaration.clf diff --git a/cleaf_backlog.txt b/cleaf_backlog.txt index e9a6276..0e14bf0 100644 --- a/cleaf_backlog.txt +++ b/cleaf_backlog.txt @@ -12,3 +12,4 @@ x 2026-06-10 2026-06-10 Choose between mutable or const for var by default and c (B) choose if we pass struct var by value or pointer and implement both methods (C) make language documentation @docs (D) add defer keyword @global +(A) add type casting @global diff --git a/src/frontend/ast.c b/src/frontend/ast.c index 6e1d8d0..9d9c311 100644 --- a/src/frontend/ast.c +++ b/src/frontend/ast.c @@ -580,8 +580,9 @@ expression_t* ast_parse_expr_call(parser_t* p) int l = 0; while (!check_next(p, ')', l)) { if ((size_t) p->pos + l > p->count) { - error_report_at_token(p->error_ctx, peek_next(p, l - 1), ERROR_SEVERITY_ERROR, - "unexpected EOF, looking for ')' token"); + error_report_at_token( + p->error_ctx, peek_next(p, l - 1), ERROR_SEVERITY_ERROR, + "unexpected EOF, looking for ')' token"); } ++l; } @@ -679,8 +680,9 @@ expression_t* ast_parse_expr_unary(parser_t* p) } if (!operand) { - error_report_at_token(p->error_ctx, peek(p), ERROR_SEVERITY_ERROR, - "expected identifier or literal before postfix operator"); + error_report_at_token( + p->error_ctx, peek(p), ERROR_SEVERITY_ERROR, + "expected identifier or literal before postfix operator"); free_expression(e); return NULL; } @@ -848,8 +850,9 @@ declaration_t* ast_parse_function(parser_t* p) if (strcmp(type_tok->string_value, "var") == 0) { if (p->error_ctx) { - error_report_at_token(p->error_ctx, type_tok, ERROR_SEVERITY_ERROR, - "`var` cannot be use for functions parameters. Use an explicit type instead"); + error_report_at_token( + p->error_ctx, type_tok, ERROR_SEVERITY_ERROR, + "`var` cannot be use for functions parameters. Use an explicit type instead"); } free_declaration(decl); return NULL; @@ -1218,8 +1221,9 @@ declaration_t* ast_parse_struct_decl(parser_t* p) if (strcmp(type_tok->string_value, "var") == 0) { if (p->error_ctx) { - error_report_at_token(p->error_ctx, type_tok, ERROR_SEVERITY_ERROR, - "`var` cannot be use for struct members. Use an explicit type instead"); + error_report_at_token( + p->error_ctx, type_tok, ERROR_SEVERITY_ERROR, + "`var` cannot be use for struct members. Use an explicit type instead"); } free_declaration(decl); return NULL; diff --git a/src/frontend/semantic.c b/src/frontend/semantic.c index e7d8b3c..15a2c73 100644 --- a/src/frontend/semantic.c +++ b/src/frontend/semantic.c @@ -225,6 +225,10 @@ known_type_t semantic_check_expression( .size = types_description[kind].size, }; } + + if (expr->type == EXPRESSION_CHAR_LIT) { + return expr->var.ident.type; + } if (expr->type == EXPRESSION_VAR) { variable_symbol_t* vs = @@ -616,7 +620,8 @@ void semantic_check_var_declaration( analyzer, decl->source_pos -1, "can't store value. Exceeding max type accetping value"); } - if (expected_type.kind >= actual_type.kind) + if (expected_type.kind >= actual_type.kind && + actual_type.kind != TYPE_CHAR) goto var_def_put; if (actual_type.kind != TYPE_ERROR) { diff --git a/test/semantic_case/char_declaration.clf b/test/semantic_case/char_declaration.clf new file mode 100644 index 0000000..509eb19 --- /dev/null +++ b/test/semantic_case/char_declaration.clf @@ -0,0 +1,11 @@ +fn main() { + char a = 'q'; + char b = 256; + char c = 'a'; + char d = 34; + char e = foo('e'); +} + +fn foo(char a): char { + return a; +} diff --git a/test/semantic_test.c b/test/semantic_test.c index 3589d6b..b0c6fe8 100644 --- a/test/semantic_test.c +++ b/test/semantic_test.c @@ -690,3 +690,8 @@ ct_test(semantic_case, const_in_func_parameter_error, "test/semantic_case/const_ free_analyzer(&analyzer); } + +ct_test(semantic_case, char_declaration, "test/semantic_case/char_declaration.clf") { + ct_assert_eq(analyzer.error_count, 2, "Should have 2 errors"); + free_analyzer(&analyzer); +} From 5c3b5711a2104357d7732b317548757a41775f84 Mon Sep 17 00:00:00 2001 From: viastolfi Date: Thu, 11 Jun 2026 11:04:10 +0200 Subject: [PATCH 3/4] feat: add char var declaration to ir and codegen steps --- cleaf_backlog.txt | 1 + src/middleend/hir.c | 14 ++++++++++++++ test/codegen_case/char_var_declaration.asm | 10 ++++++++++ test/codegen_case/char_var_declaration.clf | 4 ++++ test/codegen_test.c | 4 ++++ test/hir_case/char_var_declaration.clf | 4 ++++ test/hir_case/char_var_declaration.res | 5 +++++ test/hir_test.c | 4 ++++ 8 files changed, 46 insertions(+) create mode 100644 test/codegen_case/char_var_declaration.asm create mode 100644 test/codegen_case/char_var_declaration.clf create mode 100644 test/hir_case/char_var_declaration.clf create mode 100644 test/hir_case/char_var_declaration.res diff --git a/cleaf_backlog.txt b/cleaf_backlog.txt index 0e14bf0..f46a351 100644 --- a/cleaf_backlog.txt +++ b/cleaf_backlog.txt @@ -13,3 +13,4 @@ x 2026-06-10 2026-06-10 Choose between mutable or const for var by default and c (C) make language documentation @docs (D) add defer keyword @global (A) add type casting @global +(B) add way to get var address @global diff --git a/src/middleend/hir.c b/src/middleend/hir.c index 9c06ec7..64ee6c8 100644 --- a/src/middleend/hir.c +++ b/src/middleend/hir.c @@ -424,6 +424,20 @@ int IR_lower_expression(HIR_parser_t* hir, return 0; } + if (expr->type == EXPRESSION_CHAR_LIT) { + IR_instruction_t* instr = calloc(1, sizeof(IR_instruction_t)); + if (!instr) { + error_report_general(ERROR_SEVERITY_ERROR, "out of memory"); + return -1; + } + + instr->kind = IR_INT_CONST; + instr->dest.id = ++(func->next_temp_id); + instr->int_value = expr->char_lit.value; + da_append(func->code, instr); + return 0; + } + if (expr->type == EXPRESSION_VAR) { IR_instruction_t* instr = calloc(1, sizeof(IR_instruction_t)); if (!instr) { diff --git a/test/codegen_case/char_var_declaration.asm b/test/codegen_case/char_var_declaration.asm new file mode 100644 index 0000000..dbf938d --- /dev/null +++ b/test/codegen_case/char_var_declaration.asm @@ -0,0 +1,10 @@ +section .text +global _start +_start: + push rbp + mov rbp, rsp + sub rsp, 2 + mov r11, 97 + mov [rbp - 8], r11 + mov r12, 12 + mov [rbp - 16], r12 diff --git a/test/codegen_case/char_var_declaration.clf b/test/codegen_case/char_var_declaration.clf new file mode 100644 index 0000000..b56e069 --- /dev/null +++ b/test/codegen_case/char_var_declaration.clf @@ -0,0 +1,4 @@ +fn main() { + char a = 'a'; + char b = 12; +} diff --git a/test/codegen_test.c b/test/codegen_test.c index 5a94188..4645cdc 100644 --- a/test/codegen_test.c +++ b/test/codegen_test.c @@ -253,3 +253,7 @@ ct_test(codegen_test, asm_no_args, "test/codegen_case/asm_no_args.clf", "test/co ct_test(codegen_test, asm_with_arg, "test/codegen_case/asm_with_arg.clf", "test/codegen_case/asm_with_arg.asm") { ct_assert_eq(result, 0, "codegen gives right output for asm with variable interpolation"); } + +ct_test(codegen_test, char_var_declaration, "test/codegen_case/char_var_declaration.clf", "test/codegen_case/char_var_declaration.asm") { + ct_assert_eq(result, 0, "codegen gives right output for char var declaration"); +} diff --git a/test/hir_case/char_var_declaration.clf b/test/hir_case/char_var_declaration.clf new file mode 100644 index 0000000..b56e069 --- /dev/null +++ b/test/hir_case/char_var_declaration.clf @@ -0,0 +1,4 @@ +fn main() { + char a = 'a'; + char b = 12; +} diff --git a/test/hir_case/char_var_declaration.res b/test/hir_case/char_var_declaration.res new file mode 100644 index 0000000..5d17cd2 --- /dev/null +++ b/test/hir_case/char_var_declaration.res @@ -0,0 +1,5 @@ +Function main +0: t1 = INT_CONST 97 +1: STR slot(a), b1 +2: t2 = INT_CONST 12 +3: STR slot(b), b2 diff --git a/test/hir_test.c b/test/hir_test.c index a93dcab..e9073e5 100644 --- a/test/hir_test.c +++ b/test/hir_test.c @@ -246,3 +246,7 @@ ct_test(hir_test, asm_no_args, "test/hir_case/asm_no_args.clf", "test/hir_case/a ct_test(hir_test, asm_with_arg, "test/hir_case/asm_with_arg.clf", "test/hir_case/asm_with_arg.res") { ct_assert_eq(result, 0, "hir gives right output for asm with variable interpolation"); } + +ct_test(hir_test, char_var_declaration, "test/hir_case/char_var_declaration.clf", "test/hir_case/char_var_declaration.res") { + ct_assert_eq(result, 0, "hir gives right output for char var declaration"); +} From d0258caad97db569dd12515a0fcc2fcc6bb0fbeb Mon Sep 17 00:00:00 2001 From: viastolfi Date: Thu, 11 Jun 2026 11:04:53 +0200 Subject: [PATCH 4/4] docs: update backlog --- cleaf_backlog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cleaf_backlog.txt b/cleaf_backlog.txt index f46a351..848a7cc 100644 --- a/cleaf_backlog.txt +++ b/cleaf_backlog.txt @@ -4,7 +4,7 @@ x 2026-06-09 2026-06-09 Add "asm" function for inline assembly in the code base (B) Add "sizeof" compiler function @global (C) Add "type" keyword to create types at compile time @global (A) Import workflow for multiple file and std compilation @global -(D) Add "char" type @global +x 2026-06-11 2026-06-11 Add "char" type @global (B) Add Array type @global (B) Add signed type @global x 2026-06-10 2026-06-10 Choose between mutable or const for var by default and change compiler behavior depending on this choice @global