From 45a9a4e9a1700cca4f4b5feecae27fd539a35c58 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Mon, 22 Sep 2025 16:43:08 +0200 Subject: [PATCH] Add comments --- 3p/crypt_blowfish.c | 2 + 3p/wl.c | 3 + amalg.py | 4 +- cweb.c | 221 ++++++++++++++++++++++---------------------- cweb.h | 9 +- demo/main.c | 4 +- src/main.c | 37 +++++--- src/main.h | 81 ++++++++++------ test.c | 22 ----- 9 files changed, 201 insertions(+), 182 deletions(-) delete mode 100644 test.c diff --git a/3p/crypt_blowfish.c b/3p/crypt_blowfish.c index 9d3f3be..feb3b06 100644 --- a/3p/crypt_blowfish.c +++ b/3p/crypt_blowfish.c @@ -50,8 +50,10 @@ #define __set_errno(val) errno = (val) #endif +#ifndef CRYPT_BLOWFISH_NOINCLUDE /* Just to make sure the prototypes match the actual definitions */ #include "crypt_blowfish.h" +#endif // CRYPT_BLOWFISH_NOINCLUDE #ifdef __i386__ #define BF_ASM 1 diff --git a/3p/wl.c b/3p/wl.c index 5aa92f9..7763701 100644 --- a/3p/wl.c +++ b/3p/wl.c @@ -3,7 +3,10 @@ #include #include #include + +#ifndef WL_NOINCLUDE #include "wl.h" +#endif // WL_NOINCLUDE ///////////////////////////////////////////////////////////////////////// // BASIC diff --git a/amalg.py b/amalg.py index 4be30c3..db06cc1 100644 --- a/amalg.py +++ b/amalg.py @@ -35,12 +35,14 @@ header.save("cweb.h") source = Amalgamator() source.append_text("#include \"cweb.h\"\n") +source.append_text("#define WL_NOINCLUDE\n") source.append_text("#define HTTP_NOINCLUDE\n") -source.append_file("3p/wl.h") +source.append_text("#define CRYPT_BLOWFISH_NOINCLUDE") source.append_file("3p/chttp.h") source.append_file("3p/chttp.c") source.append_file("3p/crypt_blowfish.h") source.append_file("3p/crypt_blowfish.c") +source.append_file("3p/wl.h") source.append_file("3p/wl.c") source.append_file("src/main.c") source.save("cweb.c") diff --git a/cweb.c b/cweb.c index d0ddc16..e80a02d 100644 --- a/cweb.c +++ b/cweb.c @@ -1,101 +1,7 @@ #include "cweb.h" +#define WL_NOINCLUDE #define HTTP_NOINCLUDE - -//////////////////////////////////////////////////////////////////////////////////////// -// 3p/wl.h -//////////////////////////////////////////////////////////////////////////////////////// - -#include -#include - -typedef struct WL_Runtime WL_Runtime; -typedef struct WL_Compiler WL_Compiler; - -typedef struct { - char *ptr; - int len; -} WL_String; - -typedef struct { - char *ptr; - int len; - int cur; -} WL_Arena; - -typedef struct { - char *ptr; - int len; -} WL_Program; - -typedef enum { - WL_ADD_ERROR, - WL_ADD_AGAIN, - WL_ADD_LINK, -} WL_AddResultType; - -typedef struct { - WL_AddResultType type; - WL_String path; -} WL_AddResult; - -typedef enum { - WL_EVAL_NONE, - WL_EVAL_DONE, - WL_EVAL_ERROR, - WL_EVAL_OUTPUT, - WL_EVAL_SYSVAR, - WL_EVAL_SYSCALL, -} WL_EvalResultType; - -typedef struct { - WL_EvalResultType type; - WL_String str; -} WL_EvalResult; - -WL_Compiler* wl_compiler_init (WL_Arena *arena); -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); -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); -bool wl_arg_none (WL_Runtime *rt, int idx); -bool wl_arg_bool (WL_Runtime *rt, int idx, bool *x); -bool wl_arg_s64 (WL_Runtime *rt, int idx, int64_t *x); -bool wl_arg_f64 (WL_Runtime *rt, int idx, double *x); -bool wl_arg_str (WL_Runtime *rt, int idx, WL_String *x); -bool wl_arg_array (WL_Runtime *rt, int idx); -bool wl_arg_map (WL_Runtime *rt, int idx); -bool wl_peek_none (WL_Runtime *rt, int off); -bool wl_peek_bool (WL_Runtime *rt, int off, bool *x); -bool wl_peek_s64 (WL_Runtime *rt, int off, int64_t *x); -bool wl_peek_f64 (WL_Runtime *rt, int off, double *x); -bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x); -bool wl_pop_any (WL_Runtime *rt); -bool wl_pop_none (WL_Runtime *rt); -bool wl_pop_bool (WL_Runtime *rt, bool *x); -bool wl_pop_s64 (WL_Runtime *rt, int64_t *x); -bool wl_pop_f64 (WL_Runtime *rt, double *x); -bool wl_pop_str (WL_Runtime *rt, WL_String *x); -void wl_push_none (WL_Runtime *rt); -void wl_push_true (WL_Runtime *rt); -void wl_push_false (WL_Runtime *rt); -void wl_push_s64 (WL_Runtime *rt, int64_t x); -void wl_push_f64 (WL_Runtime *rt, double x); -void wl_push_str (WL_Runtime *rt, WL_String x); -void wl_push_array (WL_Runtime *rt, int cap); -void wl_push_map (WL_Runtime *rt, int cap); -void wl_push_arg (WL_Runtime *rt, int idx); -void wl_insert (WL_Runtime *rt); -void wl_append (WL_Runtime *rt); - +#define CRYPT_BLOWFISH_NOINCLUDE //////////////////////////////////////////////////////////////////////////////////////// // 3p/chttp.h //////////////////////////////////////////////////////////////////////////////////////// @@ -6284,8 +6190,10 @@ extern char *_crypt_gensalt_blowfish_rn(const char *prefix, #define __set_errno(val) errno = (val) #endif +#ifndef CRYPT_BLOWFISH_NOINCLUDE /* Just to make sure the prototypes match the actual definitions */ #include "crypt_blowfish.h" +#endif // CRYPT_BLOWFISH_NOINCLUDE #ifdef __i386__ #define BF_ASM 1 @@ -7140,6 +7048,101 @@ char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, return output; } +//////////////////////////////////////////////////////////////////////////////////////// +// 3p/wl.h +//////////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +typedef struct WL_Runtime WL_Runtime; +typedef struct WL_Compiler WL_Compiler; + +typedef struct { + char *ptr; + int len; +} WL_String; + +typedef struct { + char *ptr; + int len; + int cur; +} WL_Arena; + +typedef struct { + char *ptr; + int len; +} WL_Program; + +typedef enum { + WL_ADD_ERROR, + WL_ADD_AGAIN, + WL_ADD_LINK, +} WL_AddResultType; + +typedef struct { + WL_AddResultType type; + WL_String path; +} WL_AddResult; + +typedef enum { + WL_EVAL_NONE, + WL_EVAL_DONE, + WL_EVAL_ERROR, + WL_EVAL_OUTPUT, + WL_EVAL_SYSVAR, + WL_EVAL_SYSCALL, +} WL_EvalResultType; + +typedef struct { + WL_EvalResultType type; + WL_String str; +} WL_EvalResult; + +WL_Compiler* wl_compiler_init (WL_Arena *arena); +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); +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); +bool wl_arg_none (WL_Runtime *rt, int idx); +bool wl_arg_bool (WL_Runtime *rt, int idx, bool *x); +bool wl_arg_s64 (WL_Runtime *rt, int idx, int64_t *x); +bool wl_arg_f64 (WL_Runtime *rt, int idx, double *x); +bool wl_arg_str (WL_Runtime *rt, int idx, WL_String *x); +bool wl_arg_array (WL_Runtime *rt, int idx); +bool wl_arg_map (WL_Runtime *rt, int idx); +bool wl_peek_none (WL_Runtime *rt, int off); +bool wl_peek_bool (WL_Runtime *rt, int off, bool *x); +bool wl_peek_s64 (WL_Runtime *rt, int off, int64_t *x); +bool wl_peek_f64 (WL_Runtime *rt, int off, double *x); +bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x); +bool wl_pop_any (WL_Runtime *rt); +bool wl_pop_none (WL_Runtime *rt); +bool wl_pop_bool (WL_Runtime *rt, bool *x); +bool wl_pop_s64 (WL_Runtime *rt, int64_t *x); +bool wl_pop_f64 (WL_Runtime *rt, double *x); +bool wl_pop_str (WL_Runtime *rt, WL_String *x); +void wl_push_none (WL_Runtime *rt); +void wl_push_true (WL_Runtime *rt); +void wl_push_false (WL_Runtime *rt); +void wl_push_s64 (WL_Runtime *rt, int64_t x); +void wl_push_f64 (WL_Runtime *rt, double x); +void wl_push_str (WL_Runtime *rt, WL_String x); +void wl_push_array (WL_Runtime *rt, int cap); +void wl_push_map (WL_Runtime *rt, int cap); +void wl_push_arg (WL_Runtime *rt, int idx); +void wl_insert (WL_Runtime *rt); +void wl_append (WL_Runtime *rt); + //////////////////////////////////////////////////////////////////////////////////////// // 3p/wl.c //////////////////////////////////////////////////////////////////////////////////////// @@ -7149,7 +7152,10 @@ char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, #include #include #include + +#ifndef WL_NOINCLUDE #include "wl.h" +#endif // WL_NOINCLUDE ///////////////////////////////////////////////////////////////////////// // BASIC @@ -13795,12 +13801,13 @@ CWEB_Request *cweb_wait(CWEB *cweb) CWEB_Request *req = &cweb->req; int ret = http_server_wait(cweb->server, &req->req, &req->builder); - if (ret < 0) return -1; + if (ret < 0) return NULL; + + HTTP_String sess_token = http_get_cookie(req->req, HTTP_STR("sess_token")); req->arena = (WL_Arena) { cweb->pool, cweb->pool_cap, 0 }; - req->just_created_session = false; - req->sess = http_get_cookie(req->req, HTTP_STR("sess_token")); + req->sess = (CWEB_String) { sess_token.ptr, sess_token.len }; if (find_session(cweb->session_storage, req->sess, &req->csrf, &req->user_id) < 0) { req->user_id = -1; req->sess = (CWEB_String) { NULL, 0 }; @@ -13978,31 +13985,23 @@ static void evaluate_format(StaticOutputBuffer *out, CWEB_String format, CWEB_VA } } -static CWEB_String evaluate_format_for_request_impl(CWEB_Request *req, CWEB_String format, CWEB_VArgs args) +CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args) { StaticOutputBuffer out = { .dst = req->arena.ptr + req->arena.cur, .cap = req->arena.len - req->arena.cur, .len = 0 }; - evaluate_format(&out, format, args); + evaluate_format(&out, (CWEB_String) { fmt, strlen(fmt) }, args); if (out.len > req->arena.len - req->arena.cur) return (CWEB_String) { NULL, 0 }; req->arena.cur += out.len; return (CWEB_String) { out.dst, out.len }; } -#define evaluate_format_for_request(req, format, ...) evaluate_format_for_request_impl((req), (format), CWEB_VARGS(__VA_ARGS__)) -void cweb_respond_redirect_impl(CWEB_Request *req, CWEB_String target_format, CWEB_VArgs args) +void cweb_respond_redirect_impl(CWEB_Request *req, CWEB_String target) { - CWEB_String target = evaluate_format_for_request_impl(req, target_format, args); - if (target.len == 0) { - http_response_builder_status(req->builder, 500); - http_response_builder_done(req->builder); - return; - } - - CWEB_String location_header = evaluate_format_for_request(req, CWEB_STR("Location: {}"), target); + CWEB_String location_header = cweb_format(req, "Location: {}", target); if (location_header.len == 0) { http_response_builder_status(req->builder, 500); http_response_builder_done(req->builder); diff --git a/cweb.h b/cweb.h index 84710cd..f12c7c4 100644 --- a/cweb.h +++ b/cweb.h @@ -249,11 +249,12 @@ int cweb_get_param_i(CWEB_Request *req, CWEB_String name); ////////////////////////////////////// // Response -void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content); -void cweb_respond_redirect_impl(CWEB_Request *req, CWEB_String target_format, CWEB_VArgs args); -void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id); +CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args); +#define cweb_format(req, fmt, ...) cweb_format_impl((req), (fmt), CWEB_VARGS(__VA_ARGS__)) -#define cweb_respond_redirect(req, format, ...) cweb_respond_redirect_impl((req), (format), CWEB_VARGS(__VA_ARGS__)) +void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content); +void cweb_respond_redirect(CWEB_Request *req, CWEB_String target); +void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id); ////////////////////////////////////// // Database diff --git a/demo/main.c b/demo/main.c index 4dd2f63..4985e36 100644 --- a/demo/main.c +++ b/demo/main.c @@ -259,7 +259,7 @@ static void endpoint_api_post(CWEB *cweb, CWEB_Request *req) } int post_id = (int) insert_id; - cweb_respond_redirect(req, CWEB_STR("/post?id={}"), post_id); + cweb_respond_redirect(req, cweb_format(req, "/post?id={}", post_id)); } static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req) @@ -294,7 +294,7 @@ static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req) return; } - cweb_respond_redirect(req, CWEB_STR("/post?id={}"), parent_post); + cweb_respond_redirect(req, cweb_format(req, "/post?id={}", parent_post)); } static void endpoint_index(CWEB *cweb, CWEB_Request *req) diff --git a/src/main.c b/src/main.c index a7c6322..6419e77 100644 --- a/src/main.c +++ b/src/main.c @@ -836,7 +836,11 @@ bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str) CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name) { - HTTP_String res = http_get_param(req->req->body, + HTTP_String src = req->req->url.query; + if (req->req->method == HTTP_METHOD_POST) + src = req->req->body; + + HTTP_String res = http_get_param(src, (HTTP_String) { name.ptr, name.len }, req->arena.ptr + req->arena.cur, req->arena.len - req->arena.cur @@ -848,7 +852,11 @@ CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name) int cweb_get_param_i(CWEB_Request *req, CWEB_String name) { - return http_get_param_i(req->req->body, (HTTP_String) { name.ptr, name.len }); + HTTP_String src = req->req->url.query; + if (req->req->method == HTTP_METHOD_POST) + src = req->req->body; + + return http_get_param_i(src, (HTTP_String) { name.ptr, name.len }); } static int set_auth_cookie_if_necessary(CWEB_Request *req) @@ -997,31 +1005,23 @@ static void evaluate_format(StaticOutputBuffer *out, CWEB_String format, CWEB_VA } } -static CWEB_String evaluate_format_for_request_impl(CWEB_Request *req, CWEB_String format, CWEB_VArgs args) +CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args) { StaticOutputBuffer out = { .dst = req->arena.ptr + req->arena.cur, .cap = req->arena.len - req->arena.cur, .len = 0 }; - evaluate_format(&out, format, args); + evaluate_format(&out, (CWEB_String) { fmt, strlen(fmt) }, args); if (out.len > req->arena.len - req->arena.cur) return (CWEB_String) { NULL, 0 }; req->arena.cur += out.len; return (CWEB_String) { out.dst, out.len }; } -#define evaluate_format_for_request(req, format, ...) evaluate_format_for_request_impl((req), (format), CWEB_VARGS(__VA_ARGS__)) -void cweb_respond_redirect_impl(CWEB_Request *req, CWEB_String target_format, CWEB_VArgs args) +void cweb_respond_redirect(CWEB_Request *req, CWEB_String target) { - CWEB_String target = evaluate_format_for_request_impl(req, target_format, args); - if (target.len == 0) { - http_response_builder_status(req->builder, 500); - http_response_builder_done(req->builder); - return; - } - - CWEB_String location_header = evaluate_format_for_request(req, CWEB_STR("Location: {}"), target); + CWEB_String location_header = cweb_format(req, "Location: {}", target); if (location_header.len == 0) { http_response_builder_status(req->builder, 500); http_response_builder_done(req->builder); @@ -1342,7 +1342,7 @@ static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) return 0; } -static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int resource_id) +static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, CWEB_String csrf, int user_id, int resource_id) { (void) dbcache; @@ -1410,6 +1410,13 @@ static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id) { http_response_builder_status(req->builder, status); + int ret = set_auth_cookie_if_necessary(req); + if (ret < 0) { + http_response_builder_undo(req->builder); + http_response_builder_status(req->builder, -ret); + http_response_builder_done(req->builder); + return; + } WL_Program program; int ret = get_or_create_program(req->cweb->tpcache, template_file, &req->arena, &program); diff --git a/src/main.h b/src/main.h index dcb9673..568ff47 100644 --- a/src/main.h +++ b/src/main.h @@ -221,49 +221,76 @@ void cweb_free(CWEB *cweb); int cweb_enable_database(CWEB *cweb, CWEB_String file); +// Pause execution until a request is available. +// TODO: When does this function return NULL? CWEB_Request *cweb_wait(CWEB *cweb); -////////////////////////////////////// -// Session +// Returns true iff the request matches the specified endpoint +bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str); +// Returns the CSRF token associated to the current session CWEB_String cweb_get_session_csrf(CWEB_Request *req); -int cweb_get_session_user_id(CWEB_Request *req); -int cweb_set_session_user_id(CWEB_Request *req, int user_id); -////////////////////////////////////// -// Request +// Returns the user ID for the current session, or -1 if there is no session +int cweb_get_session_user_id(CWEB_Request *req); -bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str); +// Sets the user ID for the current session (it must be a positive integer). +// If the ID is -1, the session is deleted. +int cweb_set_session_user_id(CWEB_Request *req, int user_id); + +// Returns the request parameter with the specified name +// If the request uses POST, the parameter is taken from the body, +// else it's taken from the URL. If the parameter is not present, +// an empty string is returned. CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name); -int cweb_get_param_i(CWEB_Request *req, CWEB_String name); -////////////////////////////////////// -// Response +// Like cweb_get_param_s, but also parser the argument as an integer. +// If parsing fails or the parameter is missing, -1 is returned. +int cweb_get_param_i(CWEB_Request *req, CWEB_String name); +// Create a string by evaluating a format. Memory is allocated from the arena of the request. +// If the arena is full, an empty string is returned. +CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args); + +// Helper +#define cweb_format(req, fmt, ...) cweb_format_impl((req), (fmt), CWEB_VARGS(__VA_ARGS__)) + +// Responds to the specified request with the given status code and content void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content); -void cweb_respond_redirect_impl(CWEB_Request *req, CWEB_String target_format, CWEB_VArgs args); + +// Responds to the request by redirecting the client to the given target +void cweb_respond_redirect(CWEB_Request *req, CWEB_String target); + +// Responds to the request by evaluating a WL template file void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id); -#define cweb_respond_redirect(req, format, ...) cweb_respond_redirect_impl((req), (format), CWEB_VARGS(__VA_ARGS__)) - -////////////////////////////////////// -// Database - -typedef struct { - void *handle; -} CWEB_QueryResult; - -int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); -CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); -int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args); -void cweb_free_query_result(CWEB_QueryResult *res); +// Evaluates an SQL INSERT statement and returns the ID of the last inserted row. On error -1 is returned +int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); +// Helper #define cweb_database_insert(cweb, fmt, ...) cweb_database_insert_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) + +// Iterator over database rows +typedef struct { void *handle; } CWEB_QueryResult; + +// Evaluates an SQL SELECT statement, returning a scanner over the returned rows. +// You don't have to check for errors with this function +CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); + +// Helper #define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) -#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) -////////////////////////////////////// -// Password +// Returns the next row from the query result iterator. +int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args); +// Helper +#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) + +// Frees the result of a database query +void cweb_free_query_result(CWEB_QueryResult *res); + +// Calculates the bcrypt hash of the specified password int cweb_hash_password(char *pass, int passlen, int cost, CWEB_PasswordHash *hash); + +// Checks whether the password matches the given hash int cweb_check_password(char *pass, int passlen, CWEB_PasswordHash hash); diff --git a/test.c b/test.c deleted file mode 100644 index f95ae1c..0000000 --- a/test.c +++ /dev/null @@ -1,22 +0,0 @@ -// Helper macros -#define __CWEB_HELPER_DISPATCH_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N -#define __CWEB_HELPER_CONCAT_0(A, B) A ## B -#define __CWEB_HELPER_CONCAT_1(A, B) __CWEB_HELPER_CONCAT_0(A, B) -#define __CWEB_HELPER_ARG(a) (a) -#define __CWEB_HELPER_ARGS_0() (CWEB_VArgs) { 0 } -#define __CWEB_HELPER_ARGS_1(a) (CWEB_VArgs) { 1, __CWEB_HELPER_ARG(a) } -#define __CWEB_HELPER_ARGS_2(a, b) (CWEB_VArgs) { 2, __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b) } -#define __CWEB_HELPER_ARGS_3(a, b, c) (CWEB_VArgs) { 3, __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c) } -#define __CWEB_HELPER_ARGS_4(a, b, c, d) (CWEB_VArgs) { 4, __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d) } -#define __CWEB_HELPER_ARGS_5(a, b, c, d, e) (CWEB_VArgs) { 5, __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e) } -#define __CWEB_HELPER_ARGS_6(a, b, c, d, e, f) (CWEB_VArgs) { 6, __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f) } -#define __CWEB_HELPER_ARGS_7(a, b, c, d, e, f, g) (CWEB_VArgs) { 7, __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g) } -#define __CWEB_HELPER_ARGS_8(a, b, c, d, e, f, g, h) (CWEB_VArgs) { 8, __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g), __CWEB_HELPER_ARG(h) } -#define __CWEB_COUNT_ARGS(...) __CWEB_HELPER_DISPATCH_N(DUMMY, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - -#define CWEB_ARGS(...) __CWEB_HELPER_CONCAT_1(__CWEB_HELPER_ARGS_, __CWEB_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__) - -CWEB_ARGS() -CWEB_ARGS(1) -CWEB_ARGS(1, 2) -CWEB_ARGS(1, 2, 3)