Comment support
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#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,6 +1444,7 @@ 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;
|
||||
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;
|
||||
|
||||
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;
|
||||
@@ -5090,12 +5167,18 @@ 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++;
|
||||
return (WL_EvalResult) { .type=WL_EVAL_OUTPUT, .str={ str.ptr, str.len } };
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
procedure page(title, login_user_id, style, main)
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>CN - \title</title>
|
||||
<style>
|
||||
body {
|
||||
|
||||
+210
-19
@@ -1,51 +1,242 @@
|
||||
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 FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
|
||||
let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", $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 root_comments = []
|
||||
|
||||
for comment in comments: {
|
||||
if comment.parent_comment == none:
|
||||
root_comments << comment
|
||||
else
|
||||
lookup[comment.parent_comment].child << comment
|
||||
}
|
||||
|
||||
let post = posts[0]
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.child {
|
||||
border-left: 3px solid #ccc;
|
||||
.thread-header {
|
||||
padding: 15px 0;
|
||||
border-bottom: 2px solid #E8D4A9;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.thread-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.thread-title a {
|
||||
color: #1D2B42;
|
||||
text-decoration: none;
|
||||
}
|
||||
.thread-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.thread-meta {
|
||||
font-size: 12px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.thread-meta a {
|
||||
color: #7A5F2A;
|
||||
}
|
||||
.thread-text {
|
||||
font-size: 14px;
|
||||
color: #1D2B42;
|
||||
line-height: 160%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.thread-actions {
|
||||
font-size: 12px;
|
||||
}
|
||||
.thread-actions a {
|
||||
color: #7A5F2A;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Comment styles */
|
||||
.comment {
|
||||
margin-bottom: 15px;
|
||||
border-left: 1px solid #E8D4A9;
|
||||
padding-left: 10px;
|
||||
}
|
||||
form textarea {
|
||||
|
||||
.comment-meta {
|
||||
font-size: 11px;
|
||||
color: #7A5F2A;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.comment-meta a {
|
||||
color: #7A5F2A;
|
||||
}
|
||||
.comment-text {
|
||||
font-size: 13px;
|
||||
color: #1D2B42;
|
||||
line-height: 150%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.comment-actions {
|
||||
font-size: 11px;
|
||||
}
|
||||
.comment-actions a {
|
||||
color: #7A5F2A;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.comment-actions a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
|
||||
.vote-buttons {
|
||||
float: left;
|
||||
width: 15px;
|
||||
margin-right: 8px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.vote-buttons a {
|
||||
display: block;
|
||||
color: #7A5F2A;
|
||||
text-decoration: none;
|
||||
line-height: 100%;
|
||||
}
|
||||
.vote-buttons a:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
.comment-content {
|
||||
margin-left: 23px;
|
||||
}
|
||||
.comment-child {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.add-comment {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #E8D4A9;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #D4C298;
|
||||
}
|
||||
.add-comment form {
|
||||
margin: 0;
|
||||
}
|
||||
.add-comment form textarea {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
background: #F7E6C0;
|
||||
border: 1px solid #D4C298;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
.add-comment form input[type=submit] {
|
||||
background: #5780C9;
|
||||
color: #F7E6C0;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.add-comment input[type=submit]:hover {
|
||||
background: #1D2B42;
|
||||
}
|
||||
summary {
|
||||
list-style: none;
|
||||
text-decoration: underline;
|
||||
color: #7A5F2A;
|
||||
cursor: pointer;
|
||||
}
|
||||
::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
color: #7A5F2A;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.collapsed:hover {
|
||||
color: #1D2B42;
|
||||
}
|
||||
</style>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<h3>\post.title</h3>
|
||||
<p>\post.content</p>
|
||||
<div>
|
||||
\if $login_user_id != none:
|
||||
<div class="thread-header">
|
||||
<div class="thread-title">
|
||||
<span>\post.title</span>
|
||||
</div>
|
||||
<div class="thread-meta">
|
||||
submitted 3 hours ago by <a href="">\post.username</a> | <a href="">\len comments</a>
|
||||
</div>
|
||||
<div class="thread-text">
|
||||
\post.content
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
\if len comments == 0:
|
||||
<span>No comments yet!</span>
|
||||
else for comment in comments:
|
||||
<div>
|
||||
<a href="">\comment.username</a>
|
||||
<p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
\procedure render_comment(comment)
|
||||
<div class="comment">
|
||||
<div class="vote-buttons">
|
||||
<a href="">▲</a>
|
||||
<a href="">▼</a>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-meta">
|
||||
<a href="">\comment.username</a> 2 hours ago
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
\comment.content
|
||||
</p>
|
||||
<div class="child">
|
||||
</div>
|
||||
\if $login_user_id != none:
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="comment-child">
|
||||
\for child in comment.child:
|
||||
render_comment(child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
\if len root_comments == 0:
|
||||
<span>(No comments)</span>
|
||||
else for comment in root_comments:
|
||||
render_comment(comment)
|
||||
</main>
|
||||
|
||||
page(post.title, $login_user_id, style, main)
|
||||
@@ -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 =
|
||||
<style>
|
||||
.child {
|
||||
border-left: 3px solid #ccc;
|
||||
padding-left: 10px;
|
||||
}
|
||||
form textarea {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
procedure render_comment(parent)
|
||||
<div>
|
||||
<a href="">\parent.username</a>
|
||||
<p>
|
||||
\parent.content
|
||||
</p>
|
||||
<div class="child">
|
||||
\if $login_user_id != none:
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
\for child in parent.child:
|
||||
render_comment(child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<h3>\post.title</h3>
|
||||
<p>\post.content</p>
|
||||
<div>
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
\if len comments == 0:
|
||||
<span>No comments yet!</span>
|
||||
else for comment in comments:
|
||||
render_comment(comment)
|
||||
</main>
|
||||
|
||||
page(post.title, $login_user_id, style, main)
|
||||
@@ -1,5 +0,0 @@
|
||||
<ul>
|
||||
<li>1. Post A</li>
|
||||
<li>2. Post B</li>
|
||||
<li>3. Post C</li>
|
||||
</ul>
|
||||
+23
-6
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user