From 67d1eb2304ee911e9c85dfd0fdddd08a5720d8c1 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Wed, 13 Aug 2025 13:21:54 +0200 Subject: [PATCH] Comment support --- 3p/WL.c | 214 +++++++++++++++++++++++++++++---------- 3p/WL.h | 3 +- pages/page.wl | 1 + pages/post.wl | 249 ++++++++++++++++++++++++++++++++++++++++------ pages/post_old.wl | 73 ++++++++++++++ pages/thread.wl | 5 - src/main.c | 29 ++++-- 7 files changed, 478 insertions(+), 96 deletions(-) create mode 100644 pages/post_old.wl delete mode 100644 pages/thread.wl diff --git a/3p/WL.c b/3p/WL.c index 5387465..46a21df 100644 --- a/3p/WL.c +++ b/3p/WL.c @@ -1,6 +1,6 @@ #include -#include #include +#include #include #include #include "wl.h" @@ -273,6 +273,7 @@ typedef enum { TOKEN_OPER_DIV, TOKEN_OPER_MOD, TOKEN_OPER_ASS, + TOKEN_OPER_SHOVEL, TOKEN_PAREN_OPEN, TOKEN_PAREN_CLOSE, TOKEN_BRACKET_OPEN, @@ -319,6 +320,7 @@ typedef enum { NODE_OPER_MUL, NODE_OPER_DIV, NODE_OPER_MOD, + NODE_OPER_SHOVEL, NODE_VALUE_INT, NODE_VALUE_FLOAT, NODE_VALUE_STR, @@ -440,6 +442,7 @@ static void write_token(Writer *w, Token token) case TOKEN_OPER_MUL : write_text(w, S("*")); break; case TOKEN_OPER_DIV : write_text(w, S("/")); break; case TOKEN_OPER_MOD : write_text(w, S("%")); break; + case TOKEN_OPER_SHOVEL : write_text(w, S("<<")); break; case TOKEN_PAREN_OPEN : write_text(w, S("(")); break; case TOKEN_PAREN_CLOSE : write_text(w, S(")")); break; case TOKEN_BRACKET_OPEN : write_text(w, S("[")); break; @@ -677,6 +680,7 @@ static Token next_token(Parser *p) return (Token) { .type=TOKEN_VALUE_STR, .sval=(String) { .ptr=buf, .len=len } }; } + if (consume_str(&p->s, S("<<"))) return (Token) { .type=TOKEN_OPER_SHOVEL }; if (consume_str(&p->s, S("=="))) return (Token) { .type=TOKEN_OPER_EQL }; if (consume_str(&p->s, S("!="))) return (Token) { .type=TOKEN_OPER_NQL }; if (consume_str(&p->s, S("<"))) return (Token) { .type=TOKEN_OPER_LSS }; @@ -1016,6 +1020,11 @@ static int precedence(Token t, int flags) case TOKEN_OPER_ASS: return 1; + case TOKEN_OPER_SHOVEL: + if (flags & IGNORE_LSS) + return -1; + return 1; + case TOKEN_OPER_EQL: case TOKEN_OPER_NQL: return 2; @@ -1435,7 +1444,8 @@ static Node *parse_expr_inner(Parser *p, Node *left, int min_prec, int flags) case TOKEN_OPER_MUL: parent->type = NODE_OPER_MUL; break; case TOKEN_OPER_DIV: parent->type = NODE_OPER_DIV; break; case TOKEN_OPER_MOD: parent->type = NODE_OPER_MOD; break; - default: + case TOKEN_OPER_SHOVEL: parent->type = NODE_OPER_SHOVEL; break; + default: parser_report(p, "Operator not implemented"); return NULL; } @@ -2485,6 +2495,8 @@ static void cg_pop_scope(Codegen *cg) ASSERT(cg->num_scopes > 0); Scope *scope = &cg->scopes[cg->num_scopes-1]; + ASSERT(scope->type == SCOPE_PROC || scope->type == SCOPE_GLOBAL || scope->max_vars == 0); + Scope *parent_scope = NULL; if (cg->num_scopes > 1) parent_scope = &cg->scopes[cg->num_scopes-2]; @@ -2494,6 +2506,8 @@ static void cg_pop_scope(Codegen *cg) UnpatchedCall *call = scope->calls; scope->calls = call->next; + ASSERT(call - cg->calls >= 0 && call - cg->calls < MAX_UNPATCHED_CALLS); + Symbol *sym = cg_find_symbol(cg, call->name, true); if (sym == NULL) { @@ -2514,6 +2528,12 @@ static void cg_pop_scope(Codegen *cg) } cg_patch_u32(cg, call->off, sym->off); + + call->next = cg->free_list_calls; + cg->free_list_calls = call; + + // TODO: remove + ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS)); } cg->num_syms = scope->idx_syms; @@ -2531,9 +2551,13 @@ static void cg_append_unpatched_call(Codegen *cg, String name, int p) UnpatchedCall *call = cg->free_list_calls; cg->free_list_calls = call->next; - call->name = name; - call->off = p; + ASSERT(call - cg->calls >= 0 && call - cg->calls < MAX_UNPATCHED_CALLS); + call->name = name; + call->off = p; + call->next = NULL; + + ASSERT(cg->num_scopes > 0); Scope *scope = &cg->scopes[cg->num_scopes-1]; call->next = scope->calls; @@ -2582,6 +2606,9 @@ static void walk_node(Codegen *cg, Node *node); static void walk_expr_node(Codegen *cg, Node *node, bool one) { + // TODO: remove + ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS)); + switch (node->type) { case NODE_NESTED: @@ -2655,6 +2682,23 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one) } break; + case NODE_OPER_SHOVEL: + { + Node *dst = node->left; + Node *src = node->right; + + walk_expr_node(cg, node->left, true); + + cg_push_scope(cg, SCOPE_ASSIGNMENT); + walk_expr_node(cg, node->right, true); + cg_pop_scope(cg); + + cg_write_opcode(cg, OPCODE_APPEND); + if (!one) + cg_write_opcode(cg, OPCODE_POP); + } + break; + case NODE_OPER_EQL: walk_expr_node(cg, node->left, true); walk_expr_node(cg, node->right, true); @@ -2871,6 +2915,9 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one) static void walk_node(Codegen *cg, Node *node) { + // TODO: remove + ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS)); + switch (node->type) { case NODE_GLOBAL: @@ -2919,7 +2966,7 @@ static void walk_node(Codegen *cg, Node *node) walk_node(cg, node->proc_body); cg_write_opcode(cg, OPCODE_RET); - cg_patch_u8 (cg, off2, count_function_vars(cg)); + cg_patch_u8 (cg, off2, cg->scopes[cg->num_scopes-1].max_vars); cg_patch_u32(cg, off0, cg_current_offset(cg)); cg_pop_scope(cg); @@ -3081,12 +3128,22 @@ static void walk_node(Codegen *cg, Node *node) } } -static Codegen init_codegen(char *codebuf, int codecap, - char *databuf, int datacap, char *errmsg, int errcap) +#define WL_MAGIC 0xFEEDBEEF + +static int codegen(Node *node, char *dst, int cap, char *errmsg, int errcap) { - Codegen c = { - .code = { codebuf, codecap, 0 }, - .data = { databuf, datacap, 0 }, + char *hdr; + if (cap < SIZEOF(uint32_t) * 3) + hdr = NULL; + else { + hdr = dst; + dst += SIZEOF(uint32_t) * 3; + cap -= SIZEOF(uint32_t) * 3; + } + + Codegen cg = { + .code = { dst, cap/2, 0 }, + .data = { dst + cap/2, cap/2, 0 }, .num_scopes = 0, .err = false, .errmsg = errmsg, @@ -3094,28 +3151,10 @@ static Codegen init_codegen(char *codebuf, int codecap, .data_off = -1, }; - c.free_list_calls = c.calls; + cg.free_list_calls = cg.calls; for (int i = 0; i < MAX_UNPATCHED_CALLS-1; i++) - c.calls[i].next = &c.calls[i+1]; - c.calls[MAX_UNPATCHED_CALLS-1].next = NULL; - - return c; -} - -#define WL_MAGIC 0xFEEDBEEF - -static int codegen(Node *node, char *dst, int cap, char *errmsg, int errcap) -{ - char *hdr; - if (cap < sizeof(uint32_t) * 3) - hdr = NULL; - else { - hdr = dst; - dst += sizeof(uint32_t) * 3; - cap -= sizeof(uint32_t) * 3; - } - - Codegen cg = init_codegen(dst, cap/2, dst + cap/2, cap/2, errmsg, errcap); + cg.calls[i].next = &cg.calls[i+1]; + cg.calls[MAX_UNPATCHED_CALLS-1].next = NULL; cg_push_scope(&cg, SCOPE_GLOBAL); cg_write_opcode(&cg, OPCODE_VARS); @@ -3141,7 +3180,7 @@ static int codegen(Node *node, char *dst, int cap, char *errmsg, int errcap) memmove(dst + cg.code.len, dst + cap/2, cg.data.len); } - return cg.code.len + cg.data.len + sizeof(uint32_t) * 3; + return cg.code.len + cg.data.len + SIZEOF(uint32_t) * 3; } static int write_instr(Writer *w, char *src, int len, String data) @@ -3392,12 +3431,17 @@ static int write_instr(Writer *w, char *src, int len, String data) case OPCODE_SELECT: write_text(w, S("SELECT\n")); return 1; + + default: + write_text(w, S("byte ")); + write_text_s64(w, src[0]); + return 1; } return -1; } -int wl_dump_program(WL_Program program, char *dst, int cap) +static int write_program(WL_Program program, char *dst, int cap) { if (program.len < 3 * sizeof(uint32_t)) return -1; @@ -3433,6 +3477,30 @@ int wl_dump_program(WL_Program program, char *dst, int cap) return w.len; } +void wl_dump_program(WL_Program program) +{ + char buf[1<<10]; + int len = write_program(program, buf, SIZEOF(buf)); + + if (len < 0) { + printf("Invalid program\n"); + return; + } + + if (len > SIZEOF(buf)) { + char *p = malloc(len+1); + if (p == NULL) { + printf("Out of memory\n"); + return; + } + write_program(program, p, len); + p[len] = '\0'; + fwrite(p, 1, len, stdout); + } else { + fwrite(buf, 1, len, stdout); + } +} + ///////////////////////////////////////////////////////////////////////// // COMPILER ///////////////////////////////////////////////////////////////////////// @@ -3969,17 +4037,19 @@ static Value value_select(Value set, Value key, Error *err) return VALUE_ERROR; } - char keybuf[1<<8]; - int keylen = value_convert_to_str(key, keybuf, SIZEOF(keybuf)); - keylen = MIN(keylen, SIZEOF(keybuf)-1); - keybuf[keylen] = '\0'; + char key_buf[1<<8]; + int key_len = value_convert_to_str(key, key_buf, SIZEOF(key_buf)); + if (key_len > SIZEOF(key_buf)-1) + key_len = SIZEOF(key_buf)-1; + key_buf[key_len] = '\0'; - char setbuf[1<<8]; - int setlen = value_convert_to_str(set, setbuf, SIZEOF(setbuf)); - setlen = MIN(setlen, SIZEOF(setbuf)-1); - setbuf[setlen] = '\0'; + char set_buf[1<<8]; + int set_len = value_convert_to_str(set, set_buf, SIZEOF(set_buf)); + if (set_len > SIZEOF(set_buf)-1) + set_len = SIZEOF(set_buf)-1; + set_buf[set_len] = '\0'; - REPORT(err, "Invalid key '%s' used in access to map '%s'", keybuf, setbuf); + REPORT(err, "Invalid key '%s' used in access to map '%s'", key_buf, set_buf); return VALUE_ERROR; } @@ -4680,6 +4750,21 @@ static void rt_pop_group(WL_Runtime *rt) rt->stack = rt->groups[--rt->num_groups]; } +static void value_print(Value v) +{ + char buf[1<<8]; + int len = value_convert_to_str(v, buf, SIZEOF(buf)); + if (len < SIZEOF(buf)) + fwrite(buf, 1, len, stdout); + else { + len = SIZEOF(buf)-1; + fwrite(buf, 1, len, stdout); + fprintf(stdout, " [...]"); + } + putc('\n', stdout); + fflush(stdout); +} + static void step(WL_Runtime *rt) { switch (rt_read_u8(rt)) { @@ -4872,14 +4957,6 @@ static void step(WL_Runtime *rt) v1 = rt->values[rt->stack-1]; t = value_type(v1); if (t != TYPE_ARRAY && t != TYPE_MAP) { - { - char buf[1<<9]; - int len = value_convert_to_str(v1, buf, SIZEOF(buf)); - if (len > SIZEOF(buf)-1) - len = SIZEOF(buf)-1; - buf[len] = '\0'; - printf("len on (%s) type %d\n", buf, value_type(v1)); // TODO - } REPORT(&rt->err, "Invalid operation 'len' on non-aggregate value"); rt->state = RUNTIME_ERROR; break; @@ -5025,7 +5102,7 @@ WL_EvalResult wl_runtime_eval(WL_Runtime *rt) case RUNTIME_SYSVAR: { ASSERT(rt->stack >= rt->stack_before_user); - + int pushed_by_user = rt->stack - rt->stack_before_user; if (pushed_by_user > 1) { REPORT(&rt->err, "Invalid API usage"); @@ -5090,11 +5167,17 @@ WL_EvalResult wl_runtime_eval(WL_Runtime *rt) else { int len = value_convert_to_str(v, rt->buf, SIZEOF(rt->buf)); if (len > SIZEOF(rt->buf)) { - char *ptr = alloc(rt->arena, len, 1); - len = value_convert_to_str(v, ptr, len); - str = (String) { ptr, len }; - } else + char *p = alloc(rt->arena, len, 1); + if (p == NULL) { + REPORT(&rt->err, "Out of memory"); + rt->state = RUNTIME_ERROR; + return (WL_EvalResult) { .type=WL_EVAL_ERROR }; + } + len = value_convert_to_str(v, p, len); + str = (String) { p, len }; + } else { str = (String) { rt->buf, len }; + } } rt->cur_output++; @@ -5531,3 +5614,24 @@ void wl_append(WL_Runtime *rt) return; } } + +void wl_runtime_dump(WL_Runtime *rt) +{ + for (int i = 0; i < rt->num_frames; i++) { + printf("=== frame %d ===\n", i); + + Frame *frame = &rt->frames[i]; + + int num_vars; + if (i+1 < rt->num_frames) + num_vars = frame->varbase - rt->frames[i+1].varbase; + else + num_vars = frame->varbase - rt->vars; + + for (int j = 0; j < num_vars; j++) { + printf(" %d = ", j); + value_print(rt->values[frame->varbase - j]); + } + } + printf("===============\n"); +} diff --git a/3p/WL.h b/3p/WL.h index 550b736..d2657eb 100644 --- a/3p/WL.h +++ b/3p/WL.h @@ -49,11 +49,12 @@ WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String content); int wl_compiler_link (WL_Compiler *compiler, WL_Program *program); WL_String wl_compiler_error (WL_Compiler *compiler); int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap); -int wl_dump_program (WL_Program program, char *dst, int cap); +void wl_dump_program (WL_Program program); WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program); WL_EvalResult wl_runtime_eval (WL_Runtime *rt); WL_String wl_runtime_error (WL_Runtime *rt); +void wl_runtime_dump (WL_Runtime *rt); bool wl_streq (WL_String a, char *b, int blen); int wl_arg_count (WL_Runtime *rt); diff --git a/pages/page.wl b/pages/page.wl index d709458..fee84e9 100644 --- a/pages/page.wl +++ b/pages/page.wl @@ -2,6 +2,7 @@ procedure page(title, login_user_id, style, main) + CN - \title let main =
-

\post.title

-

\post.content

-
- \if $login_user_id != none: -
- - - -
+
+
+ \post.title +
+
+ submitted 3 hours ago by \post.username | \len comments +
+
+ \post.content +
+
+ + reply + +
+
+ + + +
+
+
- \if len comments == 0: - No comments yet! - else for comment in comments: -
- \comment.username -

- \comment.content -

-
+ + \procedure render_comment(comment) +
+
+ + +
+
+
+ \comment.username 2 hours ago +
+
+ \comment.content +
\if $login_user_id != none: -
- - - - -
+
+ + reply + +
+
+ + + + +
+
+
+
+
+ \for child in comment.child: + render_comment(child)
+ + \if len root_comments == 0: + (No comments) + else for comment in root_comments: + render_comment(comment)
page(post.title, $login_user_id, style, main) \ No newline at end of file diff --git a/pages/post_old.wl b/pages/post_old.wl new file mode 100644 index 0000000..6984736 --- /dev/null +++ b/pages/post_old.wl @@ -0,0 +1,73 @@ +include "pages/page.wl" + +let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id) +let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id) + +let lookup = {} + +for comment in comments: { + comment.child = [] + lookup[comment.id] = comment +} + +let roots = [] + +for comment in comments: { + if comment.parent_comment == none: + roots << comment + else + lookup[comment.parent_comment].child << comment +} + +comments = roots + +let post = posts[0] + +let style = + + +procedure render_comment(parent) +
+ \parent.username +

+ \parent.content +

+
+ \if $login_user_id != none: +
+ + + + +
+ \for child in parent.child: + render_comment(child) +
+
+ +let main = +
+

\post.title

+

\post.content

+
+
+ + + +
+
+ \if len comments == 0: + No comments yet! + else for comment in comments: + render_comment(comment) +
+ +page(post.title, $login_user_id, style, main) \ No newline at end of file diff --git a/pages/thread.wl b/pages/thread.wl deleted file mode 100644 index 65a8fe4..0000000 --- a/pages/thread.wl +++ /dev/null @@ -1,5 +0,0 @@ -
    -
  • 1. Post A
  • -
  • 2. Post B
  • -
  • 3. Post C
  • -
diff --git a/src/main.c b/src/main.c index 13bffc2..186efbe 100644 --- a/src/main.c +++ b/src/main.c @@ -876,6 +876,7 @@ int main(void) } HTTP_String parent_post_str = http_getparam(req->body, HTTP_STR("parent_post"), &arena); + HTTP_String parent_comment_str = http_getparam(req->body, HTTP_STR("parent_comment"), &arena); HTTP_String content = http_getparam(req->body, HTTP_STR("content"), &arena); int parent_post; @@ -893,6 +894,21 @@ int main(void) } } + int parent_comment; + { + char buf[32]; + if (parent_comment_str.len >= (int) sizeof(buf)) + parent_comment = -1; + else { + memcpy(buf, parent_comment_str.ptr, parent_comment_str.len); + buf[parent_comment_str.len] = '\0'; + + parent_comment = atoi(buf); + if (parent_comment == 0) + parent_comment = -1; + } + } + content = http_trim(content); if (!valid_comment_content(content)) { assert(0); // TODO @@ -902,8 +918,12 @@ int main(void) assert(0); // TODO } + int ret; sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post); + if (parent_comment == -1) + ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post); + else + ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment); if (ret != SQLITE_OK) { assert(0); // TODO } @@ -1009,13 +1029,10 @@ int main(void) assert(0); // TODO } - if (num == 0) { - printf("post with id %d does not exist\n", post_id); // TODO + if (num == 0) evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1); - } else { - printf("Evaluating template\n"); + else evaluate_template_2(builder, arena, "pages/post.wl", user_id, post_id); - } } else {