From e11089c90f05d522b0f81e455e7db14f45b7b337 Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 28 Oct 2026 17:57:18 +0300 Subject: [PATCH] feat: implement format strings --- .gdbinit | 1 + include/common/fzl_opcodes.h | 2 + include/compiler/fzl_parser.h | 1 + include/compiler/fzl_token.h | 3 + src/compiler/fzl_debug.c | 23 +++++- src/compiler/fzl_parser.c | 86 ++++++++++++++++++++++ src/compiler/fzl_reader.c | 86 +++++++++++++++++++++- src/compiler/fzl_token.c | 4 + src/compiler/fzl_walker.c | 14 ++++ src/vm/fzl_vm.c | 134 ++++++++++++++++++++++++++++++++++ src/vm/fzl_vm_debug.c | 7 +- 11 files changed, 354 insertions(+), 7 deletions(-) diff --git a/.gdbinit b/.gdbinit index e22660a..da2c0f2 100644 --- a/.gdbinit +++ b/.gdbinit @@ -5,6 +5,7 @@ skip is skip -rfunction fzl_mk* skip -rfunction fzl_free* skip -rfunction fzl_symt* +skip -rfunction fzl_linked* skip add_const skip push_op skip push_word diff --git a/include/common/fzl_opcodes.h b/include/common/fzl_opcodes.h index 1484134..93a3b07 100644 --- a/include/common/fzl_opcodes.h +++ b/include/common/fzl_opcodes.h @@ -35,6 +35,8 @@ typedef enum FZL_OPCODE_MOD = 0xA00006, // Get module of two numbers in dstack FZL_OPCODE_NEG = 0xA00007, // Negotiate number in dstack + FZL_OPCODE_BUILD_STR = 0xA10001, // Build a string from N values in dstack + FZL_OPCODE_CMP_EQ = 0xB00001, // Equal FZL_OPCODE_CMP_NEQ = 0xB00002, // Not equal FZL_OPCODE_CMP_LT = 0xB00003, // Less than diff --git a/include/compiler/fzl_parser.h b/include/compiler/fzl_parser.h index 83c39da..d61ba9e 100644 --- a/include/compiler/fzl_parser.h +++ b/include/compiler/fzl_parser.h @@ -11,6 +11,7 @@ typedef enum FZL_NODETYPE_BLOCK, FZL_NODETYPE_CALL, FZL_NODETYPE_LITERAL, + FZL_NODETYPE_FMT_STRING, FZL_NODETYPE_IDENTIFIER, FZL_NODETYPE_CONDITIONAL, FZL_NODETYPE_FUNCTION, diff --git a/include/compiler/fzl_token.h b/include/compiler/fzl_token.h index db85fb0..21ac815 100644 --- a/include/compiler/fzl_token.h +++ b/include/compiler/fzl_token.h @@ -40,6 +40,9 @@ typedef enum FZL_TOKTYPE_BOOL_LITERAL, // true FZL_TOKTYPE_NULL_LITERAL, // null + FZL_TOKTYPE_STR_FORMAT_START, // `$` before `"` + FZL_TOKTYPE_STR_FORMAT_END, // end of string formatting sequence + FZL_TOKTYPE_DAMPERSAND, // && FZL_TOKTYPE_DVBAR, // || diff --git a/src/compiler/fzl_debug.c b/src/compiler/fzl_debug.c index 0ba3d7f..ae50422 100644 --- a/src/compiler/fzl_debug.c +++ b/src/compiler/fzl_debug.c @@ -31,6 +31,26 @@ void print_node(fzl_node_t *node, int level) switch (node->type) { + case FZL_NODETYPE_UNARY_MINUS: + printf("%s Unary '-':\n", levelbuf); + print_node(node->unary_op.operand, level + 1); + break; + + case FZL_NODETYPE_UNARY_PLUS: + printf("%s Unary '+':\n", levelbuf); + print_node(node->unary_op.operand, level + 1); + break; + + case FZL_NODETYPE_BREAK: + printf("%s Break\n", levelbuf); + break; + + case FZL_NODETYPE_FMT_STRING: + printf("%s Format string:\n", levelbuf); + for (size_t i = 0; i < node->list.entry_count; i++) + print_node(node->list.entries[i], level + 1); + break; + case FZL_NODETYPE_LOOP: printf("%s Loop:\n", levelbuf); printf("-%s Init:\n", levelbuf); @@ -172,9 +192,6 @@ void print_node(fzl_node_t *node, int level) case FZL_NODETYPE_IDENTIFIER: printf("%s Identifier ('%s')\n", levelbuf, (char *)node->token->value); break; - - default: - printf("%s Secure node\n", levelbuf); } } diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 9b3a2e8..6bf159d 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -151,6 +151,91 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) fzl_token_t *tok = consume(parser); return fzl_mknode(FZL_NODETYPE_LITERAL, tok); } + else if (is(parser, FZL_TOKTYPE_STR_FORMAT_START)) + { + consume(parser); + + size_t remainder = 2; + size_t count = 0; + fzl_node_t **entries = malloc(remainder * sizeof(fzl_node_t *)); + if (!entries) + { + snprintf(plast_err, sizeof(plast_err), "out of memory"); + return NULL; + } + + while (peek(parser) && peek(parser)->type != FZL_TOKTYPE_STR_FORMAT_END) + { + if (remainder <= count) + { + remainder *= 2; + entries = realloc(entries, remainder * sizeof(fzl_node_t *)); + if (!entries) + { + snprintf(plast_err, sizeof(plast_err), "out of memory"); + return NULL; + } + } + + if (is(parser, FZL_TOKTYPE_STR_FORMAT_END)) + break; + + fzl_node_t *expr = parse_expression(parser); + if (!expr) + { + for (size_t i = 0; i < count; i++) + fzl_freeast(entries[i]); + + free(entries); + return NULL; + } + + entries[count++] = expr; + } + fzl_token_t *end = consume(parser); + if (end->type != FZL_TOKTYPE_STR_FORMAT_END) + { + snprintf(plast_err, sizeof(plast_err), + "'%s':%zu:%zu => unterminated format string", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + + for (size_t i = 0; i < count; i++) + fzl_freeast(entries[i]); + + free(entries); + } + + if (count < 1) + { + snprintf(plast_err, sizeof(plast_err), + "'%s':%zu:%zu => do you really need empty format string?", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + + for (size_t i = 0; i < count; i++) + fzl_freeast(entries[i]); + + free(entries); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_FMT_STRING, entries[0]->token); + if (!node) + { + snprintf(plast_err, sizeof(plast_err), "out of memory"); + for (size_t i = 0; i < count; i++) + fzl_freeast(entries[i]); + + free(entries); + return NULL; + } + + node->list.entries = entries; + node->list.entry_count = count; + + return node; + } else if (is(parser, FZL_TOKTYPE_LPAREN)) { consume(parser); @@ -1308,6 +1393,7 @@ void fzl_freeast(fzl_node_t *root) case FZL_NODETYPE_LITERAL: break; + case FZL_NODETYPE_FMT_STRING: case FZL_NODETYPE_BLOCK: if (root->list.entries) { diff --git a/src/compiler/fzl_reader.c b/src/compiler/fzl_reader.c index 6d6db33..fa09b1f 100644 --- a/src/compiler/fzl_reader.c +++ b/src/compiler/fzl_reader.c @@ -251,11 +251,83 @@ void skip_comment(fzl_reader_t *reader) } } +// Parse string format +fzl_token_t *parse_fmtstring_part(fzl_reader_t *reader) +{ + size_t p = reader->position; + char *src = reader->source; + + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, reader->position, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return NULL; + } + + if (src[p] == '{') + { + move(reader, 1); + fzl_token_t *id = parse_name(reader); + + if (id == NULL || src[reader->position] != '}') + { + snprintf(last_err, sizeof(last_err), + "'%s':%zu:%zu => failed to parse format string", l.filename, + l.line, l.col); + fzl_freetoken(id); + return NULL; + } + + move(reader, 1); + return id; + } + + if (src[p] == '"') + { + move(reader, 1); + + return fzl_mktoken(FZL_TOKTYPE_STR_FORMAT_END, l.line, l.col, NULL, 0, + l.filename); + } + + size_t size = 0; + size_t lpos = p; + + do + { + if (src[lpos + 1] == '"') + break; + + size++; + lpos++; + } while (lpos < reader->size && reader->source[lpos] != '{' && + reader->source[lpos] != '"'); + + ok = fzl_linked_getpos(reader->linked, reader->position, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return NULL; + } + + char *value = malloc(size); + memcpy(value, reader->source + reader->position, size); + fzl_token_t *tok = fzl_mktoken(FZL_TOKTYPE_STR_LITERAL, l.line, l.col, value, + size, l.filename); + free(value); + move(reader, size); + + return tok; +} + int fzl_read(fzl_reader_t *reader) { if (reader->position >= reader->size) return 0; + bool is_format_str = false; + do { size_t p = reader->position; @@ -268,7 +340,13 @@ int fzl_read(fzl_reader_t *reader) continue; } - if (curr == '\'' || curr == '"') + if (is_format_str) + { + token = parse_fmtstring_part(reader); + if (token && token->type == FZL_TOKTYPE_STR_FORMAT_END) + is_format_str = false; + } + else if (curr == '\'' || curr == '"') token = parse_string_literal(reader); else if (curr == '/' && reader->source[p + 1] == '/') { @@ -321,6 +399,12 @@ int fzl_read(fzl_reader_t *reader) token = add_single(reader, FZL_TOKTYPE_PERCENT); else if (curr == '=') token = add_single(reader, FZL_TOKTYPE_EQ); + else if (curr == '$' && reader->source[p + 1] == '"') + { + move(reader, 2); + is_format_str = true; + token = fzl_mktoken(FZL_TOKTYPE_STR_FORMAT_START, 0, 0, NULL, 0, ""); + } else if ((curr >= 'A' && curr <= 'Z') || (curr >= 'a' && curr <= 'z') || curr == '_') token = parse_name(reader); diff --git a/src/compiler/fzl_token.c b/src/compiler/fzl_token.c index dc62955..04ffe32 100644 --- a/src/compiler/fzl_token.c +++ b/src/compiler/fzl_token.c @@ -30,7 +30,11 @@ fzl_token_t *fzl_mktoken(const fzl_toktype_t type, size_t line, size_t col, // Free allocated token void fzl_freetoken(fzl_token_t *tok) { + if (!tok) + return; + if (tok->store_value) free(tok->value); + free(tok); } diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index b5d903b..47e17e3 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -203,6 +203,20 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) { switch (node->type) { + case FZL_NODETYPE_FMT_STRING: + { + for (size_t i = 0; i < node->list.entry_count; i++) + { + fzl_walk_res_t res = walk(walker, node->list.entries[i]); + if (res.error) + return res; + } + + push_op(walker, FZL_OPCODE_BUILD_STR); + push_word(walker, node->list.entry_count); + break; + } + case FZL_NODETYPE_BREAK: { fzl_walk_ctx_t *ctx = walker->ctx; diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index c2e062a..a10f088 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -13,6 +13,7 @@ #include #include +char build_result[512]; char last_err[256]; size_t last_jmp_ip = 0; size_t total_jmp_on_same = 0; @@ -297,6 +298,139 @@ int fzl_vm_run(fzl_vm_t *vm, bool allow_infinite, uint64_t max_iter) switch (opcode) { + case FZL_OPCODE_BUILD_STR: + { + uint64_t n = vm->ops[++vm->ip]; + if (vm->dtop < n) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected %zu value(s) in dstack", n); + print_err_data(vm); + return -1; + } + + char temp[64]; + size_t gi = 0; + memset(build_result, 0, sizeof(build_result)); + for (size_t i = 0; i < n; i++) + { + if (gi >= 512) + { + for (size_t i = 511; i > 508; i--) + build_result[i] = '.'; + break; + } + + memset(temp, 0, sizeof(temp)); + fzl_value_t val = vm->dstack[i]; + + switch (val.type) + { + case FZL_TYPE_UINT: + snprintf(temp, sizeof(temp), "%zu", val.as.u); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_UINT64: + snprintf(temp, sizeof(temp), "%lu", val.as.u64); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_UINT32: + snprintf(temp, sizeof(temp), "%u", val.as.u32); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_UINT16: + snprintf(temp, sizeof(temp), "%u", val.as.u16); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_UINT8: + snprintf(temp, sizeof(temp), "%u", val.as.u8); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_INT: + snprintf(temp, sizeof(temp), "%zd", val.as.i); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_INT64: + snprintf(temp, sizeof(temp), "%ld", val.as.i64); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_INT32: + snprintf(temp, sizeof(temp), "%d", val.as.i32); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_INT16: + snprintf(temp, sizeof(temp), "%d", val.as.i16); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_INT8: + snprintf(temp, sizeof(temp), "%d", val.as.i8); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_BOOL: + snprintf(build_result + gi, sizeof(build_result) - gi, "%s", + val.as.b ? "true" : "false"); + gi += val.as.b ? 4 : 5; + break; + + case FZL_TYPE_CHAR: + snprintf(build_result + gi, sizeof(build_result) - gi, "%d", + val.as.c); + gi += 1; + break; + + case FZL_TYPE_STRING: + snprintf(build_result + gi, sizeof(build_result) - gi, "%s", + val.as.s.value); + gi += val.as.s.length; + break; + + case FZL_TYPE_EXTERN_FN: + snprintf(temp, sizeof(temp), "", val.as.u); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_OBJECT: + snprintf(temp, sizeof(temp), "", val.as.obj); + strcpy(build_result + gi, temp); + gi += strlen(temp); + break; + + case FZL_TYPE_NULL: + snprintf(build_result + gi, sizeof(build_result) - gi, ""); + gi += 6; + break; + } + } + + vm->dtop -= n; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_STRING, + .as.s.length = strlen(build_result), + .as.s.value = build_result}; + break; + } + case FZL_OPCODE_DEREFERENCE: { if (vm->dtop < 1) diff --git a/src/vm/fzl_vm_debug.c b/src/vm/fzl_vm_debug.c index eea0ef2..ceb1788 100644 --- a/src/vm/fzl_vm_debug.c +++ b/src/vm/fzl_vm_debug.c @@ -20,6 +20,10 @@ void fzl_vm_print_ops(fzl_vm_t *vm) switch (vm->ops[i]) { + case FZL_OPCODE_BUILD_STR: + printf("BUILD_STR %zu\n", vm->ops[++i]); + break; + case FZL_OPCODE_DEREFERENCE: printf("DEREFERENCE\n"); break; @@ -163,9 +167,6 @@ void fzl_vm_print_ops(fzl_vm_t *vm) case FZL_OPCODE_JMP: printf("JMP IP: %ld\n", vm->ops[++i]); break; - - default: - printf("\n"); } } }