diff --git a/.gitignore b/.gitignore index 0393442..497d75c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.o -*.exe \ No newline at end of file +*.exe +*.out + diff --git a/WL.c b/3p/WL.c similarity index 98% rename from WL.c rename to 3p/WL.c index 7441463..21e8635 100644 --- a/WL.c +++ b/3p/WL.c @@ -351,6 +351,7 @@ typedef enum { NODE_INCLUDE, NODE_SELECT, NODE_NESTED, + NODE_OPER_LEN, NODE_OPER_POS, NODE_OPER_NEG, NODE_OPER_ASS, @@ -459,6 +460,7 @@ typedef enum { TOKEN_KWORD_TRUE, TOKEN_KWORD_FALSE, TOKEN_KWORD_INCLUDE, + TOKEN_KWORD_LEN, TOKEN_VALUE_FLOAT, TOKEN_VALUE_INT, TOKEN_VALUE_STR, @@ -550,6 +552,7 @@ String tok2str(Token token, char *buf, int max) case TOKEN_KWORD_TRUE: return S("true"); case TOKEN_KWORD_FALSE: return S("false"); case TOKEN_KWORD_INCLUDE: return S("include"); + case TOKEN_KWORD_LEN: return S("len"); case TOKEN_VALUE_FLOAT: { @@ -676,18 +679,19 @@ Token next_token(Parser *p) p->s.cur - start }; - if (streq(kword, S("if"))) return (Token) { .type=TOKEN_KWORD_IF }; - if (streq(kword, S("else"))) return (Token) { .type=TOKEN_KWORD_ELSE }; - if (streq(kword, S("while"))) return (Token) { .type=TOKEN_KWORD_WHILE }; - if (streq(kword, S("for"))) return (Token) { .type=TOKEN_KWORD_FOR }; - if (streq(kword, S("in"))) return (Token) { .type=TOKEN_KWORD_IN }; - if (streq(kword, S("fun"))) return (Token) { .type=TOKEN_KWORD_FUN }; - if (streq(kword, S("let"))) return (Token) { .type=TOKEN_KWORD_LET }; - if (streq(kword, S("print"))) return (Token) { .type=TOKEN_KWORD_PRINT }; - if (streq(kword, S("none"))) return (Token) { .type=TOKEN_KWORD_NONE }; - if (streq(kword, S("true"))) return (Token) { .type=TOKEN_KWORD_TRUE }; - if (streq(kword, S("false"))) return (Token) { .type=TOKEN_KWORD_FALSE }; + if (streq(kword, S("if"))) return (Token) { .type=TOKEN_KWORD_IF }; + if (streq(kword, S("else"))) return (Token) { .type=TOKEN_KWORD_ELSE }; + if (streq(kword, S("while"))) return (Token) { .type=TOKEN_KWORD_WHILE }; + if (streq(kword, S("for"))) return (Token) { .type=TOKEN_KWORD_FOR }; + if (streq(kword, S("in"))) return (Token) { .type=TOKEN_KWORD_IN }; + if (streq(kword, S("fun"))) return (Token) { .type=TOKEN_KWORD_FUN }; + if (streq(kword, S("let"))) return (Token) { .type=TOKEN_KWORD_LET }; + if (streq(kword, S("print"))) return (Token) { .type=TOKEN_KWORD_PRINT }; + if (streq(kword, S("none"))) return (Token) { .type=TOKEN_KWORD_NONE }; + if (streq(kword, S("true"))) return (Token) { .type=TOKEN_KWORD_TRUE }; + if (streq(kword, S("false"))) return (Token) { .type=TOKEN_KWORD_FALSE }; if (streq(kword, S("include"))) return (Token) { .type=TOKEN_KWORD_INCLUDE }; + if (streq(kword, S("len"))) return (Token) { .type=TOKEN_KWORD_LEN }; return (Token) { .type=TOKEN_IDENT, .sval=kword }; } @@ -1249,6 +1253,23 @@ Node *parse_atom(Parser *p) } break; + case TOKEN_KWORD_LEN: + { + Node *child = parse_atom(p); + if (child == NULL) + return NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_OPER_LEN; + parent->left = child; + + ret = parent; + } + break; + case TOKEN_IDENT: { Node *node = alloc_node(p); @@ -2046,6 +2067,12 @@ void print_node(Node *node) } break; + case NODE_OPER_LEN: + printf("len("); + print_node(node->left); + printf(")"); + break; + case NODE_OPER_POS: printf("("); printf("+"); @@ -2420,6 +2447,7 @@ enum { OPCODE_FOR = 0x2E, OPCODE_PUSHT = 0x2F, OPCODE_PUSHFL = 0x30, + OPCODE_LEN = 0x31, }; typedef struct { @@ -2757,6 +2785,7 @@ bool is_expr(Node *node) case NODE_SELECT: case NODE_NESTED: case NODE_FUNC_CALL: + case NODE_OPER_LEN: case NODE_OPER_POS: case NODE_OPER_NEG: case NODE_OPER_ASS: @@ -3002,6 +3031,20 @@ void assemble_expr(Assembler *a, Node *node, int num_results) } break; + case NODE_OPER_LEN: + assemble_expr(a, node->left, 1); + append_u8(&a->out, OPCODE_LEN); + + if (num_results == 0) + append_u8(&a->out, OPCODE_POP); + else if (num_results != -1 && num_results != 1) { + append_u8(&a->out, OPCODE_GROUP); + append_u8(&a->out, OPCODE_GTRUNC); + append_u32(&a->out, num_results-1); + append_u8(&a->out, OPCODE_GCOALESCE); + } + break; + case NODE_OPER_POS: assemble_expr(a, node->left, num_results); break; @@ -5574,6 +5617,24 @@ int step(WL_State *state) } break; + case OPCODE_LEN: + { + Value set = state->eval_stack[state->eval_depth-1]; + + Type type = type_of(set); + if (type != TYPE_ARRAY && type != TYPE_MAP) { + assert(0); // TODO + } + + Value len = make_int(state->a, value_length(set)); + if (len == VALUE_ERROR) { + assert(0); // TODO + } + + state->eval_stack[state->eval_depth++] = len; + } + break; + default: eval_report(state, "Invalid opcode (offset %d)", state->off-1); return -1; diff --git a/WL.h b/3p/WL.h similarity index 100% rename from WL.h rename to 3p/WL.h diff --git a/chttp.c b/3p/chttp.c similarity index 100% rename from chttp.c rename to 3p/chttp.c diff --git a/chttp.h b/3p/chttp.h similarity index 100% rename from chttp.h rename to 3p/chttp.h diff --git a/sqlite3.c b/3p/sqlite3.c similarity index 100% rename from sqlite3.c rename to 3p/sqlite3.c diff --git a/sqlite3.h b/3p/sqlite3.h similarity index 100% rename from sqlite3.h rename to 3p/sqlite3.h diff --git a/sqlite3ext.h b/3p/sqlite3ext.h similarity index 100% rename from sqlite3ext.h rename to 3p/sqlite3ext.h diff --git a/Makefile b/Makefile index 7cd9bd6..fa67d14 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,23 @@ +ifeq ($(OS),Windows_NT) + EXT = .exe + LFLAGS = -lws2_32 +else + EXT = .out + LFLAGS = +endif -all: sqlite3.o - gcc main.c variadic.c sqlite3utils.c chttp.c WL.c sqlite3.o -o main -Wall -Wextra -g3 -O0 -lws2_32 +CFLAGS = -Wall -Wextra -O0 -g3 -I3p -sqlite3.o: - gcc -c sqlite3.c -o sqlite3.o +HFILES = $(shell find src 3p -name "*.h") +CFILES = $(filter-out 3p/sqlite3.c, $(shell find src 3p -name "*.c")) + +all: cozisnews$(EXT) + +cozisnews$(EXT): $(CFILES) $(HFILES) sqlite3.o + gcc -o $@ $(CFILES) sqlite3.o $(CFLAGS) $(LFLAGS) + +sqlite3.o: 3p/sqlite3.c + gcc -o $@ -c $< + +clean: + rm *.o *.out *.exe \ No newline at end of file diff --git a/file.c b/file.c deleted file mode 100644 index 048f026..0000000 --- a/file.c +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef WL_AMALGAMATION -#include "includes.h" -#include "file.h" -#endif - -int file_open(String path, File *handle, int *size) -{ - char zt[1<<10]; - if (path.len >= COUNT(zt)) - return -1; - memcpy(zt, path.ptr, path.len); - zt[path.len] = '\0'; - -#ifdef _WIN32 - *handle = CreateFileA( - zt, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL - ); - if (*handle == INVALID_HANDLE_VALUE) { - DWORD error = GetLastError(); - if (error == ERROR_FILE_NOT_FOUND || - error == ERROR_ACCESS_DENIED) - return 1; - return -1; - } - LARGE_INTEGER fileSize; - if (!GetFileSizeEx(*handle, &fileSize)) { - CloseHandle(*handle); - return -1; - } - if (fileSize.QuadPart > INT_MAX) { - CloseHandle(*handle); - return -1; - } - *size = (int) fileSize.QuadPart; -#else - *handle = open(zt, O_RDONLY); - if (*handle < 0) { - if (errno == ENOENT) - return 1; - return -1; - } - struct stat info; - if (fstat(*handle, &info) < 0) { - close(*handle); - return -1; - } - if (S_ISDIR(info.st_mode)) { - close(*handle); - return 1; - } - if (info.st_size > INT_MAX) { - close(*handle); - return -1; - } - *size = (int) info.st_size; -#endif - return 0; -} - -void file_close(File file) -{ -#ifdef _WIN32 - CloseHandle(file); -#else - close(file); -#endif -} - -int file_read(File file, char *dst, int max) -{ -#ifdef _WIN32 - DWORD num; - BOOL ok = ReadFile(file, dst, max, &num, NULL); - if (!ok) - return -1; - return (int) num; -#else - return read(file, dst, max); -#endif -} - -int file_read_all(String path, String *dst) -{ - int len; - File handle; - if (file_open(path, &handle, &len) < 0) - return -1; - - char *ptr = malloc(len+1); - if (ptr == NULL) { - file_close(handle); - return -1; - } - - for (int copied = 0; copied < len; ) { - int ret = file_read(handle, ptr + copied, len - copied); - if (ret <= 0) { - free(ptr); - file_close(handle); - return -1; - } - copied += ret; - } - - *dst = (String) { ptr, len }; - file_close(handle); - return 0; -} diff --git a/file.h b/file.h deleted file mode 100644 index 3663dc5..0000000 --- a/file.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef WL_FILE_INCLUDED -#define WL_FILE_INCLUDED - -#ifndef WL_AMALGAMATION -#include "includes.h" -#include "basic.h" -#endif - -#ifdef _WIN32 -typedef HANDLE File; -#else -typedef int File; -#endif - -int file_open(String path, File *handle, int *size); -void file_close(File file); -int file_read(File file, char *dst, int max); -int file_read_all(String path, String *dst); - -#endif // WL_FILE_INCLUDED \ No newline at end of file diff --git a/main b/main deleted file mode 100644 index 3ea06be..0000000 Binary files a/main and /dev/null differ diff --git a/schema.sql b/misc/schema.sql similarity index 95% rename from schema.sql rename to misc/schema.sql index c613b09..1cdbda7 100644 --- a/schema.sql +++ b/misc/schema.sql @@ -9,6 +9,7 @@ CREATE TABLE IF NOT EXISTS Users ( CREATE TABLE IF NOT EXISTS Posts ( id INTEGER PRIMARY KEY, author INTEGER NOT NULL, + title TEXT NOT NULL, is_link BOOLEAN NOT NULL, content TEXT NOT NULL, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, diff --git a/shell.c b/misc/shell.c similarity index 100% rename from shell.c rename to misc/shell.c diff --git a/pages/index.wl b/pages/index.wl index ac42cc8..ecd3b8f 100644 --- a/pages/index.wl +++ b/pages/index.wl @@ -1,10 +1,9 @@ include "pages/page.wl" -let posts = [ - { title: "Show HN: Kitten TTS - 25MB CPU-Only, Open-Source TTS Model", date: "3 hours ago", num_comments: 127, link: "https://github.com/KittenML/KittenTTS" }, - { title: "Open models by OpenAI", date: "15 hours ago", num_comments: 651, link: "https://openai.com/open-models/" }, - { title: "Anthropic rejects the main developer of the library they use", date: "40 minutes ago", num_comments: 437, link: "https://grell.dev/blog/ai_rejection" } -] +let posts = $query("SELECT id, title, is_link, content, 0 as num_comments, CURRENT_TIMESTAMP as date FROM Posts") + +if posts == none: + posts = [] let style = + +let main = +
+

\post.title

+

\post.content

+
+ +page(post.title, $login_user_id, style, main) \ No newline at end of file diff --git a/pages/write.wl b/pages/write.wl index 7fa7ac4..c3ad323 100644 --- a/pages/write.wl +++ b/pages/write.wl @@ -2,15 +2,92 @@ include "pages/page.wl" let style = let main =
-
- - - + + + +
+ + +
+ + + + + +
+ +
-page("Write Post", $login_user_id, style, main) +page("Write Post", $login_user_id, style, main) \ No newline at end of file diff --git a/main.c b/src/main.c similarity index 85% rename from main.c rename to src/main.c index cc988f6..ba9bc3f 100644 --- a/main.c +++ b/src/main.c @@ -161,7 +161,7 @@ static int query_routine(WL_State *state) int evaluate_template(HTTP_ResponseBuilder builder, WL_Program program, WL_Arena arena, - char *err, int errmax, int user_id) + char *err, int errmax, int user_id, int post_id) { //WL_dump_program(program); @@ -186,10 +186,18 @@ int evaluate_template(HTTP_ResponseBuilder builder, case WL_VAR: if (WL_streq(result.str, "login_user_id", -1)) { + if (user_id < 0) WL_pushnone(state); else WL_pushint(state, user_id); + + } else if (WL_streq(result.str, "post_id", -1)) { + + if (post_id < 0) + WL_pushnone(state); + else + WL_pushint(state, post_id); } break; @@ -209,7 +217,7 @@ int evaluate_template(HTTP_ResponseBuilder builder, return 0; } -void evaluate_template_2(HTTP_ResponseBuilder builder, WL_Arena arena, char *file, int user_id) +void evaluate_template_2(HTTP_ResponseBuilder builder, WL_Arena arena, char *file, int user_id, int post_id) { http_response_builder_status(builder, 200); http_response_builder_header(builder, HTTP_STR("Content-Type: text/html")); @@ -284,7 +292,7 @@ void evaluate_template_2(HTTP_ResponseBuilder builder, WL_Arena arena, char *fil WL_Program program = result.program; char err[1<<9]; - if (evaluate_template(builder, program, arena, err, (int) sizeof(err), user_id) < 0) { + if (evaluate_template(builder, program, arena, err, (int) sizeof(err), user_id, post_id) < 0) { http_response_builder_undo(builder); http_response_builder_status(builder, 500); http_response_builder_body(builder, (HTTP_String) { err, (int) strlen(err) }); @@ -531,6 +539,12 @@ bool valid_post_content(HTTP_String str) return true; } +bool valid_link(HTTP_String str) +{ + (void) str; + return true; +} + int main(void) { http_global_init(); @@ -546,7 +560,7 @@ int main(void) char *schema_data; long schema_size; - ret = load_file("schema.sql", &schema_data, &schema_size); + ret = load_file("misc/schema.sql", &schema_data, &schema_size); if (ret < 0) { printf("Couldn't load schema\n"); return -1; @@ -738,28 +752,75 @@ int main(void) } else if (http_streq(path, HTTP_STR("/api/post"))) { if (req->method != HTTP_METHOD_POST) { - assert(0); // TODO + http_response_builder_status(builder, 405); + http_response_builder_done(builder); + continue; } if (user_id == -1) { - assert(0); // TODO + http_response_builder_status(builder, 303); + http_response_builder_header(builder, HTTP_STR("Location: /index")); + http_response_builder_done(builder); + continue; } HTTP_String title = http_getparam(req->body, HTTP_STR("title")); + HTTP_String link = http_getparam(req->body, HTTP_STR("link")); HTTP_String content = http_getparam(req->body, HTTP_STR("content")); title = http_trim(title); + link = http_trim(link); content = http_trim(content); - if (!valid_post_title(title) || !valid_post_content(content)) { + if (!valid_post_title(title) || !valid_link(link) || !valid_post_content(content)) { assert(0); // TODO } - // TODO + if (content.len == 0 && link.len == 0) { + assert(0); // TODO + } + + bool is_link = false; + if (link.len > 0) { + is_link = true; + content = link; + } + + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content); + if (ret != SQLITE_OK) { + assert(0); // TODO + } + + ret = sqlite3_step(stmt); + if (ret != SQLITE_DONE) { + assert(0); // TODO + } + + ret = sqlite3_finalize(stmt); + if (ret != SQLITE_OK) { + assert(0); // TODO + } + + int64_t tmp = sqlite3_last_insert_rowid(db); + if (tmp < 0 || tmp > INT_MAX) { + assert(0); // TODO + } + int post_id = (int) tmp; + + char location[1<<9]; + ret = snprintf(location, sizeof(location), "Location: /post?id=%d", post_id); + if (ret < 0 || ret >= (int) sizeof(location)) { + assert(0); // TODO + } + + http_response_builder_status(builder, 303); + http_response_builder_header(builder, (HTTP_String) { location, ret }); + http_response_builder_done(builder); } else if (http_streq(path, HTTP_STR("/index"))) { - evaluate_template_2(builder, arena, "pages/index.wl", user_id); + evaluate_template_2(builder, arena, "pages/index.wl", user_id, -1); } else if (http_streq(path, HTTP_STR("/write"))) { @@ -771,7 +832,7 @@ int main(void) continue; } - evaluate_template_2(builder, arena, "pages/write.wl", user_id); + evaluate_template_2(builder, arena, "pages/write.wl", user_id, -1); } else if (http_streq(path, HTTP_STR("/login"))) { @@ -783,7 +844,7 @@ int main(void) continue; } - evaluate_template_2(builder, arena, "pages/login.wl", user_id); + evaluate_template_2(builder, arena, "pages/login.wl", user_id, -1); } else if (http_streq(path, HTTP_STR("/signup"))) { @@ -795,15 +856,55 @@ int main(void) continue; } - evaluate_template_2(builder, arena, "pages/signup.wl", user_id); + evaluate_template_2(builder, arena, "pages/signup.wl", user_id, -1); - } else if (http_streq(path, HTTP_STR("/thread"))) { + } else if (http_streq(path, HTTP_STR("/post"))) { - evaluate_template_2(builder, arena, "pages/thread.wl", user_id); + HTTP_String idstr = http_getparam(req->url.query, HTTP_STR("id")); + + char buf[32]; + if (idstr.len == 0 || idstr.len >= (int) sizeof(buf)) { + evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1); + continue; + } + memcpy(buf, idstr.ptr, idstr.len); + buf[idstr.len] = '\0'; + + int post_id = atoi(buf); + if (post_id == 0) { + evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1); + continue; + } + + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare(db, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id); + if (ret != SQLITE_OK) { + assert(0); // TODO + } + + ret = sqlite3_step(stmt); + if (ret != SQLITE_ROW) { + assert(0); // TODO + } + int64_t num = sqlite3_column_int64(stmt, 0); + + ret = sqlite3_finalize(stmt); + if (ret != SQLITE_OK) { + assert(0); // TODO + } + + if (num < 0) { + assert(0); // TODO + } + + if (num == 0) + evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1); + else + evaluate_template_2(builder, arena, "pages/post.wl", user_id, post_id); } else { - evaluate_template_2(builder, arena, "pages/notfound.wl", user_id); + evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1); } } diff --git a/sqlite3utils.c b/src/sqlite3utils.c similarity index 100% rename from sqlite3utils.c rename to src/sqlite3utils.c diff --git a/sqlite3utils.h b/src/sqlite3utils.h similarity index 100% rename from sqlite3utils.h rename to src/sqlite3utils.h diff --git a/variadic.c b/src/variadic.c similarity index 100% rename from variadic.c rename to src/variadic.c diff --git a/variadic.h b/src/variadic.h similarity index 100% rename from variadic.h rename to src/variadic.h