diff --git a/.gitignore b/.gitignore index 497d75c..7376d4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -*.o -*.exe -*.out - +*.o +*.exe +*.out + diff --git a/3p/WL.c b/3p/WL.c index 9e3db75..46ef162 100644 --- a/3p/WL.c +++ b/3p/WL.c @@ -1,5650 +1,5650 @@ -#include -#include -#include -#include -#include -#include "wl.h" - -///////////////////////////////////////////////////////////////////////// -// BASIC -///////////////////////////////////////////////////////////////////////// - -typedef struct { - char *ptr; - int len; -} String; - -typedef struct { - char *buf; - int cap; - bool yes; -} Error; - -#define S(X) (String) { (X), SIZEOF(X)-1 } - -#ifdef _WIN32 -#define LLD "lld" -#define LLU "llu" -#else -#define LLD "ld" -#define LLU "lu" -#endif - -#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) -#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) -#define SIZEOF(X) (int) sizeof(X) -#define ALIGNOF(X) (int) _Alignof(X) - -#ifndef NDEBUG -#define UNREACHABLE __builtin_trap() -#define ASSERT(X) if (!(X)) __builtin_trap(); -#else -#define UNREACHABLE {} -#define ASSERT(X) {} -#endif - -static bool is_space(char c) -{ - return c == ' ' || c == '\t' || c == '\r' || c == '\n'; -} - -static bool is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -static bool is_alpha(char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -static bool is_printable(char c) -{ - return c >= ' ' && c <= '~'; -} - -static bool is_hex_digit(char c) -{ - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -#if 0 -static char to_lower(char c) -{ - if (c >= 'A' && c <= 'Z') - return c - 'A' + 'a'; - return c; -} -#endif - -static int hex_digit_to_int(char c) -{ - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return c - '0'; -} - -static bool streq(String a, String b) -{ - if (a.len != b.len) - return false; - for (int i = 0; i < a.len; i++) - if (a.ptr[i] != b.ptr[i]) - return false; - return true; -} - -#if 0 -static bool streqcase(String a, String b) -{ - if (a.len != b.len) - return false; - for (int i = 0; i < a.len; i++) - if (to_lower(a.ptr[i]) != to_lower(b.ptr[i])) - return false; - return true; -} -#endif - -#define REPORT(err, fmt, ...) report((err), __FILE__, __LINE__, fmt, ## __VA_ARGS__) -static void report(Error *err, char *file, int line, char *fmt, ...) -{ - if (err->yes) return; - - if (err->cap > 0) { - - va_list args; - va_start(args, fmt); - int len = vsnprintf(err->buf, err->cap, fmt, args); - va_end(args); - ASSERT(len >= 0); - - if (err->cap > len) { - int ret = snprintf(err->buf + len, err->cap - len, - " (reported at %s:%d)", file, line); - ASSERT(ret >= 0); - len += ret; - } - - if (len > err->cap) - len = err->cap-1; - err->buf[len] = '\0'; - } - - err->yes = true; -} - -///////////////////////////////////////////////////////////////////////// -// ARENA -///////////////////////////////////////////////////////////////////////// - -static void *alloc(WL_Arena *a, int len, int align) -{ - int pad = -(intptr_t) (a->ptr + a->cur) & (align-1); - if (a->len - a->cur < len + pad) - return NULL; - void *ret = a->ptr + a->cur + pad; - a->cur += pad + len; - return ret; -} - -static bool grow_alloc(WL_Arena *a, char *p, int new_len) -{ - int new_cur = (p - a->ptr) + new_len; - if (new_cur > a->len) - return false; - a->cur = new_cur; - return true; -} - -#if 0 -static String copystr(String s, WL_Arena *a) -{ - char *p = alloc(a, s.len, 1); - if (p == NULL) - return (String) { NULL, 0 }; - memcpy(p, s.ptr, s.len); - return (String) { p, s.len }; -} -#endif - -///////////////////////////////////////////////////////////////////////// -// WRITER -///////////////////////////////////////////////////////////////////////// - -typedef struct { - char *dst; - int cap; - int len; -} Writer; - -static void write_raw_mem(Writer *w, void *ptr, int len) -{ - if (w->cap > w->len) { - int cpy = MIN(w->cap - w->len, len); - if (ptr && w->dst) - memcpy(w->dst + w->len, ptr, cpy); - } - w->len += len; -} - -static void write_raw_u8 (Writer *w, uint8_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_u16(Writer *w, uint16_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -static void write_raw_u32(Writer *w, uint32_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_u64(Writer *w, uint64_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_s8 (Writer *w, int8_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_s16(Writer *w, int16_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_s32(Writer *w, int32_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -static void write_raw_s64(Writer *w, int64_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_f32(Writer *w, float x) { write_raw_mem(w, &x, SIZEOF(x)); } -static void write_raw_f64(Writer *w, double x) { write_raw_mem(w, &x, SIZEOF(x)); } - -static void write_text(Writer *w, String str) -{ - write_raw_mem(w, str.ptr, str.len); -} - -static void write_text_s64(Writer *w, int64_t n) -{ - int len; - if (w->len < w->cap) - len = snprintf(w->dst + w->len, w->cap - w->len, "%" LLD, n); - else - len = snprintf(NULL, 0, "%" LLD, n); - ASSERT(len >= 0); - w->len += len; -} - -static void write_text_f64(Writer *w, double n) -{ - int len; - if (w->len < w->cap) - len = snprintf(w->dst + w->len, w->cap - w->len, "%2.2f", n); - else - len = snprintf(NULL, 0, "%2.2f", n); - ASSERT(len >= 0); - w->len += len; -} - -static void patch_mem(Writer *w, void *src, int off, int len) -{ - ASSERT(off + len <= w->len); - if (off < w->cap) { - int cpy = MIN(w->cap - off, len); - memcpy(w->dst + off, src, cpy); - } -} - -///////////////////////////////////////////////////////////////////////// -// PARSER -///////////////////////////////////////////////////////////////////////// - -typedef struct { - char *src; - int len; - int cur; -} Scanner; - -typedef enum { - TOKEN_END, - TOKEN_ERROR, - TOKEN_IDENT, - TOKEN_KWORD_IF, - TOKEN_KWORD_ELSE, - TOKEN_KWORD_WHILE, - TOKEN_KWORD_FOR, - TOKEN_KWORD_IN, - TOKEN_KWORD_PROCEDURE, - TOKEN_KWORD_LET, - TOKEN_KWORD_NONE, - TOKEN_KWORD_TRUE, - TOKEN_KWORD_FALSE, - TOKEN_KWORD_INCLUDE, - TOKEN_KWORD_LEN, - TOKEN_VALUE_FLOAT, - TOKEN_VALUE_INT, - TOKEN_VALUE_STR, - TOKEN_OPER_EQL, - TOKEN_OPER_NQL, - TOKEN_OPER_LSS, - TOKEN_OPER_GRT, - TOKEN_OPER_ADD, - TOKEN_OPER_SUB, - TOKEN_OPER_MUL, - TOKEN_OPER_DIV, - TOKEN_OPER_MOD, - TOKEN_OPER_ASS, - TOKEN_OPER_SHOVEL, - TOKEN_PAREN_OPEN, - TOKEN_PAREN_CLOSE, - TOKEN_BRACKET_OPEN, - TOKEN_BRACKET_CLOSE, - TOKEN_CURLY_OPEN, - TOKEN_CURLY_CLOSE, - TOKEN_DOT, - TOKEN_COMMA, - TOKEN_COLON, - TOKEN_DOLLAR, - TOKEN_NEWLINE, -} TokType; - -typedef struct { - TokType type; - int64_t ival; - double fval; - String sval; -} Token; - -typedef enum { - NODE_PROCEDURE_DECL, - NODE_PROCEDURE_ARG, - NODE_PROCEDURE_CALL, - NODE_VAR_DECL, - NODE_COMPOUND, - NODE_GLOBAL, - NODE_IFELSE, - NODE_FOR, - NODE_WHILE, - NODE_INCLUDE, - NODE_SELECT, - NODE_NESTED, - NODE_OPER_LEN, - NODE_OPER_POS, - NODE_OPER_NEG, - NODE_OPER_ASS, - NODE_OPER_EQL, - NODE_OPER_NQL, - NODE_OPER_LSS, - NODE_OPER_GRT, - NODE_OPER_ADD, - NODE_OPER_SUB, - NODE_OPER_MUL, - NODE_OPER_DIV, - NODE_OPER_MOD, - NODE_OPER_SHOVEL, - NODE_VALUE_INT, - NODE_VALUE_FLOAT, - NODE_VALUE_STR, - NODE_VALUE_NONE, - NODE_VALUE_TRUE, - NODE_VALUE_FALSE, - NODE_VALUE_VAR, - NODE_VALUE_SYSVAR, - NODE_VALUE_HTML, - NODE_VALUE_ARRAY, - NODE_VALUE_MAP, -} NodeType; - -typedef struct Node Node; -struct Node { - NodeType type; - Node *next; - - Node *key; - - Node *left; - Node *right; - - Node *child; - - uint64_t ival; - double fval; - String sval; - - String html_tag; - Node* html_attr; - Node* html_child; - bool html_body; - - Node *if_cond; - Node *if_branch1; - Node *if_branch2; - - Node *while_cond; - Node *while_body; - - String for_var1; - String for_var2; - Node* for_set; - - String proc_name; - Node* proc_args; - Node* proc_body; - - String var_name; - Node* var_value; - - String include_path; - Node* include_next; - Node* include_root; -}; - -typedef struct { - Node *node; - Node *includes; - int errlen; -} ParseResult; - -typedef struct { - Scanner s; - WL_Arena* arena; - char* errbuf; - int errmax; - int errlen; - Node* include_head; - Node** include_tail; -} Parser; - -static bool consume_str(Scanner *s, String x) -{ - if (x.len == 0) - return false; - - if (x.len > s->len - s->cur) - return false; - - for (int i = 0; i < x.len; i++) - if (s->src[s->cur+i] != x.ptr[i]) - return false; - - s->cur += x.len; - return true; -} - -#if 0 -static void write_token(Writer *w, Token token) -{ - switch (token.type) { - - default : write_text(w, S("???")); break; - case TOKEN_END : write_text(w, S("")); break; - case TOKEN_ERROR : write_text(w, S("")); break; - case TOKEN_IDENT : write_text(w, token.sval); break; - case TOKEN_KWORD_IF : write_text(w, S("if")); break; - case TOKEN_KWORD_ELSE : write_text(w, S("else")); break; - case TOKEN_KWORD_WHILE : write_text(w, S("while")); break; - case TOKEN_KWORD_FOR : write_text(w, S("for")); break; - case TOKEN_KWORD_IN : write_text(w, S("in")); break; - case TOKEN_KWORD_PROCEDURE: write_text(w, S("procedure")); break; - case TOKEN_KWORD_LET : write_text(w, S("let")); break; - case TOKEN_KWORD_NONE : write_text(w, S("none")); break; - case TOKEN_KWORD_TRUE : write_text(w, S("true")); break; - case TOKEN_KWORD_FALSE : write_text(w, S("false")); break; - case TOKEN_KWORD_INCLUDE: write_text(w, S("include")); break; - case TOKEN_KWORD_LEN : write_text(w, S("len")); break; - case TOKEN_VALUE_FLOAT : write_text_f64(w, token.fval); break; - case TOKEN_VALUE_INT : write_text_s64(w, token.ival); break; - case TOKEN_OPER_ASS : write_text(w, S("=")); break; - case TOKEN_OPER_EQL : write_text(w, S("==")); break; - case TOKEN_OPER_NQL : write_text(w, S("!=")); break; - case TOKEN_OPER_LSS : write_text(w, S("<")); break; - case TOKEN_OPER_GRT : write_text(w, S(">")); break; - case TOKEN_OPER_ADD : write_text(w, S("+")); break; - case TOKEN_OPER_SUB : write_text(w, S("-")); break; - 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; - case TOKEN_BRACKET_CLOSE: write_text(w, S("]")); break; - case TOKEN_CURLY_OPEN : write_text(w, S("{")); break; - case TOKEN_CURLY_CLOSE : write_text(w, S("}")); break; - case TOKEN_DOT : write_text(w, S(".")); break; - case TOKEN_COMMA : write_text(w, S(",")); break; - case TOKEN_COLON : write_text(w, S(":")); break; - case TOKEN_DOLLAR : write_text(w, S("$")); break; - case TOKEN_NEWLINE : write_text(w, S("\\n")); break; - - case TOKEN_VALUE_STR: - write_text(w, S("\"")); - write_text(w, token.sval); // TODO: Escape - write_text(w, S("\"")); - break; - - } -} -#endif - -static void parser_report(Parser *p, char *fmt, ...) -{ - if (p->errmax == 0 || p->errlen > 0) - return; - - int line = 1; - int cur = 0; - while (cur < p->s.cur) { - if (p->s.src[cur] == '\n') - line++; - cur++; - } - - int len = snprintf(p->errbuf, p->errmax, "Error (line %d): ", line); - ASSERT(len >= 0); - - va_list args; - va_start(args, fmt); - int ret = vsnprintf(p->errbuf + len, p->errmax - len, fmt, args); - va_end(args); - ASSERT(ret >= 0); - len += ret; - - p->errlen = len; -} - -static Node *alloc_node(Parser *p) -{ - Node *n = alloc(p->arena, sizeof(Node), _Alignof(Node)); - if (n == NULL) { - parser_report(p, "Out of memory"); - return NULL; - } - - return n; -} - -static Token next_token(Parser *p) -{ - for (;;) { - while (p->s.cur < p->s.len && is_space(p->s.src[p->s.cur])) - p->s.cur++; - - if (!consume_str(&p->s, S(""))) - break; - p->s.cur++; - } - } - - if (p->s.cur == p->s.len) - return (Token) { .type=TOKEN_END }; - char c = p->s.src[p->s.cur]; - - if (is_alpha(c) || c == '_') { - - int start = p->s.cur; - do - p->s.cur++; - while (p->s.cur < p->s.len && (is_alpha(p->s.src[p->s.cur]) || is_digit(p->s.src[p->s.cur]) || p->s.src[p->s.cur] == '_')); - - String kword = { - p->s.src + start, - 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("procedure"))) return (Token) { .type=TOKEN_KWORD_PROCEDURE }; - if (streq(kword, S("let"))) return (Token) { .type=TOKEN_KWORD_LET }; - 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 }; - } - - if (is_digit(c)) { - - int peek = p->s.cur; - do - peek++; - while (peek < p->s.len && is_digit(p->s.src[peek])); - - if (p->s.len - peek > 1 && p->s.src[peek] == '.' && is_digit(p->s.src[peek+1])) { - - double buf = 0; - do { - int d = p->s.src[p->s.cur++] - '0'; - buf = buf * 10 + d; - } while (p->s.cur < p->s.len && p->s.src[p->s.cur] != '.'); - - p->s.cur++; - - double q = 1; - do { - int d = p->s.src[p->s.cur++] - '0'; - q /= 10; - buf += q * d; - } while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur])); - - return (Token) { .type=TOKEN_VALUE_FLOAT, .fval=buf }; - - } else { - - uint64_t buf = 0; - do { - int d = p->s.src[p->s.cur++] - '0'; - if (buf > (UINT64_MAX - d) / 10) { - parser_report(p, "Integer literal overflow"); - return (Token) { .type=TOKEN_ERROR }; - } - buf = buf * 10 + d; - } while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur])); - - return (Token) { .type=TOKEN_VALUE_INT, .ival=buf }; - } - } - - if (c == '\'' || c == '"') { - - char f = c; - p->s.cur++; - - char *buf = NULL; - int len = 0; - - for (;;) { - - int substr_off = p->s.cur; - - while (p->s.cur < p->s.len && is_printable(p->s.src[p->s.cur]) && p->s.src[p->s.cur] != f && p->s.src[p->s.cur] != '\\') - p->s.cur++; - - int substr_len = p->s.cur - substr_off; - - if (buf == NULL) - buf = alloc(p->arena, substr_len+1, 1); - else - if (!grow_alloc(p->arena, buf, len + substr_len+1)) - buf = NULL; - - if (buf == NULL) { - parser_report(p, "Out of memory"); - return (Token) { .type=TOKEN_ERROR }; - } - - if (substr_len > 0) { - memcpy( - buf + len, - p->s.src + substr_off, - p->s.cur - substr_off - ); - len += substr_len; - } - - if (p->s.cur == p->s.len) { - parser_report(p, "String literal wasn't closed"); - return (Token) { .type=TOKEN_ERROR }; - } - - if (!is_printable(p->s.src[p->s.cur])) { - parser_report(p, "Invalid byte in string literal"); - return (Token) { .type=TOKEN_ERROR }; - } - - if (p->s.src[p->s.cur] == f) - break; - - p->s.cur++; - if (p->s.cur == p->s.len) { - parser_report(p, "Missing special character after escape character \\"); - return (Token) { .type=TOKEN_ERROR }; - } - - switch (p->s.src[p->s.cur]) { - case 'n': buf[len++] = '\n'; break; - case 't': buf[len++] = '\t'; break; - case 'r': buf[len++] = '\r'; break; - case '"': buf[len++] = '"'; break; - case '\'': buf[len++] = '\''; break; - case '\\': buf[len++] = '\\'; break; - - case 'x': - { - if (p->s.len - p->s.cur < 3 - || !is_hex_digit(p->s.src[p->s.cur+1]) - || !is_hex_digit(p->s.src[p->s.cur+2])) - return (Token) { .type=TOKEN_ERROR }; - buf[len++] - = (hex_digit_to_int(p->s.src[p->s.cur+1]) << 4) - | (hex_digit_to_int(p->s.src[p->s.cur+2]) << 0); - p->s.cur += 2; - } - break; - - default: - parser_report(p, "Invalid character after escape character \\"); - return (Token) { .type=TOKEN_ERROR }; - } - - p->s.cur++; - } - - p->s.cur++; - 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 }; - if (consume_str(&p->s, S(">"))) return (Token) { .type=TOKEN_OPER_GRT }; - if (consume_str(&p->s, S("+"))) return (Token) { .type=TOKEN_OPER_ADD }; - if (consume_str(&p->s, S("-"))) return (Token) { .type=TOKEN_OPER_SUB }; - if (consume_str(&p->s, S("*"))) return (Token) { .type=TOKEN_OPER_MUL }; - if (consume_str(&p->s, S("/"))) return (Token) { .type=TOKEN_OPER_DIV }; - if (consume_str(&p->s, S("%"))) return (Token) { .type=TOKEN_OPER_MOD }; - if (consume_str(&p->s, S("="))) return (Token) { .type=TOKEN_OPER_ASS }; - - if (consume_str(&p->s, S("("))) return (Token) { .type=TOKEN_PAREN_OPEN }; - if (consume_str(&p->s, S(")"))) return (Token) { .type=TOKEN_PAREN_CLOSE }; - if (consume_str(&p->s, S("["))) return (Token) { .type=TOKEN_BRACKET_OPEN }; - if (consume_str(&p->s, S("]"))) return (Token) { .type=TOKEN_BRACKET_CLOSE }; - if (consume_str(&p->s, S("{"))) return (Token) { .type=TOKEN_CURLY_OPEN }; - if (consume_str(&p->s, S("}"))) return (Token) { .type=TOKEN_CURLY_CLOSE }; - if (consume_str(&p->s, S("."))) return (Token) { .type=TOKEN_DOT }; - if (consume_str(&p->s, S(","))) return (Token) { .type=TOKEN_COMMA }; - if (consume_str(&p->s, S(":"))) return (Token) { .type=TOKEN_COLON }; - if (consume_str(&p->s, S("$"))) return (Token) { .type=TOKEN_DOLLAR }; - - parser_report(p, "Invalid character '%c'", c); - return (Token) { .type=TOKEN_ERROR }; -} - -static Token next_token_or_newline(Parser *p) -{ - int peek = p->s.cur; - while (peek < p->s.len && is_space(p->s.src[peek]) && p->s.src[peek] != '\n') - peek++; - - if (peek < p->s.len && p->s.src[peek] == '\n') { - p->s.cur = peek+1; - return (Token) { .type=TOKEN_NEWLINE }; - } - - return next_token(p); -} - -enum { - IGNORE_GRT = 1 << 0, - IGNORE_LSS = 1 << 1, - IGNORE_DIV = 1 << 2, -}; - -static Node *parse_stmt(Parser *p, int opflags); -static Node *parse_expr(Parser *p, int opflags); - -static Node *parse_html(Parser *p) -{ - // NOTE: The first < was already consumed - - Token t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "HTML tag doesn't start with a name"); - return NULL; - } - String tagname = t.sval; - - Node *attr_head; - Node **attr_tail = &attr_head; - - bool no_body = false; - Scanner *s = &p->s; - for (;;) { - - int off = s->cur; - - bool quotes = false; - while (s->cur < s->len && s->src[s->cur] != '\\' && (quotes || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) { - if (s->src[s->cur] == '"') - quotes = !quotes; - s->cur++; - } - - if (s->cur > off) { - - Node *child = alloc_node(p); - if (child == NULL) - return NULL; - - child->type = NODE_VALUE_STR; - child->sval = (String) { p->s.src + off, p->s.cur - off }; - - *attr_tail = child; - attr_tail = &child->next; - } - - if (s->cur == s->len) { - ASSERT(0); // TODO - } - s->cur++; - - if (s->src[s->cur-1] == '>') - break; - - if (s->src[s->cur-1] == '/') { - while (s->cur < s->len && is_space(s->src[s->cur])) - s->cur++; - if (s->cur == s->len || s->src[s->cur] != '>') { - ASSERT(0); // TODO - } - s->cur++; - no_body = true; - break; - } - - ASSERT(s->src[s->cur-1] == '\\'); - - Node *child = parse_stmt(p, IGNORE_GRT | IGNORE_DIV); - if (child == NULL) - return NULL; - - *attr_tail = child; - attr_tail = &child->next; - } - - *attr_tail = NULL; - - Node *child_head; - Node **child_tail = &child_head; - - if (no_body == false) - for (;;) { - - int off = s->cur; - - while (s->cur < s->len && s->src[s->cur] != '\\' && s->src[s->cur] != '<') - s->cur++; - - if (s->cur > off) { - - Node *child = alloc_node(p); - if (child == NULL) - return NULL; - - child->type = NODE_VALUE_STR; - child->sval = (String) { p->s.src + off, p->s.cur - off }; - - *child_tail = child; - child_tail = &child->next; - } - - if (s->cur == s->len) { - ASSERT(0); // TODO - } - s->cur++; - - if (s->src[s->cur-1] == '<') { - - Scanner saved = *s; - t = next_token(p); - if (t.type == TOKEN_OPER_DIV) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - ASSERT(0); // TODO - } - String closing_tagname = t.sval; - - if (!streq(closing_tagname, tagname)) { - ASSERT(0); // TODO - } - - t = next_token(p); - if (t.type != TOKEN_OPER_GRT) { - ASSERT(0); - } - - break; - } - - *s = saved; - - Node *child = parse_html(p); - if (child == NULL) - return NULL; - - *child_tail = child; - child_tail = &child->next; - - } else { - - Node *child = parse_stmt(p, IGNORE_LSS); - if (child == NULL) - return NULL; - - *child_tail = child; - child_tail = &child->next; - } - } - - *child_tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VALUE_HTML; - parent->html_tag = tagname; - parent->html_attr = attr_head; - parent->html_child = child_head; - parent->html_body = !no_body; - - return parent; -} - -static Node *parse_array(Parser *p) -{ - // Left bracket already consumed - - Node *head; - Node **tail = &head; - - Scanner saved = p->s; - Token t = next_token(p); - if (t.type != TOKEN_BRACKET_CLOSE) { - - p->s = saved; - - for (;;) { - - Node *child = parse_expr(p, 0); - if (child == NULL) - return NULL; - - *tail = child; - tail = &child->next; - - saved = p->s; - t = next_token(p); - if (t.type == TOKEN_COMMA) { - saved = p->s; - t = next_token(p); - } - - if (t.type == TOKEN_BRACKET_CLOSE) - break; - - p->s = saved; - } - } - - *tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VALUE_ARRAY; - parent->child = head; - - return parent; -} - -static Node *parse_map(Parser *p) -{ - // Left bracket already consumed - - Node *head; - Node **tail = &head; - - Scanner saved = p->s; - Token t = next_token(p); - if (t.type != TOKEN_CURLY_CLOSE) { - - p->s = saved; - - for (;;) { - - Node *key; - - saved = p->s; - t = next_token(p); - if (t.type == TOKEN_IDENT) { - - key = alloc_node(p); - if (key == NULL) - return NULL; - - key->type = NODE_VALUE_STR; - key->sval = t.sval; - - } else { - - p->s = saved; - key = parse_expr(p, 0); - if (key == NULL) - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing ':' after key inside map literal"); - return NULL; - } - - Node *child = parse_expr(p, 0); - if (child == NULL) - return NULL; - child->key = key; - - *tail = child; - tail = &child->next; - - saved = p->s; - t = next_token(p); - if (t.type == TOKEN_COMMA) { - saved = p->s; - t = next_token(p); - } - - if (t.type == TOKEN_CURLY_CLOSE) - break; - - p->s = saved; - } - } - - *tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VALUE_MAP; - parent->child = head; - - return parent; -} - -static int precedence(Token t, int flags) -{ - switch (t.type) { - - 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; - - case TOKEN_OPER_LSS: - if (flags & IGNORE_LSS) - return -1; - return 2; - - case TOKEN_OPER_GRT: - if (flags & IGNORE_GRT) - return -1; - return 2; - - case TOKEN_OPER_ADD: - case TOKEN_OPER_SUB: - return 3; - - case TOKEN_OPER_MUL: - case TOKEN_OPER_MOD: - return 4; - - case TOKEN_OPER_DIV: - if (flags & IGNORE_DIV) - return -1; - return 4; - - default: - break; - } - - return -1; -} - -static bool right_associative(Token t) -{ - return t.type == TOKEN_OPER_ASS; -} - -static Node *parse_atom(Parser *p) -{ - Token t = next_token(p); - - Node *ret; - switch (t.type) { - case TOKEN_OPER_ADD: - { - Node *child = parse_atom(p); - if (child == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_OPER_POS; - parent->left = child; - - ret = parent; - } - break; - - case TOKEN_OPER_SUB: - { - Node *child = parse_atom(p); - if (child == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_OPER_NEG; - parent->left = child; - - ret = parent; - } - 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); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_VAR; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_VALUE_INT: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_INT; - node->ival = t.ival; - - ret = node; - } - break; - - case TOKEN_VALUE_FLOAT: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_FLOAT; - node->fval = t.fval; - - ret = node; - } - break; - - case TOKEN_VALUE_STR: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_STR; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_KWORD_NONE: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_NONE; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_KWORD_TRUE: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_TRUE; - node->sval = t.sval; - - ret = node; - } - break; - case TOKEN_KWORD_FALSE: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_FALSE; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_OPER_LSS: - { - Node *node = parse_html(p); - if (node == NULL) - return NULL; - - ret = node; - } - break; - - case TOKEN_PAREN_OPEN: - { - Node *node = parse_expr(p, 0); - if (node == NULL) - return NULL; - - Token t = next_token(p); - if (t.type != TOKEN_PAREN_CLOSE) { - parser_report(p, "Missing ')' after expression"); - return NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_NESTED; - parent->left = node; - - ret = parent; - } - break; - - case TOKEN_BRACKET_OPEN: - { - Node *node = parse_array(p); - if (node == NULL) - return NULL; - - ret = node; - } - break; - - case TOKEN_CURLY_OPEN: - { - Node *node = parse_map(p); - if (node == NULL) - return NULL; - - ret = node; - } - break; - - case TOKEN_DOLLAR: - { - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing identifier after '$'"); - return NULL; - } - - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_SYSVAR; - node->sval = t.sval; - - ret = node; - } - break; - - default: - { - parser_report(p, "Invalid token inside expression"); - } - return NULL; - } - - for (;;) { - Scanner saved = p->s; - t = next_token(p); - if (t.type == TOKEN_DOT) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Invalid token after '.' where an identifier was expected"); - return NULL; - } - - Node *child = alloc_node(p); - if (child == NULL) - return NULL; - - child->type = NODE_VALUE_STR; - child->sval = t.sval; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_SELECT; - parent->left = ret; - parent->right = child; - - ret = parent; - - } else if (t.type == TOKEN_BRACKET_OPEN) { - - Node *child = parse_expr(p, 0); - if (child == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_BRACKET_CLOSE) { - parser_report(p, "Missing token ']'"); - return NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_SELECT; - parent->left = ret; - parent->right = child; - - ret = parent; - - } else if (t.type == TOKEN_PAREN_OPEN && (ret->type == NODE_VALUE_VAR || ret->type == NODE_VALUE_SYSVAR)) { - - Node *arg_head; - Node **arg_tail = &arg_head; - - Scanner saved = p->s; - t = next_token(p); - if (t.type != TOKEN_PAREN_CLOSE) { - - p->s = saved; - - for (;;) { - - Node *argval = parse_expr(p, 0); - if (argval == NULL) - return NULL; - - *arg_tail = argval; - arg_tail = &argval->next; - - t = next_token(p); - if (t.type == TOKEN_PAREN_CLOSE) - break; - - if (t.type != TOKEN_COMMA) { - parser_report(p, "Expected ',' after argument in procedure call"); - return NULL; - } - } - } - - *arg_tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_PROCEDURE_CALL; - parent->left = ret; - parent->right = arg_head; - - ret = parent; - - } else { - p->s = saved; - break; - } - } - - return ret; -} - -static Node *parse_expr_inner(Parser *p, Node *left, int min_prec, int flags) -{ - for (;;) { - - Scanner saved = p->s; - Token t1 = next_token_or_newline(p); - if (precedence(t1, flags) < min_prec) { - p->s = saved; - break; - } - - Node *right = parse_atom(p); - if (right == NULL) - return NULL; - - for (;;) { - - saved = p->s; - Token t2 = next_token_or_newline(p); - int p1 = precedence(t1, flags); - int p2 = precedence(t2, flags); - p->s = saved; - - if (p2 < 0) - break; - - if (p2 <= p1 && (p1 != p2 || !right_associative(t2))) - break; - - right = parse_expr_inner(p, right, p1 + (p2 > p1), flags); - if (right == NULL) - return NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->left = left; - parent->right = right; - - switch (t1.type) { - case TOKEN_OPER_ASS: parent->type = NODE_OPER_ASS; break; - case TOKEN_OPER_EQL: parent->type = NODE_OPER_EQL; break; - case TOKEN_OPER_NQL: parent->type = NODE_OPER_NQL; break; - case TOKEN_OPER_LSS: parent->type = NODE_OPER_LSS; break; - case TOKEN_OPER_GRT: parent->type = NODE_OPER_GRT; break; - case TOKEN_OPER_ADD: parent->type = NODE_OPER_ADD; break; - case TOKEN_OPER_SUB: parent->type = NODE_OPER_SUB; break; - 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; - } - - left = parent; - } - - return left; -} - -static Node *parse_expr(Parser *p, int flags) -{ - Node *left = parse_atom(p); - if (left == NULL) - return NULL; - - return parse_expr_inner(p, left, 0, flags); -} - -static Node *parse_expr_stmt(Parser *p, int opflags) -{ - Node *e = parse_expr(p, opflags); - if (e == NULL) - return NULL; - - return e; -} - -static Node *parse_ifelse_stmt(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_IF) { - parser_report(p, "Missing 'if' keyword before if statement"); - return NULL; - } - - Node *cond = parse_expr(p, 0); - if (cond == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing ':' after if condition"); - return NULL; - } - - Node *if_stmt = parse_stmt(p, opflags); - if (if_stmt == NULL) - return NULL; - - Scanner saved = p->s; - t = next_token(p); - - Node *else_stmt = NULL; - if (t.type == TOKEN_KWORD_ELSE) { - - else_stmt = parse_stmt(p, opflags); - if (else_stmt == NULL) - return NULL; - - } else { - p->s = saved; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_IFELSE; - parent->if_cond = cond; - parent->if_branch1 = if_stmt; - parent->if_branch2 = else_stmt; - - return parent; -} - -static Node *parse_for_stmt(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_FOR) { - parser_report(p, "Missing 'for' keyword at the start of a for statement"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing iteraion variable name in for statement"); - return NULL; - } - String var1 = t.sval; - - t = next_token(p); - - String var2 = S(""); - if (t.type == TOKEN_COMMA) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing iteration variable name after ',' in for statement"); - return NULL; - } - var2 = t.sval; - - t = next_token(p); - } - - if (t.type != TOKEN_KWORD_IN) { - parser_report(p, "Missing 'in' keyword after iteration variable name in for statement"); - return NULL; - } - - Node *set = parse_expr(p, 0); - if (set == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing ':' after for statement set expression"); - return NULL; - } - - Node *body = parse_stmt(p, opflags); - if (body == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_FOR; - parent->left = body; - parent->for_var1 = var1; - parent->for_var2 = var2; - parent->for_set = set; - - return parent; -} - -static Node *parse_while_stmt(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_WHILE) { - parser_report(p, "Missing keyword 'while' at the start of a while statement"); - return NULL; - } - - Node *cond = parse_expr(p, 0); - if (cond == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing token ':' after while statement condition"); - return NULL; - } - - Node *stmt = parse_stmt(p, opflags); - if (stmt == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_WHILE; - parent->left = stmt; - parent->while_cond = cond; - parent->while_body = stmt; - - return parent; -} - -static Node *parse_compound_stmt(Parser *p, bool global) -{ - if (!global) { - Token t = next_token(p); - if (t.type != TOKEN_CURLY_OPEN) { - parser_report(p, "Missing '{' at the start of a compound statement"); - return NULL; - } - } - - Node *head; - Node **tail = &head; - - for (;;) { - - Scanner saved = p->s; - Token t = next_token(p); - if (!global) { - if (t.type == TOKEN_CURLY_CLOSE) - break; - } else { - if (t.type == TOKEN_END) - break; - } - p->s = saved; - - Node *node = parse_stmt(p, 0); - if (node == NULL) - return NULL; - - *tail = node; - tail = &node->next; - } - - *tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = global ? NODE_GLOBAL : NODE_COMPOUND; - parent->left = head; - - return parent; -} - -static Node *parse_proc_decl(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_PROCEDURE) { - parser_report(p, "Missing keyword 'procedure' at the start of a procedure declaration"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing procedure name after 'procedure' keyword"); - return NULL; - } - String name = t.sval; - - t = next_token(p); - if (t.type != TOKEN_PAREN_OPEN) { - parser_report(p, "Missing '(' after procedure name in declaration"); - return NULL; - } - - Node *arg_head; - Node **arg_tail = &arg_head; - - Scanner saved = p->s; - t = next_token(p); - if (t.type != TOKEN_PAREN_CLOSE) { - p->s = saved; - - for (;;) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing argument name in procedure declaration"); - return NULL; - } - String argname = t.sval; - - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_PROCEDURE_ARG; - node->sval = argname; - - *arg_tail = node; - arg_tail = &node->next; - - Scanner saved = p->s; - t = next_token(p); - if (t.type == TOKEN_COMMA) { - saved = p->s; - t = next_token(p); - } - - if (t.type == TOKEN_PAREN_CLOSE) - break; - p->s = saved; - } - } - - *arg_tail = NULL; - - Node *body = parse_stmt(p, opflags); - if (body == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_PROCEDURE_DECL; - parent->proc_name = name; - parent->proc_args = arg_head; - parent->proc_body = body; - - return parent; -} - -static Node *parse_var_decl(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_LET) { - parser_report(p, "Missing keyword 'let' at the start of a variable declaration"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing variable name after 'let' keyword"); - return NULL; - } - String name = t.sval; - - Scanner saved = p->s; - t = next_token(p); - - Node *value; - if (t.type == TOKEN_OPER_ASS) { - - value = parse_expr(p, opflags); - if (value == NULL) - return NULL; - - } else { - p->s = saved; - value = NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VAR_DECL; - parent->var_name = name; - parent->var_value = value; - - return parent; -} - -static Node *parse_include_stmt(Parser *p) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_INCLUDE) { - parser_report(p, "Missing keyword 'include' at the start of an include statement"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_VALUE_STR) { - parser_report(p, "Missing file path string after 'include' keyword"); - return NULL; - } - String path = t.sval; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_INCLUDE; - parent->include_path = path; - parent->include_root = NULL; - - *p->include_tail = parent; - p->include_tail = &parent->include_next; - - return parent; -} - -static Node *parse_stmt(Parser *p, int opflags) -{ - Scanner saved = p->s; - Token t = next_token(p); - p->s = saved; - - switch (t.type) { - - case TOKEN_KWORD_INCLUDE: - return parse_include_stmt(p); - - case TOKEN_KWORD_PROCEDURE: - return parse_proc_decl(p, opflags); - - case TOKEN_KWORD_LET: - return parse_var_decl(p, opflags); - - case TOKEN_KWORD_IF: - return parse_ifelse_stmt(p, opflags); - - case TOKEN_KWORD_WHILE: - return parse_while_stmt(p, opflags); - - case TOKEN_KWORD_FOR: - return parse_for_stmt(p, opflags); - - case TOKEN_CURLY_OPEN: - return parse_compound_stmt(p, false); - - default: - break; - } - - return parse_expr_stmt(p, opflags); -} - -static void write_node(Writer *w, Node *node) -{ - switch (node->type) { - - case NODE_VALUE_NONE : write_text(w, S("none")); break; - case NODE_VALUE_TRUE : write_text(w, S("true")); break; - case NODE_VALUE_FALSE: write_text(w, S("false")); break; - - case NODE_NESTED: - write_text(w, S("(nested ")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_COMPOUND: - { - write_text(w, S("(compound ")); - Node *cur = node->left; - while (cur) { - write_node(w, cur); - cur = cur->next; - if (cur) - write_text(w, S(" ")); - } - write_text(w, S(")")); - } - break; - - case NODE_GLOBAL: - { - write_text(w, S("(global ")); - Node *cur = node->left; - while (cur) { - write_node(w, cur); - cur = cur->next; - if (cur) - write_text(w, S(" ")); - } - write_text(w, S(")")); - } - break; - - case NODE_OPER_LEN: - write_text(w, S("(len ")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_OPER_POS: - write_text(w, S("(+")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_OPER_NEG: - write_text(w, S("(")); - write_text(w, S("-")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_OPER_ASS: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("=")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_EQL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("==")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_NQL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("!=")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_LSS: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("<")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_GRT: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S(">")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_ADD: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("+")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_SUB: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("-")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_MUL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("*")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_DIV: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("/")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_MOD: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("%%")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_SHOVEL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("<<")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_VALUE_INT: - write_text_s64(w, node->ival); - break; - - case NODE_VALUE_FLOAT: - write_text_f64(w, node->fval); - break; - - case NODE_VALUE_STR: - write_text(w, S("\"")); - write_text(w, node->sval); - write_text(w, S("\"")); - break; - - case NODE_VALUE_VAR: - write_text(w, node->sval); - break; - - case NODE_VALUE_SYSVAR: - write_text(w, S("$")); - write_text(w, node->sval); - break; - - case NODE_IFELSE: - write_text(w, S("(if ")); - write_node(w, node->if_cond); - write_text(w, S(" ")); - write_node(w, node->if_branch1); - if (node->if_branch2) { - write_text(w, S(" else ")); - write_node(w, node->if_branch2); - } - write_text(w, S(")")); - break; - - case NODE_WHILE: - write_text(w, S("(while ")); - write_node(w, node->while_cond); - write_text(w, S(" ")); - write_node(w, node->while_body); - write_text(w, S(")")); - break; - - case NODE_VALUE_HTML: - { - write_text(w, S("(html ")); - write_text(w, node->html_tag); - - Node *child = node->html_child; - while (child) { - write_text(w, S(" ")); - write_node(w, child); - child = child->next; - } - - write_text(w, S(")")); - } - break; - - case NODE_FOR: - write_text(w, S("(for ")); - write_text(w, node->for_var1); - if (node->for_var2.len > 0) { - write_text(w, S(", ")); - write_text(w, node->for_var2); - } - write_text(w, S(" in ")); - write_node(w, node->for_set); - write_text(w, S(": ")); - write_node(w, node->left); - break; - - case NODE_SELECT: - write_node(w, node->left); - write_text(w, S("[")); - write_node(w, node->right); - write_text(w, S("]")); - break; - - case NODE_VALUE_ARRAY: - { - write_text(w, S("[")); - Node *child = node->child; - while (child) { - write_node(w, child); - write_text(w, S(", ")); - child = child->next; - } - write_text(w, S("]")); - } - break; - - case NODE_VALUE_MAP: - { - write_text(w, S("{")); - Node *child = node->child; - while (child) { - write_node(w, child->key); - write_text(w, S(": ")); - write_node(w, child); - write_text(w, S(", ")); - child = child->next; - } - write_text(w, S("}")); - } - break; - - case NODE_PROCEDURE_DECL: - { - write_text(w, S("(proc ")); - write_text(w, node->proc_name); - write_text(w, S("(")); - Node *arg = node->proc_args; - while (arg) { - write_node(w, arg); - arg = arg->next; - if (arg) - write_text(w, S(", ")); - } - write_text(w, S(")")); - write_node(w, node->proc_body); - } - break; - - case NODE_PROCEDURE_ARG: - write_text(w, node->sval); - break; - - case NODE_PROCEDURE_CALL: - { - write_node(w, node->left); - write_text(w, S("(")); - Node *arg = node->right; - while (arg) { - write_node(w, arg); - arg = arg->next; - if (arg) - write_text(w, S(", ")); - } - write_text(w, S(")")); - } - break; - - case NODE_VAR_DECL: - write_text(w, S("(let ")); - write_text(w, node->var_name); - if (node->var_value) { - write_text(w, S(" = ")); - write_node(w, node->var_value); - } - write_text(w, S(")")); - break; - - case NODE_INCLUDE: - write_text(w, S("include \"")); - write_text(w, node->include_path); - write_text(w, S("\"")); - break; - } -} - -static ParseResult parse(String src, WL_Arena *arena, char *errbuf, int errmax) -{ - Parser p = { - .s={ src.ptr, src.len, 0 }, - .arena=arena, - .errbuf=errbuf, - .errmax=errmax, - .errlen=0, - }; - - p.include_tail = &p.include_head; - - Node *node = parse_compound_stmt(&p, true); - if (node == NULL) - return (ParseResult) { .node=NULL, .includes=NULL, .errlen=p.errlen }; - - *p.include_tail = NULL; - return (ParseResult) { .node=node, .includes=p.include_head, .errlen=-1 }; -} - -///////////////////////////////////////////////////////////////////////// -// CODEGEN -///////////////////////////////////////////////////////////////////////// - -enum { - OPCODE_NOPE, - OPCODE_JUMP, - OPCODE_JIFP, - OPCODE_OUTPUT, - OPCODE_SYSVAR, - OPCODE_SYSCALL, - OPCODE_CALL, - OPCODE_RET, - OPCODE_GROUP, - OPCODE_PACK, - OPCODE_GPOP, - OPCODE_FOR, - OPCODE_EXIT, - OPCODE_VARS, - OPCODE_POP, - OPCODE_SETV, - OPCODE_PUSHV, - OPCODE_PUSHI, - OPCODE_PUSHF, - OPCODE_PUSHS, - OPCODE_PUSHA, - OPCODE_PUSHM, - OPCODE_PUSHN, - OPCODE_PUSHT, - OPCODE_PUSHFL, - OPCODE_LEN, - OPCODE_NEG, - OPCODE_EQL, - OPCODE_NQL, - OPCODE_LSS, - OPCODE_GRT, - OPCODE_ADD, - OPCODE_SUB, - OPCODE_MUL, - OPCODE_DIV, - OPCODE_MOD, - OPCODE_APPEND, - OPCODE_INSERT1, - OPCODE_INSERT2, - OPCODE_SELECT, -}; - -typedef struct UnpatchedCall UnpatchedCall; -struct UnpatchedCall { - UnpatchedCall *next; - String name; - int off; -}; - -typedef enum { - SYMBOL_VARIABLE, - SYMBOL_PROCEDURE, -} SymbolType; - -typedef struct { - SymbolType type; - String name; - bool cnst; - int off; -} Symbol; - -typedef enum { - SCOPE_IF, - SCOPE_ELSE, - SCOPE_FOR, - SCOPE_WHILE, - SCOPE_PROC, - SCOPE_COMPOUND, - SCOPE_GLOBAL, - SCOPE_ASSIGNMENT, -} ScopeType; - -typedef struct { - ScopeType type; - int idx_syms; - int max_vars; - UnpatchedCall *calls; -} Scope; - -#define MAX_SYMBOLS 1024 -#define MAX_SCOPES 128 -#define MAX_UNPATCHED_CALLS 32 - -typedef struct { - - Writer code; - Writer data; - - int num_scopes; - Scope scopes[MAX_SCOPES]; - - int num_syms; - Symbol syms[MAX_SYMBOLS]; - - UnpatchedCall *free_list_calls; - UnpatchedCall calls[MAX_UNPATCHED_CALLS]; - - bool err; - char *errmsg; - int errcap; - - int data_off; - -} Codegen; - -static void cg_report(Codegen *cg, char *fmt, ...) -{ - if (cg->err) return; - - va_list args; - va_start(args, fmt); - int len = vsnprintf(cg->errmsg, cg->errcap, fmt, args); - va_end(args); - - if (len > cg->errcap) - len = cg->errcap-1; - - cg->errmsg[len] = '\0'; - cg->err = true; -} - -static int cg_write_u8(Codegen *cg, uint8_t x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_u8(&cg->code, x); - return off; -} - -static int cg_write_u32(Codegen *cg, uint32_t x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_u32(&cg->code, x); - return off; -} - -static int cg_write_s64(Codegen *cg, int64_t x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_s64(&cg->code, x); - return off; -} - -static int cg_write_f64(Codegen *cg, double x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_f64(&cg->code, x); - return off; -} - -static void cg_write_str(Codegen *cg, String x) -{ - if (cg->err) return; - - int off = cg->data.len; - write_text(&cg->data, x); - write_raw_u32(&cg->code, off); - write_raw_u32(&cg->code, x.len); -} - -static void cg_patch_u8(Codegen *cg, int off, uint8_t x) -{ - if (cg->err) return; - - patch_mem(&cg->code, &x, off, SIZEOF(x)); -} - -static void cg_patch_u32(Codegen *cg, int off, uint32_t x) -{ - if (cg->err) return; - - patch_mem(&cg->code, &x, off, SIZEOF(x)); -} - -static uint32_t cg_current_offset(Codegen *cg) -{ - return cg->code.len; -} - -int count_nodes(Node *head) -{ - int n = 0; - Node *node = head; - while (node) { - n++; - node = node->next; - } - return n; -} - -static Scope *parent_scope(Codegen *cg) -{ - ASSERT(cg->num_scopes > 0); - - int parent = cg->num_scopes-1; - while (cg->scopes[parent].type != SCOPE_PROC && cg->scopes[parent].type != SCOPE_GLOBAL) - parent--; - - return &cg->scopes[parent]; -} - -static bool inside_assignment(Codegen *cg) -{ - ASSERT(cg->num_scopes > 0); - - int parent = cg->num_scopes-1; - while (cg->scopes[parent].type != SCOPE_PROC - && cg->scopes[parent].type != SCOPE_GLOBAL - && cg->scopes[parent].type != SCOPE_ASSIGNMENT) - parent--; - - return cg->scopes[parent].type == SCOPE_ASSIGNMENT; -} - -static int count_function_vars(Codegen *cg) -{ - int n = 0; - Scope *scope = parent_scope(cg); - for (int i = scope->idx_syms; i < cg->num_syms; i++) - if (cg->syms[i].type == SYMBOL_VARIABLE) - n++; - return n; -} - -static Symbol *cg_find_symbol(Codegen *cg, String name, bool local) -{ - if (cg->err) return NULL; - - if (name.len == 0) return NULL; - ASSERT(cg->num_scopes > 0); - Scope *scope = local ? &cg->scopes[cg->num_scopes-1] : parent_scope(cg); - for (int i = cg->num_syms-1; i >= scope->idx_syms; i--) - if (streq(cg->syms[i].name, name)) - return &cg->syms[i]; - return NULL; -} - -static int cg_declare_variable(Codegen *cg, String name, bool cnst) -{ - if (cg->err) return -1; - - Symbol *sym = cg_find_symbol(cg, name, true); - if (sym) { - cg_report(cg, "Variable declared twice"); - return -1; - } - - if (cg->num_syms == MAX_SYMBOLS) { - cg_report(cg, "Symbol count limit reached"); - return -1; - } - - int off = count_function_vars(cg); - - Scope *parent = parent_scope(cg); - parent->max_vars = MAX(parent->max_vars, off+1); - - cg->syms[cg->num_syms++] = (Symbol) { - .type = SYMBOL_VARIABLE, - .name = name, - .cnst = cnst, - .off = off, - }; - return off; -} - -static void cg_declare_procedure(Codegen *cg, String name, int off) -{ - if (cg->err) return; - - Symbol *sym = cg_find_symbol(cg, name, true); - if (sym) { - cg_report(cg, "Procedure declared twice"); - return; - } - - if (cg->num_syms == MAX_SYMBOLS) { - cg_report(cg, "Symbol count limit reached"); - return; - } - - cg->syms[cg->num_syms++] = (Symbol) { - .type = SYMBOL_PROCEDURE, - .name = name, - .cnst = true, - .off = off, - }; -} - -static void cg_push_scope(Codegen *cg, ScopeType type) -{ - if (cg->err) return; - - if (cg->num_scopes == MAX_SCOPES) { - cg_report(cg, "Scope limit reached"); - return; - } - - Scope *scope = &cg->scopes[cg->num_scopes++]; - scope->type = type; - scope->idx_syms = cg->num_syms; - scope->max_vars = 0; - scope->calls = NULL; -} - -static void cg_pop_scope(Codegen *cg) -{ - if (cg->err) return; - - 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]; - - while (scope->calls) { - - 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) { - if (parent_scope == NULL) { - cg_report(cg, "Undefined function '%.*s'", - scope->calls->name.len, - scope->calls->name.ptr); - return; - } - call->next = parent_scope->calls; - parent_scope->calls = call; - continue; - } - - if (sym->type != SYMBOL_PROCEDURE) { - cg_report(cg, "Symbol '%.*s' is not a procedure", call->name.len, call->name.ptr); - return; - } - - 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; - cg->num_scopes--; -} - -static void cg_append_unpatched_call(Codegen *cg, String name, int p) -{ - if (cg->err) return; - - if (cg->free_list_calls == NULL) { - cg_report(cg, "Out of memory"); - return; - } - 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; - scope->calls = call; -} - -static bool cg_global_scope(Codegen *cg) -{ - Scope *scope = parent_scope(cg); - return scope->type == SCOPE_GLOBAL; -} - -static void cg_flush_pushs(Codegen *cg) -{ - if (cg->data_off != -1) { - if (cg->data_off < cg->data.len) { - cg_write_u8(cg, OPCODE_PUSHS); - cg_write_u32(cg, cg->data_off); - cg_write_u32(cg, cg->data.len - cg->data_off); - } - cg->data_off = -1; - } -} - -static int cg_write_opcode(Codegen *cg, uint8_t opcode) -{ - ASSERT(opcode != OPCODE_PUSHS); - cg_flush_pushs(cg); - return cg_write_u8(cg, opcode); -} - -static void cg_write_pushs(Codegen *cg, String str, bool dont_group) -{ - if (dont_group) { - cg_flush_pushs(cg); - cg_write_u8(cg, OPCODE_PUSHS); - cg_write_str(cg, str); - } else { - if (cg->data_off == -1) - cg->data_off = cg->data.len; - write_raw_mem(&cg->data, str.ptr, str.len); - } -} - -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: - walk_expr_node(cg, node->left, one); - break; - - case NODE_OPER_LEN: - walk_expr_node(cg, node->left, true); - cg_write_opcode(cg, OPCODE_LEN); - break; - - case NODE_OPER_POS: - walk_expr_node(cg, node->left, one); - break; - - case NODE_OPER_NEG: - walk_expr_node(cg, node->left, true); - cg_write_opcode(cg, OPCODE_NEG); - break; - - case NODE_OPER_ASS: - { - Node *dst = node->left; - Node *src = node->right; - - if (dst->type == NODE_VALUE_VAR) { - - String name = dst->sval; - Symbol *sym = cg_find_symbol(cg, name, false); - if (sym == NULL) { - cg_report(cg, "Write to undeclared variable"); - return; - } - if (sym->type == SYMBOL_PROCEDURE) { - cg_report(cg, "Symbol is not a variable"); - return; - } - if (sym->cnst) { - cg_report(cg, "Variable is constant"); - return; - } - - cg_push_scope(cg, SCOPE_ASSIGNMENT); - walk_expr_node(cg, src, true); - cg_pop_scope(cg); - - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, sym->off); - - if (!one) - cg_write_opcode(cg, OPCODE_POP); - - } else if (dst->type == NODE_SELECT) { - - cg_push_scope(cg, SCOPE_ASSIGNMENT); - walk_expr_node(cg, src, true); - cg_pop_scope(cg); - - walk_expr_node(cg, dst->left, true); - walk_expr_node(cg, dst->right, true); - cg_write_opcode(cg, OPCODE_INSERT2); - - if (!one) - cg_write_opcode(cg, OPCODE_POP); - - } else { - - cg_report(cg, "Assignment left side can't be assigned to"); - return; - } - } - break; - - case NODE_OPER_SHOVEL: - { - 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); - cg_write_opcode(cg, OPCODE_EQL); - break; - - case NODE_OPER_NQL: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_NQL); - break; - - case NODE_OPER_LSS: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_LSS); - break; - - case NODE_OPER_GRT: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_GRT); - break; - - case NODE_OPER_ADD: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_ADD); - break; - - case NODE_OPER_SUB: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_SUB); - break; - - case NODE_OPER_MUL: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_MUL); - break; - - case NODE_OPER_DIV: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_DIV); - break; - - case NODE_OPER_MOD: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_MOD); - break; - - case NODE_VALUE_INT: - cg_write_opcode(cg, OPCODE_PUSHI); - cg_write_s64(cg, node->ival); - break; - - case NODE_VALUE_FLOAT: - cg_write_opcode(cg, OPCODE_PUSHF); - cg_write_f64(cg, node->fval); - break; - - case NODE_VALUE_STR: - cg_write_pushs(cg, node->sval, one); - break; - - case NODE_VALUE_NONE: - cg_write_opcode(cg, OPCODE_PUSHN); - break; - - case NODE_VALUE_TRUE: - cg_write_opcode(cg, OPCODE_PUSHT); - break; - - case NODE_VALUE_FALSE: - cg_write_opcode(cg, OPCODE_PUSHFL); - break; - - case NODE_VALUE_VAR: - { - String name = node->sval; - Symbol *sym = cg_find_symbol(cg, name, false); - if (sym == NULL) { - cg_report(cg, "Access to undeclared variable '%.*s'", name.len, name.ptr); - return; - } - if (sym->type == SYMBOL_PROCEDURE) { - cg_report(cg, "Symbol is not a variable"); - return; - } - - cg_write_opcode(cg, OPCODE_PUSHV); - cg_write_u8(cg, sym->off); - } - break; - - case NODE_VALUE_SYSVAR: - cg_write_opcode(cg, OPCODE_SYSVAR); - cg_write_str(cg, node->sval); - break; - - case NODE_VALUE_HTML: - { - if (one) - cg_write_opcode(cg, OPCODE_GROUP); - - cg_write_pushs(cg, S("<"), false); - cg_write_pushs(cg, node->html_tag, false); - - Node *child = node->html_attr; - while (child) { - walk_node(cg, child); - child = child->next; - } - - if (!node->html_body) { - cg_write_pushs(cg, S("/>"), false); - } else { - cg_write_pushs(cg, S(">"), false); - Node *child = node->html_child; - while (child) { - walk_node(cg, child); - child = child->next; - } - cg_write_pushs(cg, S("html_tag, false); - cg_write_pushs(cg, S(">"), false); - } - - if (one) - cg_write_opcode(cg, OPCODE_PACK); - } - break; - - case NODE_VALUE_ARRAY: - { - cg_write_opcode(cg, OPCODE_PUSHA); - cg_write_u32(cg, count_nodes(node->child)); - - Node *child = node->child; - while (child) { - walk_expr_node(cg, child, true); - cg_write_opcode(cg, OPCODE_APPEND); - child = child->next; - } - } - break; - - case NODE_VALUE_MAP: - { - cg_write_opcode(cg, OPCODE_PUSHM); - cg_write_u32(cg, count_nodes(node->child)); - - Node *child = node->child; - while (child) { - walk_expr_node(cg, child, true); - walk_expr_node(cg, child->key, true); - cg_write_opcode(cg, OPCODE_INSERT1); - child = child->next; - } - } - break; - - case NODE_SELECT: - { - Node *set = node->left; - Node *key = node->right; - walk_expr_node(cg, set, true); - walk_expr_node(cg, key, true); - cg_write_opcode(cg, OPCODE_SELECT); - } - break; - - case NODE_PROCEDURE_CALL: - { - if (one) - cg_write_opcode(cg, OPCODE_GROUP); - - int count = 0; - Node *arg = node->right; - while (arg) { - walk_expr_node(cg, arg, true); - count++; - arg = arg->next; - } - - Node *proc = node->left; - if (proc->type == NODE_VALUE_VAR) { - - cg_write_opcode(cg, OPCODE_CALL); - cg_write_u8(cg, count); - int p = cg_write_u32(cg, 0); - cg_append_unpatched_call(cg, proc->sval, p); - - } else { - - ASSERT(proc->type == NODE_VALUE_SYSVAR); - cg_write_opcode(cg, OPCODE_SYSCALL); - cg_write_u8(cg, count); - cg_write_str(cg, proc->sval); - } - - if (one) - cg_write_opcode(cg, OPCODE_PACK); - } - break; - - default: - UNREACHABLE; - } -} - -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: - for (Node *child = node->left; - child; child = child->next) { - walk_node(cg, child); - } - break; - - case NODE_COMPOUND: - cg_push_scope(cg, SCOPE_COMPOUND); - for (Node *child = node->left; - child; child = child->next) - walk_node(cg, child); - cg_pop_scope(cg); - break; - - case NODE_PROCEDURE_DECL: - { - cg_push_scope(cg, SCOPE_PROC); - - cg_write_opcode(cg, OPCODE_JUMP); - int off0 = cg_write_u32(cg, 0); - - #define MAX_ARGS 128 - - int num_args = 0; - Node *args[MAX_ARGS]; - - Node *arg = node->proc_args; - while (arg) { - if (num_args == MAX_ARGS) { - cg_report(cg, "Procedure argument limit reached"); - return; - } - args[num_args++] = arg; - arg = arg->next; - } - - for (int i = num_args-1; i >= 0; i--) - cg_declare_variable(cg, args[i]->sval, false); - - int off1 = cg_write_opcode(cg, OPCODE_VARS); - int off2 = cg_write_u8(cg, 0); - - walk_node(cg, node->proc_body); - cg_write_opcode(cg, OPCODE_RET); - - 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); - - cg_declare_procedure(cg, node->proc_name, off1); - } - break; - - case NODE_VAR_DECL: - { - int off = cg_declare_variable(cg, node->var_name, false); - if (node->var_value) { - cg_push_scope(cg, SCOPE_ASSIGNMENT); - walk_expr_node(cg, node->var_value, true); - cg_pop_scope(cg); - } else - cg_write_opcode(cg, OPCODE_PUSHN); - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, off); - cg_write_opcode(cg, OPCODE_POP); - } - break; - - case NODE_IFELSE: - { - // If there is no else branch: - // - // - // JIFP end - // - // end: - // ... - // - // If there is: - // - // - // JIFP else - // - // JUMP end - // else: - // - // end: - // ... - - if (node->if_branch2) { - - walk_expr_node(cg, node->if_cond, true); - - cg_write_opcode(cg, OPCODE_JIFP); - int p1 = cg_write_u32(cg, 0); - - cg_push_scope(cg, SCOPE_IF); - walk_node(cg, node->if_branch1); - cg_pop_scope(cg); - - cg_write_opcode(cg, OPCODE_JUMP); - int p2 = cg_write_u32(cg, 0); - - cg_flush_pushs(cg); - cg_patch_u32(cg, p1, cg_current_offset(cg)); - - cg_push_scope(cg, SCOPE_ELSE); - walk_node(cg, node->if_branch2); - cg_pop_scope(cg); - - cg_flush_pushs(cg); - cg_patch_u32(cg, p2, cg_current_offset(cg)); - - } else { - - walk_expr_node(cg, node->if_cond, true); - - cg_write_opcode(cg, OPCODE_JIFP); - int p1 = cg_write_u32(cg, 0); - - cg_push_scope(cg, SCOPE_IF); - walk_node(cg, node->if_branch1); - cg_pop_scope(cg); - - cg_flush_pushs(cg); - cg_patch_u32(cg, p1, cg_current_offset(cg)); - } - } - break; - - case NODE_FOR: - { - cg_push_scope(cg, SCOPE_FOR); - - int var_1 = cg_declare_variable(cg, node->for_var1, false); - int var_2 = cg_declare_variable(cg, node->for_var2, true); - int var_3 = cg_declare_variable(cg, (String) { NULL, 0 }, true); - - walk_expr_node(cg, node->for_set, true); - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, var_3); - cg_write_opcode(cg, OPCODE_POP); - - cg_write_opcode(cg, OPCODE_PUSHI); - cg_write_s64(cg, -1); - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, var_2); - cg_write_opcode(cg, OPCODE_POP); - - int start = cg_write_opcode(cg, OPCODE_FOR); - cg_write_u8(cg, var_3); - cg_write_u8(cg, var_1); - cg_write_u8(cg, var_2); - int p = cg_write_u32(cg, 0); - - walk_node(cg, node->left); - - cg_write_opcode(cg, OPCODE_JUMP); - cg_write_u32(cg, start); - - cg_patch_u32(cg, p, cg_current_offset(cg)); - - cg_pop_scope(cg); - } - break; - - case NODE_WHILE: - { - // start: - // - // JIFP end - // - // JUMP start - // end: - // ... - - int start = cg_current_offset(cg); - - walk_expr_node(cg, node->while_cond, true); - - cg_write_opcode(cg, OPCODE_JIFP); - int p = cg_write_u32(cg, 0); - - cg_push_scope(cg, SCOPE_WHILE); - walk_node(cg, node->left); - cg_pop_scope(cg); - - cg_write_opcode(cg, OPCODE_JUMP); - cg_write_u32(cg, start); - - cg_patch_u32(cg, p, cg_current_offset(cg)); - } - break; - - case NODE_INCLUDE: - walk_node(cg, node->include_root); - break; - - default: - walk_expr_node(cg, node, false); - if (cg_global_scope(cg) && !inside_assignment(cg)) - cg_write_opcode(cg, OPCODE_OUTPUT); - break; - } -} - -#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 = { - .code = { dst, cap/2, 0 }, - .data = { dst + cap/2, cap/2, 0 }, - .num_scopes = 0, - .err = false, - .errmsg = errmsg, - .errcap = errcap, - .data_off = -1, - }; - - cg.free_list_calls = cg.calls; - for (int i = 0; i < MAX_UNPATCHED_CALLS-1; i++) - 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); - int off = cg_write_u8(&cg, 0); - walk_node(&cg, node); - cg_write_opcode(&cg, OPCODE_EXIT); - cg_patch_u8(&cg, off, cg.scopes[0].max_vars); - cg_pop_scope(&cg); - - if (cg.err) - return -1; - - if (hdr) { - - uint32_t magic = WL_MAGIC; - uint32_t code_len = cg.code.len; - uint32_t data_len = cg.data.len; - memcpy(hdr + 0, &magic , sizeof(uint32_t)); - memcpy(hdr + 4, &code_len, sizeof(uint32_t)); - memcpy(hdr + 8, &data_len, sizeof(uint32_t)); - - if (cg.code.len + cg.data.len <= cap) - memmove(dst + cg.code.len, dst + cap/2, cg.data.len); - } - - return cg.code.len + cg.data.len + SIZEOF(uint32_t) * 3; -} - -static int write_instr(Writer *w, char *src, int len, String data) -{ - if (len == 0) - return -1; - - switch (src[0]) { - - uint8_t b0; - uint8_t b1; - uint8_t b2; - uint32_t w0; - uint32_t w1; - int64_t i; - double d; - - case OPCODE_NOPE: - write_text(w, S("NOPE\n")); - return 1; - - case OPCODE_JUMP: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - write_text(w, S("JUMP ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_JIFP: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - write_text(w, S("JIFP ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_OUTPUT: - write_text(w, S("OUTPUT\n")); - return 1; - - case OPCODE_SYSVAR: - if (len < 9) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - memcpy(&w1, src + 5, sizeof(uint32_t)); - write_text(w, S("SYSVAR \"")); - write_text(w, (String) { data.ptr + w0, w1 }); - write_text(w, S("\"\n")); - return 9; - - case OPCODE_SYSCALL: - if (len < 10) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - memcpy(&w0, src + 2, sizeof(uint32_t)); - memcpy(&w1, src + 6, sizeof(uint32_t)); - write_text(w, S("SYSCALL ")); - write_text_s64(w, b0); - write_text(w, S(" \"")); - write_text(w, (String) { data.ptr + w0, w1 }); - write_text(w, S("\"\n")); - return 10; - - case OPCODE_CALL: - if (len < 6) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - memcpy(&w0, src + 2, sizeof(uint32_t)); - write_text(w, S("CALL ")); - write_text_s64(w, b0); - write_text(w, S(" ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 6; - - case OPCODE_RET: - write_text(w, S("RET\n")); - return 1; - - case OPCODE_GROUP: - write_text(w, S("GROUP\n")); - return 1; - - case OPCODE_PACK: - write_text(w, S("PACK\n")); - return 1; - - case OPCODE_GPOP: - write_text(w, S("GPOP\n")); - return 1; - - case OPCODE_FOR: - if (len < 8) return -1; - memcpy(&b0, src + 1, sizeof(b0)); - memcpy(&b1, src + 2, sizeof(b1)); - memcpy(&b2, src + 3, sizeof(b2)); - memcpy(&w0, src + 4, sizeof(w0)); - write_text(w, S("FOR ")); - write_text_s64(w, b0); - write_text(w, S(" ")); - write_text_s64(w, b1); - write_text(w, S(" ")); - write_text_s64(w, b2); - write_text(w, S(" ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 8; - - case OPCODE_EXIT: - write_text(w, S("EXIT\n")); - return 1; - - case OPCODE_VARS: - if (len < 2) return -1; - memcpy(&b0, src + 1, sizeof(b0)); - write_text(w, S("VARS ")); - write_text_s64(w, b0); - write_text(w, S("\n")); - return 2; - - case OPCODE_POP: - write_text(w, S("POP\n")); - return 1; - - case OPCODE_SETV: - if (len < 2) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - write_text(w, S("SETV ")); - write_text_s64(w, b0); - write_text(w, S("\n")); - return 2; - - case OPCODE_PUSHV: - if (len < 2) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - write_text(w, S("PUSHV ")); - write_text_s64(w, b0); - write_text(w, S("\n")); - return 2; - - case OPCODE_PUSHI: - if (len < 9) return -1; - memcpy(&i, src + 1, sizeof(int64_t)); - write_text(w, S("PUSHI ")); - write_text_s64(w, i); - write_text(w, S("\n")); - return 9; - - case OPCODE_PUSHF: - if (len < 9) return -1; - memcpy(&d, src + 1, sizeof(double)); - write_text(w, S("PUSHF ")); - write_text_f64(w, d); - write_text(w, S("\n")); - return 9; - - case OPCODE_PUSHS: - if (len < 9) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - memcpy(&w1, src + 5, sizeof(uint32_t)); - write_text(w, S("PUSHS \"")); - write_text(w, (String) { data.ptr + w0, w1 }); - write_text(w, S("\"\n")); - return 9; - - case OPCODE_PUSHA: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(w0)); - write_text(w, S("PUSHA ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_PUSHM: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(w0)); - write_text(w, S("PUSHM ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_PUSHN: - write_text(w, S("PUSHN\n")); - return 1; - - case OPCODE_PUSHT: - write_text(w, S("PUSHT\n")); - return 1; - - case OPCODE_PUSHFL: - write_text(w, S("PUSHFL\n")); - return 1; - - case OPCODE_LEN: - write_text(w, S("LEN\n")); - return 1; - - case OPCODE_NEG: - write_text(w, S("NEG\n")); - return 1; - - case OPCODE_EQL: - write_text(w, S("EQL\n")); - return 1; - - case OPCODE_NQL: - write_text(w, S("NQL\n")); - return 1; - - case OPCODE_LSS: - write_text(w, S("LSS\n")); - return 1; - - case OPCODE_GRT: - write_text(w, S("GRT\n")); - return 1; - - case OPCODE_ADD: - write_text(w, S("ADD\n")); - return 1; - - case OPCODE_SUB: - write_text(w, S("SUB\n")); - return 1; - - case OPCODE_MUL: - write_text(w, S("MUL\n")); - return 1; - - case OPCODE_DIV: - write_text(w, S("DIV\n")); - return 1; - - case OPCODE_MOD: - write_text(w, S("MOD\n")); - return 1; - - case OPCODE_APPEND: - write_text(w, S("APPEND\n")); - return 1; - - case OPCODE_INSERT1: - write_text(w, S("INSERT1\n")); - return 1; - - case OPCODE_INSERT2: - write_text(w, S("INSERT2\n")); - return 1; - - 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; -} - -static int write_program(WL_Program program, char *dst, int cap) -{ - if ((uint32_t) program.len < 3 * sizeof(uint32_t)) - return -1; - - uint32_t magic; - uint32_t code_len; - uint32_t data_len; - - memcpy(&magic , program.ptr + 0, sizeof(uint32_t)); - memcpy(&code_len, program.ptr + 4, sizeof(uint32_t)); - memcpy(&data_len, program.ptr + 8, sizeof(uint32_t)); - - if (magic != WL_MAGIC) - return -1; - - if (code_len + data_len + 3 * sizeof(uint32_t) != (uint32_t) program.len) - return -1; - - String code = { program.ptr + 3 * sizeof(uint32_t) , code_len }; - String data = { program.ptr + 3 * sizeof(uint32_t) + code_len, data_len }; - - Writer w = { dst, cap, 0 }; - - int cur = 0; - while (cur < code.len) { - write_text_s64(&w, cur); - write_text(&w, S(": ")); - int ret = write_instr(&w, code.ptr + cur, code.len - cur, data); - if (ret < 0) return -1; - cur += ret; - } - - 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 -///////////////////////////////////////////////////////////////////////// - -#define FILE_LIMIT 128 - -typedef struct { - String file; - Node* root; - Node* includes; -} CompiledFile; - -struct WL_Compiler { - - WL_Arena* arena; - CompiledFile files[FILE_LIMIT]; - int num_files; - String waiting_file; - - bool err; - char msg[1<<8]; -}; - -WL_Compiler *wl_compiler_init(WL_Arena *arena) -{ - WL_Compiler *compiler = alloc(arena, SIZEOF(WL_Compiler), _Alignof(WL_Compiler)); - if (compiler == NULL) - return NULL; - compiler->arena = arena; - compiler->num_files = 0; - compiler->waiting_file = (String) { NULL, 0 }; - compiler->err = false; - return compiler; -} - -WL_AddResult wl_compiler_add(WL_Compiler *compiler, WL_String content) -{ - if (compiler->err) - return (WL_AddResult) { .type=WL_ADD_ERROR }; - - ParseResult pres = parse((String) { content.ptr, content.len }, compiler->arena, compiler->msg, SIZEOF(compiler->msg)); - if (pres.node == NULL) { - compiler->err = true; - return (WL_AddResult) { .type=WL_ADD_ERROR }; - } - - CompiledFile compiled_file = { - .file = compiler->waiting_file, - .root = pres.node, - .includes = pres.includes, - }; - compiler->files[compiler->num_files++] = compiled_file; - compiler->waiting_file = (String) { NULL, 0 }; - - for (int i = 0; i < compiler->num_files; i++) { - - Node *include = compiler->files[i].includes; - while (include) { - - ASSERT(include->type == NODE_INCLUDE); - - if (include->include_root == NULL) { - for (int j = 0; j < compiler->num_files; j++) { - if (streq(include->include_path, compiler->files[j].file)) { - include->include_root = compiler->files[j].root; - break; - } - } - } - - if (include->include_root == NULL) { - - if (compiler->num_files == FILE_LIMIT) { - ASSERT(0); // TODO - } - - // TODO: Make the path relative to the compiled file - - compiler->waiting_file = include->include_path; - return (WL_AddResult) { .type=WL_ADD_AGAIN, .path={ include->include_path.ptr, include->include_path.len } }; - } - - include = include->include_next; - } - } - - return (WL_AddResult) { .type=WL_ADD_LINK }; -} - -int wl_compiler_link(WL_Compiler *compiler, WL_Program *program) -{ - if (compiler->err) return -1; - - if (compiler->num_files == 0 || compiler->waiting_file.len > 0) { - int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Missing files in compilation unit"); - if (len > SIZEOF(compiler->msg)) - len = SIZEOF(compiler->msg)-1; - compiler->msg[len] = '\0'; - compiler->err = true; - return -1; - } - - char *dst = compiler->arena->ptr + compiler->arena->cur; - int cap = compiler->arena->len - compiler->arena->cur; - - int len = codegen(compiler->files[0].root, dst, cap, compiler->msg, SIZEOF(compiler->msg)); - if (len < 0) { - compiler->err = true; - return -1; - } - if (len > cap) { - int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Out of memory"); - if (len > SIZEOF(compiler->msg)) - len = SIZEOF(compiler->msg)-1; - compiler->msg[len] = '\0'; - compiler->err = true; - return -1; - } - - *program = (WL_Program) { dst, len }; - - compiler->arena->cur += len; - return 0; -} - -WL_String wl_compiler_error(WL_Compiler *compiler) -{ - return compiler->err - ? (WL_String) { compiler->msg, strlen(compiler->msg) } - : (WL_String) { NULL, 0 }; -} - -int wl_dump_ast(WL_Compiler *compiler, char *dst, int cap) -{ - Writer w = { dst, cap, 0 }; - for (int i = 0; i < compiler->num_files; i++) { - write_text(&w, S("(file \"")); - write_text(&w, compiler->files[i].file); - write_text(&w, S("\" ")); - write_node(&w, compiler->files[i].root); - write_text(&w, S(")")); - } - return w.len; -} - -///////////////////////////////////////////////////////////////////////// -// OBJECT MODEL -///////////////////////////////////////////////////////////////////////// - -typedef enum { - TYPE_NONE, - TYPE_BOOL, - TYPE_INT, - TYPE_FLOAT, - TYPE_STRING, - TYPE_ARRAY, - TYPE_MAP, - TYPE_ERROR, -} Type; - -#define TAG_ERROR 0 -#define TAG_POSITIVE_INT 1 -#define TAG_NEGATIVE_INT 2 -#define TAG_BOOL 3 -#define TAG_NONE 4 -#define TAG_PTR 5 - -#define VALUE_NONE ((0 << 3) | TAG_NONE) -#define VALUE_TRUE ((0 << 3) | TAG_BOOL) -#define VALUE_FALSE ((1 << 3) | TAG_BOOL) -#define VALUE_ERROR ((0 << 3) | TAG_ERROR) - -typedef uint64_t Value; - -typedef struct Extension Extension; -struct Extension { - Extension *next; - int count; - int capacity; - Value vals[]; -}; - -typedef struct { - Type type; - int count; - int capacity; - Extension *ext; - Value vals[]; -} AggregateValue; - -typedef struct { - Type type; - double raw; -} FloatValue; - -typedef struct { - Type type; - int64_t raw; -} IntValue; - -typedef struct { - Type type; - int len; - char data[]; -} StringValue; - -static int value_convert_to_str(Value v, char *dst, int cap); - -static Type value_type(Value v) -{ - switch (v & 7) { - case TAG_ERROR: return TYPE_ERROR; - case TAG_POSITIVE_INT: return TYPE_INT; - case TAG_NEGATIVE_INT: return TYPE_INT; - case TAG_BOOL : return TYPE_BOOL; - case TAG_NONE : return TYPE_NONE; - case TAG_PTR : return *(Type*) (v & ~(Value) 7); break; - } - return TAG_ERROR; -} - -static int64_t value_to_s64(Value v) -{ - ASSERT(value_type(v) == TYPE_INT); - - if ((v & 7) == TAG_POSITIVE_INT) - return (int64_t) (v >> 3); - - if ((v & 7) == TAG_NEGATIVE_INT) - return (int64_t) ((v >> 3) | ((Value) 7 << 61)); - - IntValue *p = (IntValue*) (v & ~(Value) 7); - return p->raw; -} - -static double value_to_f64(Value v) -{ - ASSERT(value_type(v) == TYPE_FLOAT); - - FloatValue *p = (FloatValue*) (v & ~(Value) 7); - return p->raw; -} - -static String value_to_str(Value v) -{ - ASSERT(value_type(v) == TYPE_STRING); - - StringValue *p = (StringValue*) (v & ~(Value) 7); - return (String) { p->data, p->len }; -} - -/* - -2 bits -> 2^2 = 4 - -00000 0 . -00001 1 . -00010 2 . -00011 3 . -00100 4 . -00101 5 . -00110 6 . -00111 7 . -01000 8 -01001 9 -01010 10 -01011 11 -01100 12 -01101 13 -01110 14 -01111 15 -10000 -16 -10001 -15 -10010 -14 -10011 -13 -10100 -12 -10101 -11 -10110 -10 -10111 -9 -11000 -8 . -11001 -7 . -11010 -6 . -11011 -5 . -11100 -4 . -11101 -3 . -11110 -2 . -11111 -1 . - -*/ - -static Value value_from_s64(int64_t x, WL_Arena *arena, Error *err) -{ - Value v = (Value) x; - Value upper3bits = v >> 61; - - if (upper3bits == 0) - return (v << 3) | TAG_POSITIVE_INT; - - if (upper3bits == 7) - return (v << 3) | TAG_NEGATIVE_INT; - - IntValue *p = alloc(arena, SIZEOF(IntValue), _Alignof(IntValue)); - if (p == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - p->type = TYPE_INT; - p->raw = x; - - ASSERT(((Value) p & 7) == 0); - return ((Value) p) | TAG_PTR; -} - -static Value value_from_f64(double x, WL_Arena *arena, Error *err) -{ - FloatValue *v = alloc(arena, SIZEOF(FloatValue), _Alignof(FloatValue)); - if (v == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - v->type = TYPE_FLOAT; - v->raw = x; - - ASSERT(((uintptr_t) v & 7) == 0); - return ((Value) v) | TAG_PTR; -} - -static Value value_from_str(String x, WL_Arena *arena, Error *err) -{ - StringValue *v = alloc(arena, SIZEOF(StringValue) + x.len, 8); - if (v == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - v->type = TYPE_STRING; - v->len = x.len; - memcpy(v->data, x.ptr, x.len); - - ASSERT(((uintptr_t) v & 7) == 0); - return ((Value) v) | TAG_PTR; -} - -static Value aggregate_empty(bool map, uint32_t cap, WL_Arena *arena, Error *err) -{ - AggregateValue *v = alloc(arena, SIZEOF(AggregateValue) + 2 * cap * SIZEOF(Value), MAX(_Alignof(AggregateValue), 8)); - if (v == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - v->type = map ? TYPE_MAP : TYPE_ARRAY; - v->count = 0; - v->capacity = cap; - v->ext = NULL; - - ASSERT(((uintptr_t) v & 7) == 0); - return ((Value) v) | TAG_PTR; -} - -static int64_t aggregate_length(AggregateValue *agg) -{ - int64_t n = agg->count; - - Extension *ext = agg->ext; - while (ext) { - n += ext->count; - ext = ext->next; - } - - return n; -} - -static Value *aggregate_select_by_raw_index(AggregateValue *agg, int64_t idx) -{ - ASSERT(agg->type == TYPE_ARRAY || agg->type == TYPE_MAP); - - if (idx < 0 || idx >= aggregate_length(agg)) - return NULL; - - if (idx < agg->count) - return &agg->vals[idx]; - - idx -= agg->count; - Extension *ext = agg->ext; - while (ext) { - if (idx < ext->count) - return &ext->vals[idx]; - idx -= ext->count; - ext = ext->next; - } - - UNREACHABLE; - return NULL; -} - -static bool value_eql(Value a, Value b); - -static Value *aggregate_select(AggregateValue *agg, Value key) -{ - if (agg->type == TYPE_MAP) { - - for (int i = 0; i < agg->count; i += 2) - if (value_eql(agg->vals[i], key)) - return &agg->vals[i+1]; - - Extension *ext = agg->ext; - while (ext) { - for (int i = 0; i < ext->count; i += 2) - if (value_eql(ext->vals[i], key)) { - return &ext->vals[i+1]; - } - ext = ext->next; - } - - return NULL; - - } else { - - ASSERT(agg->type == TYPE_ARRAY); - - if (value_type(key) != TYPE_INT) - return NULL; - int64_t idx = value_to_s64(key); - - return aggregate_select_by_raw_index(agg, idx); - } -} - -static bool aggregate_append(AggregateValue *agg, Value v1, Value v2, WL_Arena *arena) -{ - if (agg->count < agg->capacity) { - agg->vals[agg->count++] = v1; - if (v2 != VALUE_ERROR) - agg->vals[agg->count++] = v2; - return true; - } - - Extension *tail = agg->ext; - if (tail) - while (tail->next) - tail = tail->next; - - Extension *ext; - if (tail == NULL || tail->count == tail->capacity) { - - int cap = 8; - ext = alloc(arena, SIZEOF(Extension) + cap * sizeof(Value), ALIGNOF(Extension)); - if (ext == NULL) - return false; - - ext->count = 0; - ext->capacity = cap; - ext->next = NULL; - - if (tail) - tail->next = ext; - else - agg->ext = ext; - - } else - ext = tail; - - ext->vals[ext->count++] = v1; - if (v2 != VALUE_ERROR) - ext->vals[ext->count++] = v2; - return true; -} - -static Value value_empty_map(uint32_t cap, WL_Arena *arena, Error *err) -{ - return aggregate_empty(true, 2 * cap, arena, err); -} - -static Value value_empty_array(uint32_t cap, WL_Arena *arena, Error *err) -{ - return aggregate_empty(false, cap, arena, err); -} - -static int64_t value_length(Value set) -{ - ASSERT(value_type(set) == TYPE_MAP || value_type(set) == TYPE_ARRAY); - AggregateValue *agg = (void*) (set & ~(Value) 7); - int64_t len = aggregate_length(agg); - if (agg->type == TYPE_MAP) - len /= 2; - return len; -} - -static bool value_insert(Value set, Value key, Value val, WL_Arena *arena, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_MAP && t != TYPE_ARRAY) { - REPORT(err, "Invalid insertion on non-map and non-array value"); - return false; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - Value *dst = aggregate_select(agg, key); - if (dst != NULL) { - *dst = val; - return true; - } - - if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) { - REPORT(err, "Invalid index used in array access"); - return false; - } - - if (!aggregate_append(agg, key, val, arena)) { - REPORT(err, "Out of memory"); - return false; - } - - return true; -} - -static Value value_select(Value set, Value key, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_MAP && t != TYPE_ARRAY) { - REPORT(err, "Invalid selection from non-map and non-array value"); - return VALUE_ERROR; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - Value *dst = aggregate_select(agg, key); - if (dst) return *dst; - - if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) { - REPORT(err, "Invalid index used in array access"); - return VALUE_ERROR; - } - - 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 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'", key_buf, set_buf); - return VALUE_ERROR; -} - -static Value value_select_by_index(Value set, int64_t idx, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_MAP && t != TYPE_ARRAY) { - REPORT(err, "Invalid selection from non-map and non-array value"); - return VALUE_ERROR; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - if (agg->type == TYPE_MAP) - idx *= 2; - - Value *src = aggregate_select_by_raw_index(agg, idx); - if (src == NULL) { - REPORT(err, "Invalid selection from non-map and non-array value"); - return VALUE_ERROR; - } - - return *src; -} - -static bool value_append(Value set, Value val, WL_Arena *arena, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_ARRAY) { - REPORT(err, "Invalid append on non-array value"); - return false; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - if (!aggregate_append(agg, val, VALUE_ERROR, arena)) { - REPORT(err, "Out of memory"); - return false; - } - - return true; -} - -static bool value_eql(Value a, Value b) -{ - Type t1 = value_type(a); - Type t2 = value_type(b); - - if (t1 != t2) - return false; - - switch (t1) { - - case TYPE_NONE: - return true; - - case TYPE_BOOL: - return a == b; - - case TYPE_INT: - return value_to_s64(a) == value_to_s64(b); - - case TYPE_FLOAT: - return value_to_f64(a) == value_to_f64(b); - - case TYPE_MAP: - return false; // TODO - - case TYPE_ARRAY: - return false; // TODO - - case TYPE_STRING: - return streq(value_to_str(a), value_to_str(b)); - - case TYPE_ERROR: - return true; - } - - return false; -} - -static bool value_nql(Value a, Value b) -{ - return !value_eql(a, b); -} - -#define TYPE_PAIR(X, Y) (((uint16_t) (X) << 16) | (uint16_t) (Y)) - -bool value_greater(Value a, Value b, Error *err) -{ - Type t1 = value_type(a); - Type t2 = value_type(b); - switch (TYPE_PAIR(t1, t2)) { - case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) > value_to_s64(b); - case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) > value_to_f64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) > value_to_s64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) > value_to_f64(b); - default:break; - } - REPORT(err, "Invalid '>' operation on non-numeric type"); - return false; -} - -bool value_lower(Value a, Value b, Error *err) -{ - Type t1 = value_type(a); - Type t2 = value_type(b); - switch (TYPE_PAIR(t1, t2)) { - case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) < value_to_s64(b); - case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) < value_to_f64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) < value_to_s64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) < value_to_f64(b); - default:break; - } - REPORT(err, "Invalid '<' operation on non-numeric type"); - return false; -} - -static Value value_neg(Value v, WL_Arena *arena, Error *err) -{ - Type t = value_type(v); - if (t == TYPE_INT) - return value_from_s64(-value_to_s64(v), arena, err); // TODO: overflow - - if (t == TYPE_FLOAT) - return value_from_f64(-value_to_f64(v), arena, err); - - REPORT(err, "Invalid '-' operation on non-numeric type"); - return VALUE_ERROR; -} - -static Value value_add(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - // TODO: check overflow and underflow - r = value_from_s64(u + v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u + v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u + v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - // TODO: check overflow and underflow - r = value_from_f64(u + v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '+' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_sub(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - // TODO: check overflow and underflow - r = value_from_s64(u - v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u - v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u - v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - // TODO: check overflow and underflow - r = value_from_f64(u - v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '-' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_mul(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - // TODO: check overflow and underflow - r = value_from_s64(u * v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u * v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u * v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - // TODO: check overflow and underflow - r = value_from_f64(u * v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '*' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_div(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - // TODO: check division by 0 - - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - r = value_from_s64(u / v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - // TODO: check division by 0 - - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u / v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - // TODO: check division by 0 - - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u / v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u / v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '/' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_mod(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - if (t1 != TYPE_INT || t2 != TYPE_INT) { - REPORT(err, "Invalid operation '%%' on non-integer value"); - return VALUE_ERROR; - } - - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - Value r = value_from_s64(u % v, arena, err); - return r; -} - -static void value_convert_to_str_inner(Writer *w, Value v) -{ - Type t = value_type(v); - switch (t) { - - case TYPE_NONE: - break; - - case TYPE_BOOL: - write_text(w, v == VALUE_TRUE ? S("true") : S("false")); - break; - - case TYPE_INT: - write_text_s64(w, value_to_s64(v)); - break; - - case TYPE_FLOAT: - write_text_f64(w, value_to_f64(v)); - break; - - case TYPE_STRING: - write_text(w, value_to_str(v)); - break; - - case TYPE_ARRAY: - { - AggregateValue *agg = (void*) (v & ~(Value) 7); - for (int i = 0; i < agg->count; i++) - value_convert_to_str_inner(w, agg->vals[i]); - Extension *ext = agg->ext; - while (ext) { - for (int i = 0; i < ext->count; i++) - value_convert_to_str_inner(w, ext->vals[i]); - ext = ext->next; - } - } - break; - - case TYPE_MAP: - { - write_text(w, S("{")); - AggregateValue *agg = (void*) (v & ~(Value) 7); - for (int i = 0; i < agg->count; i += 2) { - value_convert_to_str_inner(w, agg->vals[i+0]); - write_text(w, S(": ")); - value_convert_to_str_inner(w, agg->vals[i+1]); - if (i+2 < agg->count || agg->ext) - write_text(w, S(", ")); - } - Extension *ext = agg->ext; - while (ext) { - for (int i = 0; i < ext->count; i += 2) { - value_convert_to_str_inner(w, ext->vals[i+0]); - write_text(w, S(": ")); - value_convert_to_str_inner(w, ext->vals[i+1]); - if (i+2 < ext->count || ext->next) - write_text(w, S(", ")); - } - ext = ext->next; - } - write_text(w, S("}")); - } - break; - - case TYPE_ERROR: - break; - } -} - -static int value_convert_to_str(Value v, char *dst, int cap) -{ - Writer w = { dst, cap, 0}; - value_convert_to_str_inner(&w, v); - return w.len; -} - -#undef TYPE_PAIR - -///////////////////////////////////////////////////////////////////////// -// RUNTIME -///////////////////////////////////////////////////////////////////////// - -#define MAX_STACK 1024 -#define MAX_FRAMES 1024 -#define MAX_GROUPS 8 - -typedef struct { - int retaddr; - int varbase; -} Frame; - -typedef enum { - RUNTIME_BEGIN, - RUNTIME_LOOP, - RUNTIME_DONE, - RUNTIME_ERROR, - RUNTIME_OUTPUT, - RUNTIME_SYSVAR, - RUNTIME_SYSCALL, -} RuntimeState; - -struct WL_Runtime { - - RuntimeState state; - - String code; - String data; - int off; - - int vars; - int stack; - Value values[MAX_STACK]; - - int num_frames; - Frame frames[MAX_FRAMES]; - - int num_groups; - int groups[MAX_GROUPS]; - - WL_Arena *arena; - - char msg[128]; - Error err; - - int stack_before_user; - String str_for_user; - int num_output; - int cur_output; - char buf[128]; -}; - -WL_Runtime *wl_runtime_init(WL_Arena *arena, WL_Program program) -{ - if ((uint32_t) program.len < 3 * sizeof(uint32_t)) - return NULL; - - uint32_t magic; - uint32_t code_len; - uint32_t data_len; - - memcpy(&magic , program.ptr + 0, sizeof(uint32_t)); - memcpy(&code_len, program.ptr + 4, sizeof(uint32_t)); - memcpy(&data_len, program.ptr + 8, sizeof(uint32_t)); - - if (magic != WL_MAGIC) - return NULL; - - String code = { program.ptr + sizeof(uint32_t) * 3 , code_len }; - String data = { program.ptr + sizeof(uint32_t) * 3 + code_len, data_len }; - - WL_Runtime *rt = alloc(arena, SIZEOF(WL_Runtime), ALIGNOF(WL_Runtime)); - if (rt == NULL) - return NULL; - - *rt = (WL_Runtime) { - .state = RUNTIME_BEGIN, - .code = code, - .data = data, - .off = 0, - .stack = 0, - .vars = MAX_STACK-1, - .num_frames = 0, - .arena = arena, - .err = { NULL, 0, false }, - }; - rt->err.buf = rt->msg; - rt->err.cap = SIZEOF(rt->msg); - - rt->frames[rt->num_frames++] = (Frame) { - .retaddr = 0, - .varbase = rt->vars, - }; - - return rt; -} - -WL_String wl_runtime_error(WL_Runtime *rt) -{ - return rt->err.yes - ? (WL_String) { rt->msg, strlen(rt->msg) } - : (WL_String) { NULL, 0 }; -} - -static void rt_read_mem(WL_Runtime *r, void *dst, int len) -{ - ASSERT(r->off + len <= r->code.len); - memcpy(dst, r->code.ptr + r->off, len); - r->off += len; -} - -static uint8_t rt_read_u8(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - uint8_t x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static uint32_t rt_read_u32(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - uint32_t x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static int64_t rt_read_s64(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - int64_t x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static double rt_read_f64(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - double x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static String rt_read_str(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - uint32_t off = rt_read_u32(rt); - uint32_t len = rt_read_u32(rt); - ASSERT(off + len <= (uint32_t) rt->data.len); - return (String) { rt->data.ptr + off, len }; -} - -static Value *rt_variable(WL_Runtime *rt, uint8_t x) -{ - ASSERT(rt->num_frames > 0); - - Frame *frame = &rt->frames[rt->num_frames-1]; - - ASSERT(frame->varbase - x >= 0 - && frame->varbase - x < MAX_STACK); - - return &rt->values[frame->varbase - x]; -} - -static int values_usage(WL_Runtime *rt) -{ - int num_vars = (MAX_STACK - rt->vars - 1); - return rt->stack + num_vars; -} - -static bool rt_check_stack(WL_Runtime *rt, int min) -{ - if (MAX_STACK - values_usage(rt) < min) { - REPORT(&rt->err, "Out of stack"); - rt->state = RUNTIME_ERROR; - return false; - } - return true; -} - -static bool rt_push_frame(WL_Runtime *rt, uint8_t args) -{ - if (rt->num_frames == MAX_FRAMES) { - REPORT(&rt->err, "Call stack limit reached"); - rt->state = RUNTIME_ERROR; - return false; - } - - if (MAX_STACK - values_usage(rt) < args) { - REPORT(&rt->err, "Stack limit reached"); - rt->state = RUNTIME_ERROR; - return false; - } - - Frame *frame = &rt->frames[rt->num_frames++]; - frame->retaddr = rt->off; - frame->varbase = rt->vars; - - for (int i = 0; i < args; i++) - rt->values[rt->vars--] = rt->values[--rt->stack]; - - return true; -} - -static void rt_pop_frame(WL_Runtime *rt) -{ - ASSERT(rt->num_frames > 0); - Frame *frame = &rt->frames[rt->num_frames-1]; - rt->off = frame->retaddr; - rt->vars = frame->varbase; - rt->num_frames--; -} - -static void rt_set_frame_vars(WL_Runtime *rt, uint8_t num) -{ - ASSERT(rt->num_frames > 0); - Frame *frame = &rt->frames[rt->num_frames-1]; - int num_vars = frame->varbase - rt->vars; - if (num_vars < num) - for (int i = 0; i < num - num_vars; i++) - rt->values[rt->vars - i] = VALUE_NONE; - rt->vars = frame->varbase - num; -} - -static void rt_push_group(WL_Runtime *rt) -{ - if (rt->num_groups == MAX_GROUPS) { - REPORT(&rt->err, "Out of memory"); - rt->state = RUNTIME_ERROR; - return; - } - rt->groups[rt->num_groups++] = rt->stack; -} - -static void rt_pack_group(WL_Runtime *rt) -{ - if (!rt_check_stack(rt, 1)) - return; - - ASSERT(rt->num_groups > 0); - int start = rt->groups[--rt->num_groups]; - int end = rt->stack; - - if (end - start > 1) { - - Value set = value_empty_array(end - start, rt->arena, &rt->err); - if (set == VALUE_ERROR) - return; - - for (int i = start; i < end; i++) - if (!value_append(set, rt->values[i], rt->arena, &rt->err)) - return; - - rt->stack = start; - rt->values[rt->stack++] = set; - } -} - -static void rt_pop_group(WL_Runtime *rt) -{ - ASSERT(rt->num_groups > 0); - 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)) { - - Type t; - Value v1; - Value v2; - Value v3; - uint32_t o; - uint8_t b1; - uint8_t b2; - uint8_t b3; - int64_t i; - double f; - String s; - - case OPCODE_NOPE: - break; - - case OPCODE_JUMP: - rt->off = rt_read_u32(rt); - break; - - case OPCODE_JIFP: - ASSERT(rt->stack > 0); - o = rt_read_u32(rt); - v1 = rt->values[--rt->stack]; - if (v1 == VALUE_FALSE) - rt->off = o; - else if (value_type(v1) != TYPE_BOOL) { - REPORT(&rt->err, "Invalid non-boolean condition"); - rt->state = RUNTIME_ERROR; - break; - } - break; - - case OPCODE_VARS: - b1 = rt_read_u8(rt); - rt_set_frame_vars(rt, b1); - break; - - case OPCODE_OUTPUT: - if (rt->stack > 0) { - rt->cur_output = 0; - rt->num_output = rt->stack; - rt->state = RUNTIME_OUTPUT; - } - break; - - case OPCODE_SYSVAR: - s = rt_read_str(rt); - rt_push_frame(rt, 0); - rt->stack_before_user = rt->stack; - rt->str_for_user = s; - rt->state = RUNTIME_SYSVAR; - break; - - case OPCODE_SYSCALL: - b1 = rt_read_u8(rt); - s = rt_read_str(rt); - rt_push_frame(rt, b1); - rt->stack_before_user = rt->stack; - rt->str_for_user = s; - rt->state = RUNTIME_SYSCALL; - break; - - case OPCODE_CALL: - b1 = rt_read_u8(rt); - o = rt_read_u32(rt); - rt_push_frame(rt, b1); - rt->off = o; - break; - - case OPCODE_RET: - rt_pop_frame(rt); - break; - - case OPCODE_GROUP: - rt_push_group(rt); - break; - - case OPCODE_PACK: - rt_pack_group(rt); - break; - - case OPCODE_GPOP: - rt_pop_group(rt); - break; - - case OPCODE_FOR: - b1 = rt_read_u8(rt); - b2 = rt_read_u8(rt); - b3 = rt_read_u8(rt); - o = rt_read_u32(rt); - - v1 = *rt_variable(rt, b3); - ASSERT(value_type(v1) == TYPE_INT); - i = value_to_s64(v1); - - v2 = *rt_variable(rt, b1); - - if (value_length(v2)-1 == i) { - rt->off = o; - break; - } - i++; - - v1 = value_select_by_index(v2, i, &rt->err); - if (v1 == VALUE_ERROR) break; - - *rt_variable(rt, b2) = v1; - - v1 = value_from_s64(i, rt->arena, &rt->err); // TODO: this could be in-place - *rt_variable(rt, b3) = v1; - break; - - case OPCODE_EXIT: - rt->state = RUNTIME_DONE; - break; - - case OPCODE_POP: - ASSERT(rt->stack > 0); - rt->stack--; - break; - - case OPCODE_SETV: - ASSERT(rt->stack > 0); - b1 = rt_read_u8(rt); - *rt_variable(rt, b1) = rt->values[rt->stack-1]; - break; - - case OPCODE_PUSHV: - if (!rt_check_stack(rt, 1)) break; - b1 = rt_read_u8(rt); - rt->values[rt->stack++] = *rt_variable(rt, b1); - break; - - case OPCODE_PUSHI: - if (!rt_check_stack(rt, 1)) break; - i = rt_read_s64(rt); - v1 = value_from_s64(i, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHF: - if (!rt_check_stack(rt, 1)) break; - f = rt_read_f64(rt); - v1 = value_from_f64(f, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHS: - if (!rt_check_stack(rt, 1)) break; - s = rt_read_str(rt); - v1 = value_from_str(s, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHA: - if (!rt_check_stack(rt, 1)) break; - o = rt_read_u32(rt); - v1 = value_empty_array(o, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHM: - if (!rt_check_stack(rt, 1)) break; - o = rt_read_u32(rt); - v1 = value_empty_map(o, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHN: - if (!rt_check_stack(rt, 1)) break; - rt->values[rt->stack++] = VALUE_NONE; - break; - - case OPCODE_PUSHT: - if (!rt_check_stack(rt, 1)) break; - rt->values[rt->stack++] = VALUE_TRUE; - break; - - case OPCODE_PUSHFL: - if (!rt_check_stack(rt, 1)) break; - rt->values[rt->stack++] = VALUE_FALSE; - break; - - case OPCODE_LEN: - ASSERT(rt->stack > 0); - v1 = rt->values[rt->stack-1]; - t = value_type(v1); - if (t != TYPE_ARRAY && t != TYPE_MAP) { - REPORT(&rt->err, "Invalid operation 'len' on non-aggregate value"); - rt->state = RUNTIME_ERROR; - break; - } - v2 = value_from_s64(value_length(v1), rt->arena, &rt->err); - rt->values[rt->stack-1] = v2; - break; - - case OPCODE_NEG: - ASSERT(rt->stack > 0); - v1 = rt->values[rt->stack-1]; - v2 = value_neg(v1, rt->arena, &rt->err); - rt->values[rt->stack-1] = v2; - break; - - case OPCODE_EQL: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_eql(v2, v1) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_NQL: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_nql(v2, v1) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_LSS: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_lower(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_GRT: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_greater(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_ADD: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_add(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_SUB: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_sub(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_MUL: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_mul(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_DIV: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_div(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_MOD: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_mod(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_APPEND: - ASSERT(rt->stack > 1); - v2 = rt->values[--rt->stack]; - v1 = rt->values[rt->stack-1]; - value_append(v1, v2, rt->arena, &rt->err); - break; - - case OPCODE_INSERT1: - ASSERT(rt->stack > 2); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = rt->values[rt->stack-1]; - value_insert(v3, v1, v2, rt->arena, &rt->err); - break; - - case OPCODE_INSERT2: - ASSERT(rt->stack > 2); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = rt->values[rt->stack-1]; - value_insert(v2, v1, v3, rt->arena, &rt->err); - break; - - case OPCODE_SELECT: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_select(v2, v1, &rt->err); - rt->values[rt->stack++] = v3; - break; - - default: - UNREACHABLE; - } -} - -WL_EvalResult wl_runtime_eval(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_OUTPUT || rt->cur_output == rt->num_output) { - - switch (rt->state) { - - case RUNTIME_BEGIN: - break; - - case RUNTIME_DONE: - return (WL_EvalResult) { .type=WL_EVAL_DONE }; - - case RUNTIME_ERROR: - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - - case RUNTIME_OUTPUT: - rt->stack -= rt->num_output; - break; - - 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"); - rt->state = RUNTIME_ERROR; - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - } - - if (rt->stack == rt->stack_before_user) { - // User didn't push anything on the stack - if (!rt_check_stack(rt, 1)) - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - rt->values[rt->stack++] = VALUE_NONE; - } - - rt_pop_frame(rt); - } - break; - - case RUNTIME_SYSCALL: - ASSERT(rt->stack >= rt->stack_before_user); - rt_pop_frame(rt); - break; - - default: - UNREACHABLE; - } - - rt->state = RUNTIME_LOOP; - - do { - - step(rt); - - if (rt->err.yes) - rt->state = RUNTIME_ERROR; - - } while (rt->state == RUNTIME_LOOP); - - } - - switch (rt->state) { - - case RUNTIME_BEGIN: - case RUNTIME_LOOP: - UNREACHABLE; - - case RUNTIME_DONE: - break; - - case RUNTIME_ERROR: - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - - case RUNTIME_OUTPUT: - { - ASSERT(rt->cur_output < rt->num_output); - - Value v = rt->values[rt->stack - rt->num_output + rt->cur_output]; - Type type = value_type(v); - - String str; - if (type == TYPE_STRING) - str = value_to_str(v); - else { - int len = value_convert_to_str(v, rt->buf, SIZEOF(rt->buf)); - if (len > SIZEOF(rt->buf)) { - 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 } }; - } - - case RUNTIME_SYSVAR: - return (WL_EvalResult) { .type=WL_EVAL_SYSVAR, .str=(WL_String) { rt->str_for_user.ptr, rt->str_for_user.len } }; - - case RUNTIME_SYSCALL: - return (WL_EvalResult) { .type=WL_EVAL_SYSCALL, .str=(WL_String) { rt->str_for_user.ptr, rt->str_for_user.len } }; - } - - return (WL_EvalResult) { .type=WL_EVAL_DONE }; -} - -bool wl_streq(WL_String a, char *b, int blen) -{ - if (b == NULL) b = ""; - if (blen < 0) blen = strlen(b); - return streq((String) { a.ptr, a.len }, (String) { b, blen }); -} - -int wl_arg_count(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return -1; - - ASSERT(rt->num_frames > 0); - return rt->frames[rt->num_frames-1].varbase - rt->vars; // TODO: is this right? -} - -static Value user_arg(WL_Runtime *rt, int idx, Type type) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return -1; - - int tot = wl_arg_count(rt); - if (idx < 0 || idx >= tot) - return false; - - Value v = *rt_variable(rt, tot - idx - 1); - if (value_type(v) != type) - return VALUE_ERROR; - - return v; -} - -bool wl_arg_none(WL_Runtime *rt, int idx) -{ - Value v = user_arg(rt, idx, TYPE_NONE); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_arg_bool(WL_Runtime *rt, int idx, bool *x) -{ - Value v = user_arg(rt, idx, TYPE_BOOL); - if (v == VALUE_ERROR) - return false; - *x = (v == VALUE_TRUE); - return true; -} - -bool wl_arg_s64(WL_Runtime *rt, int idx, int64_t *x) -{ - Value v = user_arg(rt, idx, TYPE_INT); - if (v == VALUE_ERROR) - return false; - *x = value_to_s64(v); - return true; -} - -bool wl_arg_f64(WL_Runtime *rt, int idx, double *x) -{ - Value v = user_arg(rt, idx, TYPE_FLOAT); - if (v == VALUE_ERROR) - return false; - *x = value_to_f64(v); - return true; -} - -bool wl_arg_str(WL_Runtime *rt, int idx, WL_String *x) -{ - Value v = user_arg(rt, idx, TYPE_STRING); - if (v == VALUE_ERROR) - return false; - String s = value_to_str(v); - *x = (WL_String) { s.ptr, s.len }; - return true; -} - -bool wl_arg_array(WL_Runtime *rt, int idx) -{ - Value v = user_arg(rt, idx, TYPE_ARRAY); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_arg_map(WL_Runtime *rt, int idx) -{ - Value v = user_arg(rt, idx, TYPE_MAP); - if (v == VALUE_ERROR) - return false; - return true; -} - -static Value user_peek(WL_Runtime *rt, int off, Type type) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return VALUE_ERROR; - - if (rt->stack + off < rt->stack_before_user || off >= 0) - return VALUE_ERROR; - - Value v = rt->values[rt->stack + off]; - if (value_type(v) != type) - return VALUE_ERROR; - - return v; -} - -bool wl_peek_none(WL_Runtime *rt, int off) -{ - Value v = user_peek(rt, off, TYPE_NONE); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_peek_bool(WL_Runtime *rt, int off, bool *x) -{ - Value v = user_peek(rt, off, TYPE_BOOL); - if (v == VALUE_ERROR) - return false; - *x = (v == VALUE_TRUE); - return true; -} - -bool wl_peek_s64(WL_Runtime *rt, int off, int64_t *x) -{ - Value v = user_peek(rt, off, TYPE_INT); - if (v == VALUE_ERROR) - return false; - *x = value_to_s64(v); - return true; -} - -bool wl_peek_f64(WL_Runtime *rt, int off, double *x) -{ - Value v = user_peek(rt, off, TYPE_FLOAT); - if (v == VALUE_ERROR) - return false; - *x = value_to_f64(v); - return true; -} - -bool wl_peek_str(WL_Runtime *rt, int off, WL_String *x) -{ - Value v = user_peek(rt, off, TYPE_STRING); - if (v == VALUE_ERROR) - return false; - String s = value_to_str(v); - *x = (WL_String) { s.ptr, s.len }; - return true; -} - -bool wl_pop_any(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return VALUE_ERROR; - - if (rt->stack == rt->stack_before_user) - return false; - - ASSERT(rt->stack > 0); - rt->stack--; - return true; -} - -static Value user_pop(WL_Runtime *rt, Type type) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return VALUE_ERROR; - - if (rt->stack == rt->stack_before_user) - return VALUE_ERROR; - - ASSERT(rt->stack > 0); - Value v = rt->values[rt->stack-1]; - if (value_type(v) != type) - return VALUE_ERROR; - - rt->stack--; - return v; -} - -bool wl_pop_none(WL_Runtime *rt) -{ - Value v = user_pop(rt, TYPE_NONE); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_pop_bool(WL_Runtime *rt, bool *x) -{ - Value v = user_pop(rt, TYPE_BOOL); - if (v == VALUE_ERROR) - return false; - *x = (v == VALUE_TRUE); - return true; -} - -bool wl_pop_s64(WL_Runtime *rt, int64_t *x) -{ - Value v = user_pop(rt, TYPE_INT); - if (v == VALUE_ERROR) - return false; - *x = value_to_s64(v); - return true; -} - -bool wl_pop_f64(WL_Runtime *rt, double *x) -{ - Value v = user_pop(rt, TYPE_FLOAT); - if (v == VALUE_ERROR) - return false; - *x = value_to_f64(v); - return true; -} - -bool wl_pop_str(WL_Runtime *rt, WL_String *x) -{ - Value v = user_pop(rt, TYPE_STRING); - if (v == VALUE_ERROR) - return false; - String s = value_to_str(v); - *x = (WL_String) { s.ptr, s.len }; - return true; -} - -void wl_push_none(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - rt->values[rt->stack++] = VALUE_NONE; -} - -void wl_push_true(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - rt->values[rt->stack++] = VALUE_TRUE; -} - -void wl_push_false(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - rt->values[rt->stack++] = VALUE_FALSE; -} - -void wl_push_s64(WL_Runtime *rt, int64_t x) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_from_s64(x, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_f64(WL_Runtime *rt, double x) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_from_f64(x, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_str(WL_Runtime *rt, WL_String x) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_from_str((String) { x.ptr, x.len }, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_array(WL_Runtime *rt, int cap) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_empty_array(cap, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_map(WL_Runtime *rt, int cap) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_empty_map(cap, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_arg(WL_Runtime *rt, int idx) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - int tot = wl_arg_count(rt); - if (idx < 0 || idx >= tot) { - REPORT(&rt->err, "Invalid API usagge"); - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = *rt_variable(rt, tot - idx - 1); -} - -void wl_insert(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (rt->stack - rt->stack_before_user < 3) { - REPORT(&rt->err, "Invalid API usagge"); - rt->state = RUNTIME_ERROR; - return; - } - - Value key = rt->values[--rt->stack]; - Value val = rt->values[--rt->stack]; - Value set = rt->values[rt->stack-1]; - - if (!value_insert(set, key, val, rt->arena, &rt->err)) { - rt->state = RUNTIME_ERROR; - return; - } -} - -void wl_append(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (rt->stack - rt->stack_before_user < 2) { - REPORT(&rt->err, "Invalid API usagge"); - rt->state = RUNTIME_ERROR; - return; - } - - Value val = rt->values[--rt->stack]; - Value set = rt->values[rt->stack-1]; - - if (!value_append(set, val, rt->arena, &rt->err)) { - rt->state = RUNTIME_ERROR; - 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"); -} +#include +#include +#include +#include +#include +#include "wl.h" + +///////////////////////////////////////////////////////////////////////// +// BASIC +///////////////////////////////////////////////////////////////////////// + +typedef struct { + char *ptr; + int len; +} String; + +typedef struct { + char *buf; + int cap; + bool yes; +} Error; + +#define S(X) (String) { (X), SIZEOF(X)-1 } + +#ifdef _WIN32 +#define LLD "lld" +#define LLU "llu" +#else +#define LLD "ld" +#define LLU "lu" +#endif + +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define SIZEOF(X) (int) sizeof(X) +#define ALIGNOF(X) (int) _Alignof(X) + +#ifndef NDEBUG +#define UNREACHABLE __builtin_trap() +#define ASSERT(X) if (!(X)) __builtin_trap(); +#else +#define UNREACHABLE {} +#define ASSERT(X) {} +#endif + +static bool is_space(char c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +static bool is_alpha(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +static bool is_printable(char c) +{ + return c >= ' ' && c <= '~'; +} + +static bool is_hex_digit(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +#if 0 +static char to_lower(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A' + 'a'; + return c; +} +#endif + +static int hex_digit_to_int(char c) +{ + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return c - '0'; +} + +static bool streq(String a, String b) +{ + if (a.len != b.len) + return false; + for (int i = 0; i < a.len; i++) + if (a.ptr[i] != b.ptr[i]) + return false; + return true; +} + +#if 0 +static bool streqcase(String a, String b) +{ + if (a.len != b.len) + return false; + for (int i = 0; i < a.len; i++) + if (to_lower(a.ptr[i]) != to_lower(b.ptr[i])) + return false; + return true; +} +#endif + +#define REPORT(err, fmt, ...) report((err), __FILE__, __LINE__, fmt, ## __VA_ARGS__) +static void report(Error *err, char *file, int line, char *fmt, ...) +{ + if (err->yes) return; + + if (err->cap > 0) { + + va_list args; + va_start(args, fmt); + int len = vsnprintf(err->buf, err->cap, fmt, args); + va_end(args); + ASSERT(len >= 0); + + if (err->cap > len) { + int ret = snprintf(err->buf + len, err->cap - len, + " (reported at %s:%d)", file, line); + ASSERT(ret >= 0); + len += ret; + } + + if (len > err->cap) + len = err->cap-1; + err->buf[len] = '\0'; + } + + err->yes = true; +} + +///////////////////////////////////////////////////////////////////////// +// ARENA +///////////////////////////////////////////////////////////////////////// + +static void *alloc(WL_Arena *a, int len, int align) +{ + int pad = -(intptr_t) (a->ptr + a->cur) & (align-1); + if (a->len - a->cur < len + pad) + return NULL; + void *ret = a->ptr + a->cur + pad; + a->cur += pad + len; + return ret; +} + +static bool grow_alloc(WL_Arena *a, char *p, int new_len) +{ + int new_cur = (p - a->ptr) + new_len; + if (new_cur > a->len) + return false; + a->cur = new_cur; + return true; +} + +#if 0 +static String copystr(String s, WL_Arena *a) +{ + char *p = alloc(a, s.len, 1); + if (p == NULL) + return (String) { NULL, 0 }; + memcpy(p, s.ptr, s.len); + return (String) { p, s.len }; +} +#endif + +///////////////////////////////////////////////////////////////////////// +// WRITER +///////////////////////////////////////////////////////////////////////// + +typedef struct { + char *dst; + int cap; + int len; +} Writer; + +static void write_raw_mem(Writer *w, void *ptr, int len) +{ + if (w->cap > w->len) { + int cpy = MIN(w->cap - w->len, len); + if (ptr && w->dst) + memcpy(w->dst + w->len, ptr, cpy); + } + w->len += len; +} + +static void write_raw_u8 (Writer *w, uint8_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +//static void write_raw_u16(Writer *w, uint16_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +static void write_raw_u32(Writer *w, uint32_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +//static void write_raw_u64(Writer *w, uint64_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +//static void write_raw_s8 (Writer *w, int8_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +//static void write_raw_s16(Writer *w, int16_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +//static void write_raw_s32(Writer *w, int32_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +static void write_raw_s64(Writer *w, int64_t x) { write_raw_mem(w, &x, SIZEOF(x)); } +//static void write_raw_f32(Writer *w, float x) { write_raw_mem(w, &x, SIZEOF(x)); } +static void write_raw_f64(Writer *w, double x) { write_raw_mem(w, &x, SIZEOF(x)); } + +static void write_text(Writer *w, String str) +{ + write_raw_mem(w, str.ptr, str.len); +} + +static void write_text_s64(Writer *w, int64_t n) +{ + int len; + if (w->len < w->cap) + len = snprintf(w->dst + w->len, w->cap - w->len, "%" LLD, n); + else + len = snprintf(NULL, 0, "%" LLD, n); + ASSERT(len >= 0); + w->len += len; +} + +static void write_text_f64(Writer *w, double n) +{ + int len; + if (w->len < w->cap) + len = snprintf(w->dst + w->len, w->cap - w->len, "%2.2f", n); + else + len = snprintf(NULL, 0, "%2.2f", n); + ASSERT(len >= 0); + w->len += len; +} + +static void patch_mem(Writer *w, void *src, int off, int len) +{ + ASSERT(off + len <= w->len); + if (off < w->cap) { + int cpy = MIN(w->cap - off, len); + memcpy(w->dst + off, src, cpy); + } +} + +///////////////////////////////////////////////////////////////////////// +// PARSER +///////////////////////////////////////////////////////////////////////// + +typedef struct { + char *src; + int len; + int cur; +} Scanner; + +typedef enum { + TOKEN_END, + TOKEN_ERROR, + TOKEN_IDENT, + TOKEN_KWORD_IF, + TOKEN_KWORD_ELSE, + TOKEN_KWORD_WHILE, + TOKEN_KWORD_FOR, + TOKEN_KWORD_IN, + TOKEN_KWORD_PROCEDURE, + TOKEN_KWORD_LET, + TOKEN_KWORD_NONE, + TOKEN_KWORD_TRUE, + TOKEN_KWORD_FALSE, + TOKEN_KWORD_INCLUDE, + TOKEN_KWORD_LEN, + TOKEN_VALUE_FLOAT, + TOKEN_VALUE_INT, + TOKEN_VALUE_STR, + TOKEN_OPER_EQL, + TOKEN_OPER_NQL, + TOKEN_OPER_LSS, + TOKEN_OPER_GRT, + TOKEN_OPER_ADD, + TOKEN_OPER_SUB, + TOKEN_OPER_MUL, + TOKEN_OPER_DIV, + TOKEN_OPER_MOD, + TOKEN_OPER_ASS, + TOKEN_OPER_SHOVEL, + TOKEN_PAREN_OPEN, + TOKEN_PAREN_CLOSE, + TOKEN_BRACKET_OPEN, + TOKEN_BRACKET_CLOSE, + TOKEN_CURLY_OPEN, + TOKEN_CURLY_CLOSE, + TOKEN_DOT, + TOKEN_COMMA, + TOKEN_COLON, + TOKEN_DOLLAR, + TOKEN_NEWLINE, +} TokType; + +typedef struct { + TokType type; + int64_t ival; + double fval; + String sval; +} Token; + +typedef enum { + NODE_PROCEDURE_DECL, + NODE_PROCEDURE_ARG, + NODE_PROCEDURE_CALL, + NODE_VAR_DECL, + NODE_COMPOUND, + NODE_GLOBAL, + NODE_IFELSE, + NODE_FOR, + NODE_WHILE, + NODE_INCLUDE, + NODE_SELECT, + NODE_NESTED, + NODE_OPER_LEN, + NODE_OPER_POS, + NODE_OPER_NEG, + NODE_OPER_ASS, + NODE_OPER_EQL, + NODE_OPER_NQL, + NODE_OPER_LSS, + NODE_OPER_GRT, + NODE_OPER_ADD, + NODE_OPER_SUB, + NODE_OPER_MUL, + NODE_OPER_DIV, + NODE_OPER_MOD, + NODE_OPER_SHOVEL, + NODE_VALUE_INT, + NODE_VALUE_FLOAT, + NODE_VALUE_STR, + NODE_VALUE_NONE, + NODE_VALUE_TRUE, + NODE_VALUE_FALSE, + NODE_VALUE_VAR, + NODE_VALUE_SYSVAR, + NODE_VALUE_HTML, + NODE_VALUE_ARRAY, + NODE_VALUE_MAP, +} NodeType; + +typedef struct Node Node; +struct Node { + NodeType type; + Node *next; + + Node *key; + + Node *left; + Node *right; + + Node *child; + + uint64_t ival; + double fval; + String sval; + + String html_tag; + Node* html_attr; + Node* html_child; + bool html_body; + + Node *if_cond; + Node *if_branch1; + Node *if_branch2; + + Node *while_cond; + Node *while_body; + + String for_var1; + String for_var2; + Node* for_set; + + String proc_name; + Node* proc_args; + Node* proc_body; + + String var_name; + Node* var_value; + + String include_path; + Node* include_next; + Node* include_root; +}; + +typedef struct { + Node *node; + Node *includes; + int errlen; +} ParseResult; + +typedef struct { + Scanner s; + WL_Arena* arena; + char* errbuf; + int errmax; + int errlen; + Node* include_head; + Node** include_tail; +} Parser; + +static bool consume_str(Scanner *s, String x) +{ + if (x.len == 0) + return false; + + if (x.len > s->len - s->cur) + return false; + + for (int i = 0; i < x.len; i++) + if (s->src[s->cur+i] != x.ptr[i]) + return false; + + s->cur += x.len; + return true; +} + +#if 0 +static void write_token(Writer *w, Token token) +{ + switch (token.type) { + + default : write_text(w, S("???")); break; + case TOKEN_END : write_text(w, S("")); break; + case TOKEN_ERROR : write_text(w, S("")); break; + case TOKEN_IDENT : write_text(w, token.sval); break; + case TOKEN_KWORD_IF : write_text(w, S("if")); break; + case TOKEN_KWORD_ELSE : write_text(w, S("else")); break; + case TOKEN_KWORD_WHILE : write_text(w, S("while")); break; + case TOKEN_KWORD_FOR : write_text(w, S("for")); break; + case TOKEN_KWORD_IN : write_text(w, S("in")); break; + case TOKEN_KWORD_PROCEDURE: write_text(w, S("procedure")); break; + case TOKEN_KWORD_LET : write_text(w, S("let")); break; + case TOKEN_KWORD_NONE : write_text(w, S("none")); break; + case TOKEN_KWORD_TRUE : write_text(w, S("true")); break; + case TOKEN_KWORD_FALSE : write_text(w, S("false")); break; + case TOKEN_KWORD_INCLUDE: write_text(w, S("include")); break; + case TOKEN_KWORD_LEN : write_text(w, S("len")); break; + case TOKEN_VALUE_FLOAT : write_text_f64(w, token.fval); break; + case TOKEN_VALUE_INT : write_text_s64(w, token.ival); break; + case TOKEN_OPER_ASS : write_text(w, S("=")); break; + case TOKEN_OPER_EQL : write_text(w, S("==")); break; + case TOKEN_OPER_NQL : write_text(w, S("!=")); break; + case TOKEN_OPER_LSS : write_text(w, S("<")); break; + case TOKEN_OPER_GRT : write_text(w, S(">")); break; + case TOKEN_OPER_ADD : write_text(w, S("+")); break; + case TOKEN_OPER_SUB : write_text(w, S("-")); break; + 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; + case TOKEN_BRACKET_CLOSE: write_text(w, S("]")); break; + case TOKEN_CURLY_OPEN : write_text(w, S("{")); break; + case TOKEN_CURLY_CLOSE : write_text(w, S("}")); break; + case TOKEN_DOT : write_text(w, S(".")); break; + case TOKEN_COMMA : write_text(w, S(",")); break; + case TOKEN_COLON : write_text(w, S(":")); break; + case TOKEN_DOLLAR : write_text(w, S("$")); break; + case TOKEN_NEWLINE : write_text(w, S("\\n")); break; + + case TOKEN_VALUE_STR: + write_text(w, S("\"")); + write_text(w, token.sval); // TODO: Escape + write_text(w, S("\"")); + break; + + } +} +#endif + +static void parser_report(Parser *p, char *fmt, ...) +{ + if (p->errmax == 0 || p->errlen > 0) + return; + + int line = 1; + int cur = 0; + while (cur < p->s.cur) { + if (p->s.src[cur] == '\n') + line++; + cur++; + } + + int len = snprintf(p->errbuf, p->errmax, "Error (line %d): ", line); + ASSERT(len >= 0); + + va_list args; + va_start(args, fmt); + int ret = vsnprintf(p->errbuf + len, p->errmax - len, fmt, args); + va_end(args); + ASSERT(ret >= 0); + len += ret; + + p->errlen = len; +} + +static Node *alloc_node(Parser *p) +{ + Node *n = alloc(p->arena, sizeof(Node), _Alignof(Node)); + if (n == NULL) { + parser_report(p, "Out of memory"); + return NULL; + } + + return n; +} + +static Token next_token(Parser *p) +{ + for (;;) { + while (p->s.cur < p->s.len && is_space(p->s.src[p->s.cur])) + p->s.cur++; + + if (!consume_str(&p->s, S(""))) + break; + p->s.cur++; + } + } + + if (p->s.cur == p->s.len) + return (Token) { .type=TOKEN_END }; + char c = p->s.src[p->s.cur]; + + if (is_alpha(c) || c == '_') { + + int start = p->s.cur; + do + p->s.cur++; + while (p->s.cur < p->s.len && (is_alpha(p->s.src[p->s.cur]) || is_digit(p->s.src[p->s.cur]) || p->s.src[p->s.cur] == '_')); + + String kword = { + p->s.src + start, + 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("procedure"))) return (Token) { .type=TOKEN_KWORD_PROCEDURE }; + if (streq(kword, S("let"))) return (Token) { .type=TOKEN_KWORD_LET }; + 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 }; + } + + if (is_digit(c)) { + + int peek = p->s.cur; + do + peek++; + while (peek < p->s.len && is_digit(p->s.src[peek])); + + if (p->s.len - peek > 1 && p->s.src[peek] == '.' && is_digit(p->s.src[peek+1])) { + + double buf = 0; + do { + int d = p->s.src[p->s.cur++] - '0'; + buf = buf * 10 + d; + } while (p->s.cur < p->s.len && p->s.src[p->s.cur] != '.'); + + p->s.cur++; + + double q = 1; + do { + int d = p->s.src[p->s.cur++] - '0'; + q /= 10; + buf += q * d; + } while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur])); + + return (Token) { .type=TOKEN_VALUE_FLOAT, .fval=buf }; + + } else { + + uint64_t buf = 0; + do { + int d = p->s.src[p->s.cur++] - '0'; + if (buf > (UINT64_MAX - d) / 10) { + parser_report(p, "Integer literal overflow"); + return (Token) { .type=TOKEN_ERROR }; + } + buf = buf * 10 + d; + } while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur])); + + return (Token) { .type=TOKEN_VALUE_INT, .ival=buf }; + } + } + + if (c == '\'' || c == '"') { + + char f = c; + p->s.cur++; + + char *buf = NULL; + int len = 0; + + for (;;) { + + int substr_off = p->s.cur; + + while (p->s.cur < p->s.len && is_printable(p->s.src[p->s.cur]) && p->s.src[p->s.cur] != f && p->s.src[p->s.cur] != '\\') + p->s.cur++; + + int substr_len = p->s.cur - substr_off; + + if (buf == NULL) + buf = alloc(p->arena, substr_len+1, 1); + else + if (!grow_alloc(p->arena, buf, len + substr_len+1)) + buf = NULL; + + if (buf == NULL) { + parser_report(p, "Out of memory"); + return (Token) { .type=TOKEN_ERROR }; + } + + if (substr_len > 0) { + memcpy( + buf + len, + p->s.src + substr_off, + p->s.cur - substr_off + ); + len += substr_len; + } + + if (p->s.cur == p->s.len) { + parser_report(p, "String literal wasn't closed"); + return (Token) { .type=TOKEN_ERROR }; + } + + if (!is_printable(p->s.src[p->s.cur])) { + parser_report(p, "Invalid byte in string literal"); + return (Token) { .type=TOKEN_ERROR }; + } + + if (p->s.src[p->s.cur] == f) + break; + + p->s.cur++; + if (p->s.cur == p->s.len) { + parser_report(p, "Missing special character after escape character \\"); + return (Token) { .type=TOKEN_ERROR }; + } + + switch (p->s.src[p->s.cur]) { + case 'n': buf[len++] = '\n'; break; + case 't': buf[len++] = '\t'; break; + case 'r': buf[len++] = '\r'; break; + case '"': buf[len++] = '"'; break; + case '\'': buf[len++] = '\''; break; + case '\\': buf[len++] = '\\'; break; + + case 'x': + { + if (p->s.len - p->s.cur < 3 + || !is_hex_digit(p->s.src[p->s.cur+1]) + || !is_hex_digit(p->s.src[p->s.cur+2])) + return (Token) { .type=TOKEN_ERROR }; + buf[len++] + = (hex_digit_to_int(p->s.src[p->s.cur+1]) << 4) + | (hex_digit_to_int(p->s.src[p->s.cur+2]) << 0); + p->s.cur += 2; + } + break; + + default: + parser_report(p, "Invalid character after escape character \\"); + return (Token) { .type=TOKEN_ERROR }; + } + + p->s.cur++; + } + + p->s.cur++; + 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 }; + if (consume_str(&p->s, S(">"))) return (Token) { .type=TOKEN_OPER_GRT }; + if (consume_str(&p->s, S("+"))) return (Token) { .type=TOKEN_OPER_ADD }; + if (consume_str(&p->s, S("-"))) return (Token) { .type=TOKEN_OPER_SUB }; + if (consume_str(&p->s, S("*"))) return (Token) { .type=TOKEN_OPER_MUL }; + if (consume_str(&p->s, S("/"))) return (Token) { .type=TOKEN_OPER_DIV }; + if (consume_str(&p->s, S("%"))) return (Token) { .type=TOKEN_OPER_MOD }; + if (consume_str(&p->s, S("="))) return (Token) { .type=TOKEN_OPER_ASS }; + + if (consume_str(&p->s, S("("))) return (Token) { .type=TOKEN_PAREN_OPEN }; + if (consume_str(&p->s, S(")"))) return (Token) { .type=TOKEN_PAREN_CLOSE }; + if (consume_str(&p->s, S("["))) return (Token) { .type=TOKEN_BRACKET_OPEN }; + if (consume_str(&p->s, S("]"))) return (Token) { .type=TOKEN_BRACKET_CLOSE }; + if (consume_str(&p->s, S("{"))) return (Token) { .type=TOKEN_CURLY_OPEN }; + if (consume_str(&p->s, S("}"))) return (Token) { .type=TOKEN_CURLY_CLOSE }; + if (consume_str(&p->s, S("."))) return (Token) { .type=TOKEN_DOT }; + if (consume_str(&p->s, S(","))) return (Token) { .type=TOKEN_COMMA }; + if (consume_str(&p->s, S(":"))) return (Token) { .type=TOKEN_COLON }; + if (consume_str(&p->s, S("$"))) return (Token) { .type=TOKEN_DOLLAR }; + + parser_report(p, "Invalid character '%c'", c); + return (Token) { .type=TOKEN_ERROR }; +} + +static Token next_token_or_newline(Parser *p) +{ + int peek = p->s.cur; + while (peek < p->s.len && is_space(p->s.src[peek]) && p->s.src[peek] != '\n') + peek++; + + if (peek < p->s.len && p->s.src[peek] == '\n') { + p->s.cur = peek+1; + return (Token) { .type=TOKEN_NEWLINE }; + } + + return next_token(p); +} + +enum { + IGNORE_GRT = 1 << 0, + IGNORE_LSS = 1 << 1, + IGNORE_DIV = 1 << 2, +}; + +static Node *parse_stmt(Parser *p, int opflags); +static Node *parse_expr(Parser *p, int opflags); + +static Node *parse_html(Parser *p) +{ + // NOTE: The first < was already consumed + + Token t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "HTML tag doesn't start with a name"); + return NULL; + } + String tagname = t.sval; + + Node *attr_head; + Node **attr_tail = &attr_head; + + bool no_body = false; + Scanner *s = &p->s; + for (;;) { + + int off = s->cur; + + bool quotes = false; + while (s->cur < s->len && s->src[s->cur] != '\\' && (quotes || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) { + if (s->src[s->cur] == '"') + quotes = !quotes; + s->cur++; + } + + if (s->cur > off) { + + Node *child = alloc_node(p); + if (child == NULL) + return NULL; + + child->type = NODE_VALUE_STR; + child->sval = (String) { p->s.src + off, p->s.cur - off }; + + *attr_tail = child; + attr_tail = &child->next; + } + + if (s->cur == s->len) { + ASSERT(0); // TODO + } + s->cur++; + + if (s->src[s->cur-1] == '>') + break; + + if (s->src[s->cur-1] == '/') { + while (s->cur < s->len && is_space(s->src[s->cur])) + s->cur++; + if (s->cur == s->len || s->src[s->cur] != '>') { + ASSERT(0); // TODO + } + s->cur++; + no_body = true; + break; + } + + ASSERT(s->src[s->cur-1] == '\\'); + + Node *child = parse_stmt(p, IGNORE_GRT | IGNORE_DIV); + if (child == NULL) + return NULL; + + *attr_tail = child; + attr_tail = &child->next; + } + + *attr_tail = NULL; + + Node *child_head; + Node **child_tail = &child_head; + + if (no_body == false) + for (;;) { + + int off = s->cur; + + while (s->cur < s->len && s->src[s->cur] != '\\' && s->src[s->cur] != '<') + s->cur++; + + if (s->cur > off) { + + Node *child = alloc_node(p); + if (child == NULL) + return NULL; + + child->type = NODE_VALUE_STR; + child->sval = (String) { p->s.src + off, p->s.cur - off }; + + *child_tail = child; + child_tail = &child->next; + } + + if (s->cur == s->len) { + ASSERT(0); // TODO + } + s->cur++; + + if (s->src[s->cur-1] == '<') { + + Scanner saved = *s; + t = next_token(p); + if (t.type == TOKEN_OPER_DIV) { + + t = next_token(p); + if (t.type != TOKEN_IDENT) { + ASSERT(0); // TODO + } + String closing_tagname = t.sval; + + if (!streq(closing_tagname, tagname)) { + ASSERT(0); // TODO + } + + t = next_token(p); + if (t.type != TOKEN_OPER_GRT) { + ASSERT(0); + } + + break; + } + + *s = saved; + + Node *child = parse_html(p); + if (child == NULL) + return NULL; + + *child_tail = child; + child_tail = &child->next; + + } else { + + Node *child = parse_stmt(p, IGNORE_LSS); + if (child == NULL) + return NULL; + + *child_tail = child; + child_tail = &child->next; + } + } + + *child_tail = NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_VALUE_HTML; + parent->html_tag = tagname; + parent->html_attr = attr_head; + parent->html_child = child_head; + parent->html_body = !no_body; + + return parent; +} + +static Node *parse_array(Parser *p) +{ + // Left bracket already consumed + + Node *head; + Node **tail = &head; + + Scanner saved = p->s; + Token t = next_token(p); + if (t.type != TOKEN_BRACKET_CLOSE) { + + p->s = saved; + + for (;;) { + + Node *child = parse_expr(p, 0); + if (child == NULL) + return NULL; + + *tail = child; + tail = &child->next; + + saved = p->s; + t = next_token(p); + if (t.type == TOKEN_COMMA) { + saved = p->s; + t = next_token(p); + } + + if (t.type == TOKEN_BRACKET_CLOSE) + break; + + p->s = saved; + } + } + + *tail = NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_VALUE_ARRAY; + parent->child = head; + + return parent; +} + +static Node *parse_map(Parser *p) +{ + // Left bracket already consumed + + Node *head; + Node **tail = &head; + + Scanner saved = p->s; + Token t = next_token(p); + if (t.type != TOKEN_CURLY_CLOSE) { + + p->s = saved; + + for (;;) { + + Node *key; + + saved = p->s; + t = next_token(p); + if (t.type == TOKEN_IDENT) { + + key = alloc_node(p); + if (key == NULL) + return NULL; + + key->type = NODE_VALUE_STR; + key->sval = t.sval; + + } else { + + p->s = saved; + key = parse_expr(p, 0); + if (key == NULL) + return NULL; + } + + t = next_token(p); + if (t.type != TOKEN_COLON) { + parser_report(p, "Missing ':' after key inside map literal"); + return NULL; + } + + Node *child = parse_expr(p, 0); + if (child == NULL) + return NULL; + child->key = key; + + *tail = child; + tail = &child->next; + + saved = p->s; + t = next_token(p); + if (t.type == TOKEN_COMMA) { + saved = p->s; + t = next_token(p); + } + + if (t.type == TOKEN_CURLY_CLOSE) + break; + + p->s = saved; + } + } + + *tail = NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_VALUE_MAP; + parent->child = head; + + return parent; +} + +static int precedence(Token t, int flags) +{ + switch (t.type) { + + 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; + + case TOKEN_OPER_LSS: + if (flags & IGNORE_LSS) + return -1; + return 2; + + case TOKEN_OPER_GRT: + if (flags & IGNORE_GRT) + return -1; + return 2; + + case TOKEN_OPER_ADD: + case TOKEN_OPER_SUB: + return 3; + + case TOKEN_OPER_MUL: + case TOKEN_OPER_MOD: + return 4; + + case TOKEN_OPER_DIV: + if (flags & IGNORE_DIV) + return -1; + return 4; + + default: + break; + } + + return -1; +} + +static bool right_associative(Token t) +{ + return t.type == TOKEN_OPER_ASS; +} + +static Node *parse_atom(Parser *p) +{ + Token t = next_token(p); + + Node *ret; + switch (t.type) { + case TOKEN_OPER_ADD: + { + Node *child = parse_atom(p); + if (child == NULL) + return NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_OPER_POS; + parent->left = child; + + ret = parent; + } + break; + + case TOKEN_OPER_SUB: + { + Node *child = parse_atom(p); + if (child == NULL) + return NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_OPER_NEG; + parent->left = child; + + ret = parent; + } + 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); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_VAR; + node->sval = t.sval; + + ret = node; + } + break; + + case TOKEN_VALUE_INT: + { + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_INT; + node->ival = t.ival; + + ret = node; + } + break; + + case TOKEN_VALUE_FLOAT: + { + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_FLOAT; + node->fval = t.fval; + + ret = node; + } + break; + + case TOKEN_VALUE_STR: + { + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_STR; + node->sval = t.sval; + + ret = node; + } + break; + + case TOKEN_KWORD_NONE: + { + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_NONE; + node->sval = t.sval; + + ret = node; + } + break; + + case TOKEN_KWORD_TRUE: + { + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_TRUE; + node->sval = t.sval; + + ret = node; + } + break; + case TOKEN_KWORD_FALSE: + { + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_FALSE; + node->sval = t.sval; + + ret = node; + } + break; + + case TOKEN_OPER_LSS: + { + Node *node = parse_html(p); + if (node == NULL) + return NULL; + + ret = node; + } + break; + + case TOKEN_PAREN_OPEN: + { + Node *node = parse_expr(p, 0); + if (node == NULL) + return NULL; + + Token t = next_token(p); + if (t.type != TOKEN_PAREN_CLOSE) { + parser_report(p, "Missing ')' after expression"); + return NULL; + } + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_NESTED; + parent->left = node; + + ret = parent; + } + break; + + case TOKEN_BRACKET_OPEN: + { + Node *node = parse_array(p); + if (node == NULL) + return NULL; + + ret = node; + } + break; + + case TOKEN_CURLY_OPEN: + { + Node *node = parse_map(p); + if (node == NULL) + return NULL; + + ret = node; + } + break; + + case TOKEN_DOLLAR: + { + t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "Missing identifier after '$'"); + return NULL; + } + + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_VALUE_SYSVAR; + node->sval = t.sval; + + ret = node; + } + break; + + default: + { + parser_report(p, "Invalid token inside expression"); + } + return NULL; + } + + for (;;) { + Scanner saved = p->s; + t = next_token(p); + if (t.type == TOKEN_DOT) { + + t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "Invalid token after '.' where an identifier was expected"); + return NULL; + } + + Node *child = alloc_node(p); + if (child == NULL) + return NULL; + + child->type = NODE_VALUE_STR; + child->sval = t.sval; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_SELECT; + parent->left = ret; + parent->right = child; + + ret = parent; + + } else if (t.type == TOKEN_BRACKET_OPEN) { + + Node *child = parse_expr(p, 0); + if (child == NULL) + return NULL; + + t = next_token(p); + if (t.type != TOKEN_BRACKET_CLOSE) { + parser_report(p, "Missing token ']'"); + return NULL; + } + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_SELECT; + parent->left = ret; + parent->right = child; + + ret = parent; + + } else if (t.type == TOKEN_PAREN_OPEN && (ret->type == NODE_VALUE_VAR || ret->type == NODE_VALUE_SYSVAR)) { + + Node *arg_head; + Node **arg_tail = &arg_head; + + Scanner saved = p->s; + t = next_token(p); + if (t.type != TOKEN_PAREN_CLOSE) { + + p->s = saved; + + for (;;) { + + Node *argval = parse_expr(p, 0); + if (argval == NULL) + return NULL; + + *arg_tail = argval; + arg_tail = &argval->next; + + t = next_token(p); + if (t.type == TOKEN_PAREN_CLOSE) + break; + + if (t.type != TOKEN_COMMA) { + parser_report(p, "Expected ',' after argument in procedure call"); + return NULL; + } + } + } + + *arg_tail = NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_PROCEDURE_CALL; + parent->left = ret; + parent->right = arg_head; + + ret = parent; + + } else { + p->s = saved; + break; + } + } + + return ret; +} + +static Node *parse_expr_inner(Parser *p, Node *left, int min_prec, int flags) +{ + for (;;) { + + Scanner saved = p->s; + Token t1 = next_token_or_newline(p); + if (precedence(t1, flags) < min_prec) { + p->s = saved; + break; + } + + Node *right = parse_atom(p); + if (right == NULL) + return NULL; + + for (;;) { + + saved = p->s; + Token t2 = next_token_or_newline(p); + int p1 = precedence(t1, flags); + int p2 = precedence(t2, flags); + p->s = saved; + + if (p2 < 0) + break; + + if (p2 <= p1 && (p1 != p2 || !right_associative(t2))) + break; + + right = parse_expr_inner(p, right, p1 + (p2 > p1), flags); + if (right == NULL) + return NULL; + } + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->left = left; + parent->right = right; + + switch (t1.type) { + case TOKEN_OPER_ASS: parent->type = NODE_OPER_ASS; break; + case TOKEN_OPER_EQL: parent->type = NODE_OPER_EQL; break; + case TOKEN_OPER_NQL: parent->type = NODE_OPER_NQL; break; + case TOKEN_OPER_LSS: parent->type = NODE_OPER_LSS; break; + case TOKEN_OPER_GRT: parent->type = NODE_OPER_GRT; break; + case TOKEN_OPER_ADD: parent->type = NODE_OPER_ADD; break; + case TOKEN_OPER_SUB: parent->type = NODE_OPER_SUB; break; + 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; + } + + left = parent; + } + + return left; +} + +static Node *parse_expr(Parser *p, int flags) +{ + Node *left = parse_atom(p); + if (left == NULL) + return NULL; + + return parse_expr_inner(p, left, 0, flags); +} + +static Node *parse_expr_stmt(Parser *p, int opflags) +{ + Node *e = parse_expr(p, opflags); + if (e == NULL) + return NULL; + + return e; +} + +static Node *parse_ifelse_stmt(Parser *p, int opflags) +{ + Token t = next_token(p); + if (t.type != TOKEN_KWORD_IF) { + parser_report(p, "Missing 'if' keyword before if statement"); + return NULL; + } + + Node *cond = parse_expr(p, 0); + if (cond == NULL) + return NULL; + + t = next_token(p); + if (t.type != TOKEN_COLON) { + parser_report(p, "Missing ':' after if condition"); + return NULL; + } + + Node *if_stmt = parse_stmt(p, opflags); + if (if_stmt == NULL) + return NULL; + + Scanner saved = p->s; + t = next_token(p); + + Node *else_stmt = NULL; + if (t.type == TOKEN_KWORD_ELSE) { + + else_stmt = parse_stmt(p, opflags); + if (else_stmt == NULL) + return NULL; + + } else { + p->s = saved; + } + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_IFELSE; + parent->if_cond = cond; + parent->if_branch1 = if_stmt; + parent->if_branch2 = else_stmt; + + return parent; +} + +static Node *parse_for_stmt(Parser *p, int opflags) +{ + Token t = next_token(p); + if (t.type != TOKEN_KWORD_FOR) { + parser_report(p, "Missing 'for' keyword at the start of a for statement"); + return NULL; + } + + t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "Missing iteraion variable name in for statement"); + return NULL; + } + String var1 = t.sval; + + t = next_token(p); + + String var2 = S(""); + if (t.type == TOKEN_COMMA) { + + t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "Missing iteration variable name after ',' in for statement"); + return NULL; + } + var2 = t.sval; + + t = next_token(p); + } + + if (t.type != TOKEN_KWORD_IN) { + parser_report(p, "Missing 'in' keyword after iteration variable name in for statement"); + return NULL; + } + + Node *set = parse_expr(p, 0); + if (set == NULL) + return NULL; + + t = next_token(p); + if (t.type != TOKEN_COLON) { + parser_report(p, "Missing ':' after for statement set expression"); + return NULL; + } + + Node *body = parse_stmt(p, opflags); + if (body == NULL) + return NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_FOR; + parent->left = body; + parent->for_var1 = var1; + parent->for_var2 = var2; + parent->for_set = set; + + return parent; +} + +static Node *parse_while_stmt(Parser *p, int opflags) +{ + Token t = next_token(p); + if (t.type != TOKEN_KWORD_WHILE) { + parser_report(p, "Missing keyword 'while' at the start of a while statement"); + return NULL; + } + + Node *cond = parse_expr(p, 0); + if (cond == NULL) + return NULL; + + t = next_token(p); + if (t.type != TOKEN_COLON) { + parser_report(p, "Missing token ':' after while statement condition"); + return NULL; + } + + Node *stmt = parse_stmt(p, opflags); + if (stmt == NULL) + return NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_WHILE; + parent->left = stmt; + parent->while_cond = cond; + parent->while_body = stmt; + + return parent; +} + +static Node *parse_compound_stmt(Parser *p, bool global) +{ + if (!global) { + Token t = next_token(p); + if (t.type != TOKEN_CURLY_OPEN) { + parser_report(p, "Missing '{' at the start of a compound statement"); + return NULL; + } + } + + Node *head; + Node **tail = &head; + + for (;;) { + + Scanner saved = p->s; + Token t = next_token(p); + if (!global) { + if (t.type == TOKEN_CURLY_CLOSE) + break; + } else { + if (t.type == TOKEN_END) + break; + } + p->s = saved; + + Node *node = parse_stmt(p, 0); + if (node == NULL) + return NULL; + + *tail = node; + tail = &node->next; + } + + *tail = NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = global ? NODE_GLOBAL : NODE_COMPOUND; + parent->left = head; + + return parent; +} + +static Node *parse_proc_decl(Parser *p, int opflags) +{ + Token t = next_token(p); + if (t.type != TOKEN_KWORD_PROCEDURE) { + parser_report(p, "Missing keyword 'procedure' at the start of a procedure declaration"); + return NULL; + } + + t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "Missing procedure name after 'procedure' keyword"); + return NULL; + } + String name = t.sval; + + t = next_token(p); + if (t.type != TOKEN_PAREN_OPEN) { + parser_report(p, "Missing '(' after procedure name in declaration"); + return NULL; + } + + Node *arg_head; + Node **arg_tail = &arg_head; + + Scanner saved = p->s; + t = next_token(p); + if (t.type != TOKEN_PAREN_CLOSE) { + p->s = saved; + + for (;;) { + + t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "Missing argument name in procedure declaration"); + return NULL; + } + String argname = t.sval; + + Node *node = alloc_node(p); + if (node == NULL) + return NULL; + + node->type = NODE_PROCEDURE_ARG; + node->sval = argname; + + *arg_tail = node; + arg_tail = &node->next; + + Scanner saved = p->s; + t = next_token(p); + if (t.type == TOKEN_COMMA) { + saved = p->s; + t = next_token(p); + } + + if (t.type == TOKEN_PAREN_CLOSE) + break; + p->s = saved; + } + } + + *arg_tail = NULL; + + Node *body = parse_stmt(p, opflags); + if (body == NULL) + return NULL; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_PROCEDURE_DECL; + parent->proc_name = name; + parent->proc_args = arg_head; + parent->proc_body = body; + + return parent; +} + +static Node *parse_var_decl(Parser *p, int opflags) +{ + Token t = next_token(p); + if (t.type != TOKEN_KWORD_LET) { + parser_report(p, "Missing keyword 'let' at the start of a variable declaration"); + return NULL; + } + + t = next_token(p); + if (t.type != TOKEN_IDENT) { + parser_report(p, "Missing variable name after 'let' keyword"); + return NULL; + } + String name = t.sval; + + Scanner saved = p->s; + t = next_token(p); + + Node *value; + if (t.type == TOKEN_OPER_ASS) { + + value = parse_expr(p, opflags); + if (value == NULL) + return NULL; + + } else { + p->s = saved; + value = NULL; + } + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_VAR_DECL; + parent->var_name = name; + parent->var_value = value; + + return parent; +} + +static Node *parse_include_stmt(Parser *p) +{ + Token t = next_token(p); + if (t.type != TOKEN_KWORD_INCLUDE) { + parser_report(p, "Missing keyword 'include' at the start of an include statement"); + return NULL; + } + + t = next_token(p); + if (t.type != TOKEN_VALUE_STR) { + parser_report(p, "Missing file path string after 'include' keyword"); + return NULL; + } + String path = t.sval; + + Node *parent = alloc_node(p); + if (parent == NULL) + return NULL; + + parent->type = NODE_INCLUDE; + parent->include_path = path; + parent->include_root = NULL; + + *p->include_tail = parent; + p->include_tail = &parent->include_next; + + return parent; +} + +static Node *parse_stmt(Parser *p, int opflags) +{ + Scanner saved = p->s; + Token t = next_token(p); + p->s = saved; + + switch (t.type) { + + case TOKEN_KWORD_INCLUDE: + return parse_include_stmt(p); + + case TOKEN_KWORD_PROCEDURE: + return parse_proc_decl(p, opflags); + + case TOKEN_KWORD_LET: + return parse_var_decl(p, opflags); + + case TOKEN_KWORD_IF: + return parse_ifelse_stmt(p, opflags); + + case TOKEN_KWORD_WHILE: + return parse_while_stmt(p, opflags); + + case TOKEN_KWORD_FOR: + return parse_for_stmt(p, opflags); + + case TOKEN_CURLY_OPEN: + return parse_compound_stmt(p, false); + + default: + break; + } + + return parse_expr_stmt(p, opflags); +} + +static void write_node(Writer *w, Node *node) +{ + switch (node->type) { + + case NODE_VALUE_NONE : write_text(w, S("none")); break; + case NODE_VALUE_TRUE : write_text(w, S("true")); break; + case NODE_VALUE_FALSE: write_text(w, S("false")); break; + + case NODE_NESTED: + write_text(w, S("(nested ")); + write_node(w, node->left); + write_text(w, S(")")); + break; + + case NODE_COMPOUND: + { + write_text(w, S("(compound ")); + Node *cur = node->left; + while (cur) { + write_node(w, cur); + cur = cur->next; + if (cur) + write_text(w, S(" ")); + } + write_text(w, S(")")); + } + break; + + case NODE_GLOBAL: + { + write_text(w, S("(global ")); + Node *cur = node->left; + while (cur) { + write_node(w, cur); + cur = cur->next; + if (cur) + write_text(w, S(" ")); + } + write_text(w, S(")")); + } + break; + + case NODE_OPER_LEN: + write_text(w, S("(len ")); + write_node(w, node->left); + write_text(w, S(")")); + break; + + case NODE_OPER_POS: + write_text(w, S("(+")); + write_node(w, node->left); + write_text(w, S(")")); + break; + + case NODE_OPER_NEG: + write_text(w, S("(")); + write_text(w, S("-")); + write_node(w, node->left); + write_text(w, S(")")); + break; + + case NODE_OPER_ASS: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("=")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_EQL: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("==")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_NQL: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("!=")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_LSS: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("<")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_GRT: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S(">")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_ADD: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("+")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_SUB: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("-")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_MUL: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("*")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_DIV: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("/")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_MOD: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("%%")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_OPER_SHOVEL: + write_text(w, S("(")); + write_node(w, node->left); + write_text(w, S("<<")); + write_node(w, node->right); + write_text(w, S(")")); + break; + + case NODE_VALUE_INT: + write_text_s64(w, node->ival); + break; + + case NODE_VALUE_FLOAT: + write_text_f64(w, node->fval); + break; + + case NODE_VALUE_STR: + write_text(w, S("\"")); + write_text(w, node->sval); + write_text(w, S("\"")); + break; + + case NODE_VALUE_VAR: + write_text(w, node->sval); + break; + + case NODE_VALUE_SYSVAR: + write_text(w, S("$")); + write_text(w, node->sval); + break; + + case NODE_IFELSE: + write_text(w, S("(if ")); + write_node(w, node->if_cond); + write_text(w, S(" ")); + write_node(w, node->if_branch1); + if (node->if_branch2) { + write_text(w, S(" else ")); + write_node(w, node->if_branch2); + } + write_text(w, S(")")); + break; + + case NODE_WHILE: + write_text(w, S("(while ")); + write_node(w, node->while_cond); + write_text(w, S(" ")); + write_node(w, node->while_body); + write_text(w, S(")")); + break; + + case NODE_VALUE_HTML: + { + write_text(w, S("(html ")); + write_text(w, node->html_tag); + + Node *child = node->html_child; + while (child) { + write_text(w, S(" ")); + write_node(w, child); + child = child->next; + } + + write_text(w, S(")")); + } + break; + + case NODE_FOR: + write_text(w, S("(for ")); + write_text(w, node->for_var1); + if (node->for_var2.len > 0) { + write_text(w, S(", ")); + write_text(w, node->for_var2); + } + write_text(w, S(" in ")); + write_node(w, node->for_set); + write_text(w, S(": ")); + write_node(w, node->left); + break; + + case NODE_SELECT: + write_node(w, node->left); + write_text(w, S("[")); + write_node(w, node->right); + write_text(w, S("]")); + break; + + case NODE_VALUE_ARRAY: + { + write_text(w, S("[")); + Node *child = node->child; + while (child) { + write_node(w, child); + write_text(w, S(", ")); + child = child->next; + } + write_text(w, S("]")); + } + break; + + case NODE_VALUE_MAP: + { + write_text(w, S("{")); + Node *child = node->child; + while (child) { + write_node(w, child->key); + write_text(w, S(": ")); + write_node(w, child); + write_text(w, S(", ")); + child = child->next; + } + write_text(w, S("}")); + } + break; + + case NODE_PROCEDURE_DECL: + { + write_text(w, S("(proc ")); + write_text(w, node->proc_name); + write_text(w, S("(")); + Node *arg = node->proc_args; + while (arg) { + write_node(w, arg); + arg = arg->next; + if (arg) + write_text(w, S(", ")); + } + write_text(w, S(")")); + write_node(w, node->proc_body); + } + break; + + case NODE_PROCEDURE_ARG: + write_text(w, node->sval); + break; + + case NODE_PROCEDURE_CALL: + { + write_node(w, node->left); + write_text(w, S("(")); + Node *arg = node->right; + while (arg) { + write_node(w, arg); + arg = arg->next; + if (arg) + write_text(w, S(", ")); + } + write_text(w, S(")")); + } + break; + + case NODE_VAR_DECL: + write_text(w, S("(let ")); + write_text(w, node->var_name); + if (node->var_value) { + write_text(w, S(" = ")); + write_node(w, node->var_value); + } + write_text(w, S(")")); + break; + + case NODE_INCLUDE: + write_text(w, S("include \"")); + write_text(w, node->include_path); + write_text(w, S("\"")); + break; + } +} + +static ParseResult parse(String src, WL_Arena *arena, char *errbuf, int errmax) +{ + Parser p = { + .s={ src.ptr, src.len, 0 }, + .arena=arena, + .errbuf=errbuf, + .errmax=errmax, + .errlen=0, + }; + + p.include_tail = &p.include_head; + + Node *node = parse_compound_stmt(&p, true); + if (node == NULL) + return (ParseResult) { .node=NULL, .includes=NULL, .errlen=p.errlen }; + + *p.include_tail = NULL; + return (ParseResult) { .node=node, .includes=p.include_head, .errlen=-1 }; +} + +///////////////////////////////////////////////////////////////////////// +// CODEGEN +///////////////////////////////////////////////////////////////////////// + +enum { + OPCODE_NOPE, + OPCODE_JUMP, + OPCODE_JIFP, + OPCODE_OUTPUT, + OPCODE_SYSVAR, + OPCODE_SYSCALL, + OPCODE_CALL, + OPCODE_RET, + OPCODE_GROUP, + OPCODE_PACK, + OPCODE_GPOP, + OPCODE_FOR, + OPCODE_EXIT, + OPCODE_VARS, + OPCODE_POP, + OPCODE_SETV, + OPCODE_PUSHV, + OPCODE_PUSHI, + OPCODE_PUSHF, + OPCODE_PUSHS, + OPCODE_PUSHA, + OPCODE_PUSHM, + OPCODE_PUSHN, + OPCODE_PUSHT, + OPCODE_PUSHFL, + OPCODE_LEN, + OPCODE_NEG, + OPCODE_EQL, + OPCODE_NQL, + OPCODE_LSS, + OPCODE_GRT, + OPCODE_ADD, + OPCODE_SUB, + OPCODE_MUL, + OPCODE_DIV, + OPCODE_MOD, + OPCODE_APPEND, + OPCODE_INSERT1, + OPCODE_INSERT2, + OPCODE_SELECT, +}; + +typedef struct UnpatchedCall UnpatchedCall; +struct UnpatchedCall { + UnpatchedCall *next; + String name; + int off; +}; + +typedef enum { + SYMBOL_VARIABLE, + SYMBOL_PROCEDURE, +} SymbolType; + +typedef struct { + SymbolType type; + String name; + bool cnst; + int off; +} Symbol; + +typedef enum { + SCOPE_IF, + SCOPE_ELSE, + SCOPE_FOR, + SCOPE_WHILE, + SCOPE_PROC, + SCOPE_COMPOUND, + SCOPE_GLOBAL, + SCOPE_ASSIGNMENT, +} ScopeType; + +typedef struct { + ScopeType type; + int idx_syms; + int max_vars; + UnpatchedCall *calls; +} Scope; + +#define MAX_SYMBOLS 1024 +#define MAX_SCOPES 128 +#define MAX_UNPATCHED_CALLS 32 + +typedef struct { + + Writer code; + Writer data; + + int num_scopes; + Scope scopes[MAX_SCOPES]; + + int num_syms; + Symbol syms[MAX_SYMBOLS]; + + UnpatchedCall *free_list_calls; + UnpatchedCall calls[MAX_UNPATCHED_CALLS]; + + bool err; + char *errmsg; + int errcap; + + int data_off; + +} Codegen; + +static void cg_report(Codegen *cg, char *fmt, ...) +{ + if (cg->err) return; + + va_list args; + va_start(args, fmt); + int len = vsnprintf(cg->errmsg, cg->errcap, fmt, args); + va_end(args); + + if (len > cg->errcap) + len = cg->errcap-1; + + cg->errmsg[len] = '\0'; + cg->err = true; +} + +static int cg_write_u8(Codegen *cg, uint8_t x) +{ + if (cg->err) return -1; + + int off = cg->code.len; + write_raw_u8(&cg->code, x); + return off; +} + +static int cg_write_u32(Codegen *cg, uint32_t x) +{ + if (cg->err) return -1; + + int off = cg->code.len; + write_raw_u32(&cg->code, x); + return off; +} + +static int cg_write_s64(Codegen *cg, int64_t x) +{ + if (cg->err) return -1; + + int off = cg->code.len; + write_raw_s64(&cg->code, x); + return off; +} + +static int cg_write_f64(Codegen *cg, double x) +{ + if (cg->err) return -1; + + int off = cg->code.len; + write_raw_f64(&cg->code, x); + return off; +} + +static void cg_write_str(Codegen *cg, String x) +{ + if (cg->err) return; + + int off = cg->data.len; + write_text(&cg->data, x); + write_raw_u32(&cg->code, off); + write_raw_u32(&cg->code, x.len); +} + +static void cg_patch_u8(Codegen *cg, int off, uint8_t x) +{ + if (cg->err) return; + + patch_mem(&cg->code, &x, off, SIZEOF(x)); +} + +static void cg_patch_u32(Codegen *cg, int off, uint32_t x) +{ + if (cg->err) return; + + patch_mem(&cg->code, &x, off, SIZEOF(x)); +} + +static uint32_t cg_current_offset(Codegen *cg) +{ + return cg->code.len; +} + +int count_nodes(Node *head) +{ + int n = 0; + Node *node = head; + while (node) { + n++; + node = node->next; + } + return n; +} + +static Scope *parent_scope(Codegen *cg) +{ + ASSERT(cg->num_scopes > 0); + + int parent = cg->num_scopes-1; + while (cg->scopes[parent].type != SCOPE_PROC && cg->scopes[parent].type != SCOPE_GLOBAL) + parent--; + + return &cg->scopes[parent]; +} + +static bool inside_assignment(Codegen *cg) +{ + ASSERT(cg->num_scopes > 0); + + int parent = cg->num_scopes-1; + while (cg->scopes[parent].type != SCOPE_PROC + && cg->scopes[parent].type != SCOPE_GLOBAL + && cg->scopes[parent].type != SCOPE_ASSIGNMENT) + parent--; + + return cg->scopes[parent].type == SCOPE_ASSIGNMENT; +} + +static int count_function_vars(Codegen *cg) +{ + int n = 0; + Scope *scope = parent_scope(cg); + for (int i = scope->idx_syms; i < cg->num_syms; i++) + if (cg->syms[i].type == SYMBOL_VARIABLE) + n++; + return n; +} + +static Symbol *cg_find_symbol(Codegen *cg, String name, bool local) +{ + if (cg->err) return NULL; + + if (name.len == 0) return NULL; + ASSERT(cg->num_scopes > 0); + Scope *scope = local ? &cg->scopes[cg->num_scopes-1] : parent_scope(cg); + for (int i = cg->num_syms-1; i >= scope->idx_syms; i--) + if (streq(cg->syms[i].name, name)) + return &cg->syms[i]; + return NULL; +} + +static int cg_declare_variable(Codegen *cg, String name, bool cnst) +{ + if (cg->err) return -1; + + Symbol *sym = cg_find_symbol(cg, name, true); + if (sym) { + cg_report(cg, "Variable declared twice"); + return -1; + } + + if (cg->num_syms == MAX_SYMBOLS) { + cg_report(cg, "Symbol count limit reached"); + return -1; + } + + int off = count_function_vars(cg); + + Scope *parent = parent_scope(cg); + parent->max_vars = MAX(parent->max_vars, off+1); + + cg->syms[cg->num_syms++] = (Symbol) { + .type = SYMBOL_VARIABLE, + .name = name, + .cnst = cnst, + .off = off, + }; + return off; +} + +static void cg_declare_procedure(Codegen *cg, String name, int off) +{ + if (cg->err) return; + + Symbol *sym = cg_find_symbol(cg, name, true); + if (sym) { + cg_report(cg, "Procedure declared twice"); + return; + } + + if (cg->num_syms == MAX_SYMBOLS) { + cg_report(cg, "Symbol count limit reached"); + return; + } + + cg->syms[cg->num_syms++] = (Symbol) { + .type = SYMBOL_PROCEDURE, + .name = name, + .cnst = true, + .off = off, + }; +} + +static void cg_push_scope(Codegen *cg, ScopeType type) +{ + if (cg->err) return; + + if (cg->num_scopes == MAX_SCOPES) { + cg_report(cg, "Scope limit reached"); + return; + } + + Scope *scope = &cg->scopes[cg->num_scopes++]; + scope->type = type; + scope->idx_syms = cg->num_syms; + scope->max_vars = 0; + scope->calls = NULL; +} + +static void cg_pop_scope(Codegen *cg) +{ + if (cg->err) return; + + 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]; + + while (scope->calls) { + + 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) { + if (parent_scope == NULL) { + cg_report(cg, "Undefined function '%.*s'", + scope->calls->name.len, + scope->calls->name.ptr); + return; + } + call->next = parent_scope->calls; + parent_scope->calls = call; + continue; + } + + if (sym->type != SYMBOL_PROCEDURE) { + cg_report(cg, "Symbol '%.*s' is not a procedure", call->name.len, call->name.ptr); + return; + } + + 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; + cg->num_scopes--; +} + +static void cg_append_unpatched_call(Codegen *cg, String name, int p) +{ + if (cg->err) return; + + if (cg->free_list_calls == NULL) { + cg_report(cg, "Out of memory"); + return; + } + 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; + scope->calls = call; +} + +static bool cg_global_scope(Codegen *cg) +{ + Scope *scope = parent_scope(cg); + return scope->type == SCOPE_GLOBAL; +} + +static void cg_flush_pushs(Codegen *cg) +{ + if (cg->data_off != -1) { + if (cg->data_off < cg->data.len) { + cg_write_u8(cg, OPCODE_PUSHS); + cg_write_u32(cg, cg->data_off); + cg_write_u32(cg, cg->data.len - cg->data_off); + } + cg->data_off = -1; + } +} + +static int cg_write_opcode(Codegen *cg, uint8_t opcode) +{ + ASSERT(opcode != OPCODE_PUSHS); + cg_flush_pushs(cg); + return cg_write_u8(cg, opcode); +} + +static void cg_write_pushs(Codegen *cg, String str, bool dont_group) +{ + if (dont_group) { + cg_flush_pushs(cg); + cg_write_u8(cg, OPCODE_PUSHS); + cg_write_str(cg, str); + } else { + if (cg->data_off == -1) + cg->data_off = cg->data.len; + write_raw_mem(&cg->data, str.ptr, str.len); + } +} + +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: + walk_expr_node(cg, node->left, one); + break; + + case NODE_OPER_LEN: + walk_expr_node(cg, node->left, true); + cg_write_opcode(cg, OPCODE_LEN); + break; + + case NODE_OPER_POS: + walk_expr_node(cg, node->left, one); + break; + + case NODE_OPER_NEG: + walk_expr_node(cg, node->left, true); + cg_write_opcode(cg, OPCODE_NEG); + break; + + case NODE_OPER_ASS: + { + Node *dst = node->left; + Node *src = node->right; + + if (dst->type == NODE_VALUE_VAR) { + + String name = dst->sval; + Symbol *sym = cg_find_symbol(cg, name, false); + if (sym == NULL) { + cg_report(cg, "Write to undeclared variable"); + return; + } + if (sym->type == SYMBOL_PROCEDURE) { + cg_report(cg, "Symbol is not a variable"); + return; + } + if (sym->cnst) { + cg_report(cg, "Variable is constant"); + return; + } + + cg_push_scope(cg, SCOPE_ASSIGNMENT); + walk_expr_node(cg, src, true); + cg_pop_scope(cg); + + cg_write_opcode(cg, OPCODE_SETV); + cg_write_u8(cg, sym->off); + + if (!one) + cg_write_opcode(cg, OPCODE_POP); + + } else if (dst->type == NODE_SELECT) { + + cg_push_scope(cg, SCOPE_ASSIGNMENT); + walk_expr_node(cg, src, true); + cg_pop_scope(cg); + + walk_expr_node(cg, dst->left, true); + walk_expr_node(cg, dst->right, true); + cg_write_opcode(cg, OPCODE_INSERT2); + + if (!one) + cg_write_opcode(cg, OPCODE_POP); + + } else { + + cg_report(cg, "Assignment left side can't be assigned to"); + return; + } + } + break; + + case NODE_OPER_SHOVEL: + { + 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); + cg_write_opcode(cg, OPCODE_EQL); + break; + + case NODE_OPER_NQL: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_NQL); + break; + + case NODE_OPER_LSS: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_LSS); + break; + + case NODE_OPER_GRT: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_GRT); + break; + + case NODE_OPER_ADD: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_ADD); + break; + + case NODE_OPER_SUB: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_SUB); + break; + + case NODE_OPER_MUL: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_MUL); + break; + + case NODE_OPER_DIV: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_DIV); + break; + + case NODE_OPER_MOD: + walk_expr_node(cg, node->left, true); + walk_expr_node(cg, node->right, true); + cg_write_opcode(cg, OPCODE_MOD); + break; + + case NODE_VALUE_INT: + cg_write_opcode(cg, OPCODE_PUSHI); + cg_write_s64(cg, node->ival); + break; + + case NODE_VALUE_FLOAT: + cg_write_opcode(cg, OPCODE_PUSHF); + cg_write_f64(cg, node->fval); + break; + + case NODE_VALUE_STR: + cg_write_pushs(cg, node->sval, one); + break; + + case NODE_VALUE_NONE: + cg_write_opcode(cg, OPCODE_PUSHN); + break; + + case NODE_VALUE_TRUE: + cg_write_opcode(cg, OPCODE_PUSHT); + break; + + case NODE_VALUE_FALSE: + cg_write_opcode(cg, OPCODE_PUSHFL); + break; + + case NODE_VALUE_VAR: + { + String name = node->sval; + Symbol *sym = cg_find_symbol(cg, name, false); + if (sym == NULL) { + cg_report(cg, "Access to undeclared variable '%.*s'", name.len, name.ptr); + return; + } + if (sym->type == SYMBOL_PROCEDURE) { + cg_report(cg, "Symbol is not a variable"); + return; + } + + cg_write_opcode(cg, OPCODE_PUSHV); + cg_write_u8(cg, sym->off); + } + break; + + case NODE_VALUE_SYSVAR: + cg_write_opcode(cg, OPCODE_SYSVAR); + cg_write_str(cg, node->sval); + break; + + case NODE_VALUE_HTML: + { + if (one) + cg_write_opcode(cg, OPCODE_GROUP); + + cg_write_pushs(cg, S("<"), false); + cg_write_pushs(cg, node->html_tag, false); + + Node *child = node->html_attr; + while (child) { + walk_node(cg, child); + child = child->next; + } + + if (!node->html_body) { + cg_write_pushs(cg, S("/>"), false); + } else { + cg_write_pushs(cg, S(">"), false); + Node *child = node->html_child; + while (child) { + walk_node(cg, child); + child = child->next; + } + cg_write_pushs(cg, S("html_tag, false); + cg_write_pushs(cg, S(">"), false); + } + + if (one) + cg_write_opcode(cg, OPCODE_PACK); + } + break; + + case NODE_VALUE_ARRAY: + { + cg_write_opcode(cg, OPCODE_PUSHA); + cg_write_u32(cg, count_nodes(node->child)); + + Node *child = node->child; + while (child) { + walk_expr_node(cg, child, true); + cg_write_opcode(cg, OPCODE_APPEND); + child = child->next; + } + } + break; + + case NODE_VALUE_MAP: + { + cg_write_opcode(cg, OPCODE_PUSHM); + cg_write_u32(cg, count_nodes(node->child)); + + Node *child = node->child; + while (child) { + walk_expr_node(cg, child, true); + walk_expr_node(cg, child->key, true); + cg_write_opcode(cg, OPCODE_INSERT1); + child = child->next; + } + } + break; + + case NODE_SELECT: + { + Node *set = node->left; + Node *key = node->right; + walk_expr_node(cg, set, true); + walk_expr_node(cg, key, true); + cg_write_opcode(cg, OPCODE_SELECT); + } + break; + + case NODE_PROCEDURE_CALL: + { + if (one) + cg_write_opcode(cg, OPCODE_GROUP); + + int count = 0; + Node *arg = node->right; + while (arg) { + walk_expr_node(cg, arg, true); + count++; + arg = arg->next; + } + + Node *proc = node->left; + if (proc->type == NODE_VALUE_VAR) { + + cg_write_opcode(cg, OPCODE_CALL); + cg_write_u8(cg, count); + int p = cg_write_u32(cg, 0); + cg_append_unpatched_call(cg, proc->sval, p); + + } else { + + ASSERT(proc->type == NODE_VALUE_SYSVAR); + cg_write_opcode(cg, OPCODE_SYSCALL); + cg_write_u8(cg, count); + cg_write_str(cg, proc->sval); + } + + if (one) + cg_write_opcode(cg, OPCODE_PACK); + } + break; + + default: + UNREACHABLE; + } +} + +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: + for (Node *child = node->left; + child; child = child->next) { + walk_node(cg, child); + } + break; + + case NODE_COMPOUND: + cg_push_scope(cg, SCOPE_COMPOUND); + for (Node *child = node->left; + child; child = child->next) + walk_node(cg, child); + cg_pop_scope(cg); + break; + + case NODE_PROCEDURE_DECL: + { + cg_push_scope(cg, SCOPE_PROC); + + cg_write_opcode(cg, OPCODE_JUMP); + int off0 = cg_write_u32(cg, 0); + + #define MAX_ARGS 128 + + int num_args = 0; + Node *args[MAX_ARGS]; + + Node *arg = node->proc_args; + while (arg) { + if (num_args == MAX_ARGS) { + cg_report(cg, "Procedure argument limit reached"); + return; + } + args[num_args++] = arg; + arg = arg->next; + } + + for (int i = num_args-1; i >= 0; i--) + cg_declare_variable(cg, args[i]->sval, false); + + int off1 = cg_write_opcode(cg, OPCODE_VARS); + int off2 = cg_write_u8(cg, 0); + + walk_node(cg, node->proc_body); + cg_write_opcode(cg, OPCODE_RET); + + 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); + + cg_declare_procedure(cg, node->proc_name, off1); + } + break; + + case NODE_VAR_DECL: + { + int off = cg_declare_variable(cg, node->var_name, false); + if (node->var_value) { + cg_push_scope(cg, SCOPE_ASSIGNMENT); + walk_expr_node(cg, node->var_value, true); + cg_pop_scope(cg); + } else + cg_write_opcode(cg, OPCODE_PUSHN); + cg_write_opcode(cg, OPCODE_SETV); + cg_write_u8(cg, off); + cg_write_opcode(cg, OPCODE_POP); + } + break; + + case NODE_IFELSE: + { + // If there is no else branch: + // + // + // JIFP end + // + // end: + // ... + // + // If there is: + // + // + // JIFP else + // + // JUMP end + // else: + // + // end: + // ... + + if (node->if_branch2) { + + walk_expr_node(cg, node->if_cond, true); + + cg_write_opcode(cg, OPCODE_JIFP); + int p1 = cg_write_u32(cg, 0); + + cg_push_scope(cg, SCOPE_IF); + walk_node(cg, node->if_branch1); + cg_pop_scope(cg); + + cg_write_opcode(cg, OPCODE_JUMP); + int p2 = cg_write_u32(cg, 0); + + cg_flush_pushs(cg); + cg_patch_u32(cg, p1, cg_current_offset(cg)); + + cg_push_scope(cg, SCOPE_ELSE); + walk_node(cg, node->if_branch2); + cg_pop_scope(cg); + + cg_flush_pushs(cg); + cg_patch_u32(cg, p2, cg_current_offset(cg)); + + } else { + + walk_expr_node(cg, node->if_cond, true); + + cg_write_opcode(cg, OPCODE_JIFP); + int p1 = cg_write_u32(cg, 0); + + cg_push_scope(cg, SCOPE_IF); + walk_node(cg, node->if_branch1); + cg_pop_scope(cg); + + cg_flush_pushs(cg); + cg_patch_u32(cg, p1, cg_current_offset(cg)); + } + } + break; + + case NODE_FOR: + { + cg_push_scope(cg, SCOPE_FOR); + + int var_1 = cg_declare_variable(cg, node->for_var1, false); + int var_2 = cg_declare_variable(cg, node->for_var2, true); + int var_3 = cg_declare_variable(cg, (String) { NULL, 0 }, true); + + walk_expr_node(cg, node->for_set, true); + cg_write_opcode(cg, OPCODE_SETV); + cg_write_u8(cg, var_3); + cg_write_opcode(cg, OPCODE_POP); + + cg_write_opcode(cg, OPCODE_PUSHI); + cg_write_s64(cg, -1); + cg_write_opcode(cg, OPCODE_SETV); + cg_write_u8(cg, var_2); + cg_write_opcode(cg, OPCODE_POP); + + int start = cg_write_opcode(cg, OPCODE_FOR); + cg_write_u8(cg, var_3); + cg_write_u8(cg, var_1); + cg_write_u8(cg, var_2); + int p = cg_write_u32(cg, 0); + + walk_node(cg, node->left); + + cg_write_opcode(cg, OPCODE_JUMP); + cg_write_u32(cg, start); + + cg_patch_u32(cg, p, cg_current_offset(cg)); + + cg_pop_scope(cg); + } + break; + + case NODE_WHILE: + { + // start: + // + // JIFP end + // + // JUMP start + // end: + // ... + + int start = cg_current_offset(cg); + + walk_expr_node(cg, node->while_cond, true); + + cg_write_opcode(cg, OPCODE_JIFP); + int p = cg_write_u32(cg, 0); + + cg_push_scope(cg, SCOPE_WHILE); + walk_node(cg, node->left); + cg_pop_scope(cg); + + cg_write_opcode(cg, OPCODE_JUMP); + cg_write_u32(cg, start); + + cg_patch_u32(cg, p, cg_current_offset(cg)); + } + break; + + case NODE_INCLUDE: + walk_node(cg, node->include_root); + break; + + default: + walk_expr_node(cg, node, false); + if (cg_global_scope(cg) && !inside_assignment(cg)) + cg_write_opcode(cg, OPCODE_OUTPUT); + break; + } +} + +#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 = { + .code = { dst, cap/2, 0 }, + .data = { dst + cap/2, cap/2, 0 }, + .num_scopes = 0, + .err = false, + .errmsg = errmsg, + .errcap = errcap, + .data_off = -1, + }; + + cg.free_list_calls = cg.calls; + for (int i = 0; i < MAX_UNPATCHED_CALLS-1; i++) + 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); + int off = cg_write_u8(&cg, 0); + walk_node(&cg, node); + cg_write_opcode(&cg, OPCODE_EXIT); + cg_patch_u8(&cg, off, cg.scopes[0].max_vars); + cg_pop_scope(&cg); + + if (cg.err) + return -1; + + if (hdr) { + + uint32_t magic = WL_MAGIC; + uint32_t code_len = cg.code.len; + uint32_t data_len = cg.data.len; + memcpy(hdr + 0, &magic , sizeof(uint32_t)); + memcpy(hdr + 4, &code_len, sizeof(uint32_t)); + memcpy(hdr + 8, &data_len, sizeof(uint32_t)); + + if (cg.code.len + cg.data.len <= cap) + memmove(dst + cg.code.len, dst + cap/2, cg.data.len); + } + + return cg.code.len + cg.data.len + SIZEOF(uint32_t) * 3; +} + +static int write_instr(Writer *w, char *src, int len, String data) +{ + if (len == 0) + return -1; + + switch (src[0]) { + + uint8_t b0; + uint8_t b1; + uint8_t b2; + uint32_t w0; + uint32_t w1; + int64_t i; + double d; + + case OPCODE_NOPE: + write_text(w, S("NOPE\n")); + return 1; + + case OPCODE_JUMP: + if (len < 5) return -1; + memcpy(&w0, src + 1, sizeof(uint32_t)); + write_text(w, S("JUMP ")); + write_text_s64(w, w0); + write_text(w, S("\n")); + return 5; + + case OPCODE_JIFP: + if (len < 5) return -1; + memcpy(&w0, src + 1, sizeof(uint32_t)); + write_text(w, S("JIFP ")); + write_text_s64(w, w0); + write_text(w, S("\n")); + return 5; + + case OPCODE_OUTPUT: + write_text(w, S("OUTPUT\n")); + return 1; + + case OPCODE_SYSVAR: + if (len < 9) return -1; + memcpy(&w0, src + 1, sizeof(uint32_t)); + memcpy(&w1, src + 5, sizeof(uint32_t)); + write_text(w, S("SYSVAR \"")); + write_text(w, (String) { data.ptr + w0, w1 }); + write_text(w, S("\"\n")); + return 9; + + case OPCODE_SYSCALL: + if (len < 10) return -1; + memcpy(&b0, src + 1, sizeof(uint8_t)); + memcpy(&w0, src + 2, sizeof(uint32_t)); + memcpy(&w1, src + 6, sizeof(uint32_t)); + write_text(w, S("SYSCALL ")); + write_text_s64(w, b0); + write_text(w, S(" \"")); + write_text(w, (String) { data.ptr + w0, w1 }); + write_text(w, S("\"\n")); + return 10; + + case OPCODE_CALL: + if (len < 6) return -1; + memcpy(&b0, src + 1, sizeof(uint8_t)); + memcpy(&w0, src + 2, sizeof(uint32_t)); + write_text(w, S("CALL ")); + write_text_s64(w, b0); + write_text(w, S(" ")); + write_text_s64(w, w0); + write_text(w, S("\n")); + return 6; + + case OPCODE_RET: + write_text(w, S("RET\n")); + return 1; + + case OPCODE_GROUP: + write_text(w, S("GROUP\n")); + return 1; + + case OPCODE_PACK: + write_text(w, S("PACK\n")); + return 1; + + case OPCODE_GPOP: + write_text(w, S("GPOP\n")); + return 1; + + case OPCODE_FOR: + if (len < 8) return -1; + memcpy(&b0, src + 1, sizeof(b0)); + memcpy(&b1, src + 2, sizeof(b1)); + memcpy(&b2, src + 3, sizeof(b2)); + memcpy(&w0, src + 4, sizeof(w0)); + write_text(w, S("FOR ")); + write_text_s64(w, b0); + write_text(w, S(" ")); + write_text_s64(w, b1); + write_text(w, S(" ")); + write_text_s64(w, b2); + write_text(w, S(" ")); + write_text_s64(w, w0); + write_text(w, S("\n")); + return 8; + + case OPCODE_EXIT: + write_text(w, S("EXIT\n")); + return 1; + + case OPCODE_VARS: + if (len < 2) return -1; + memcpy(&b0, src + 1, sizeof(b0)); + write_text(w, S("VARS ")); + write_text_s64(w, b0); + write_text(w, S("\n")); + return 2; + + case OPCODE_POP: + write_text(w, S("POP\n")); + return 1; + + case OPCODE_SETV: + if (len < 2) return -1; + memcpy(&b0, src + 1, sizeof(uint8_t)); + write_text(w, S("SETV ")); + write_text_s64(w, b0); + write_text(w, S("\n")); + return 2; + + case OPCODE_PUSHV: + if (len < 2) return -1; + memcpy(&b0, src + 1, sizeof(uint8_t)); + write_text(w, S("PUSHV ")); + write_text_s64(w, b0); + write_text(w, S("\n")); + return 2; + + case OPCODE_PUSHI: + if (len < 9) return -1; + memcpy(&i, src + 1, sizeof(int64_t)); + write_text(w, S("PUSHI ")); + write_text_s64(w, i); + write_text(w, S("\n")); + return 9; + + case OPCODE_PUSHF: + if (len < 9) return -1; + memcpy(&d, src + 1, sizeof(double)); + write_text(w, S("PUSHF ")); + write_text_f64(w, d); + write_text(w, S("\n")); + return 9; + + case OPCODE_PUSHS: + if (len < 9) return -1; + memcpy(&w0, src + 1, sizeof(uint32_t)); + memcpy(&w1, src + 5, sizeof(uint32_t)); + write_text(w, S("PUSHS \"")); + write_text(w, (String) { data.ptr + w0, w1 }); + write_text(w, S("\"\n")); + return 9; + + case OPCODE_PUSHA: + if (len < 5) return -1; + memcpy(&w0, src + 1, sizeof(w0)); + write_text(w, S("PUSHA ")); + write_text_s64(w, w0); + write_text(w, S("\n")); + return 5; + + case OPCODE_PUSHM: + if (len < 5) return -1; + memcpy(&w0, src + 1, sizeof(w0)); + write_text(w, S("PUSHM ")); + write_text_s64(w, w0); + write_text(w, S("\n")); + return 5; + + case OPCODE_PUSHN: + write_text(w, S("PUSHN\n")); + return 1; + + case OPCODE_PUSHT: + write_text(w, S("PUSHT\n")); + return 1; + + case OPCODE_PUSHFL: + write_text(w, S("PUSHFL\n")); + return 1; + + case OPCODE_LEN: + write_text(w, S("LEN\n")); + return 1; + + case OPCODE_NEG: + write_text(w, S("NEG\n")); + return 1; + + case OPCODE_EQL: + write_text(w, S("EQL\n")); + return 1; + + case OPCODE_NQL: + write_text(w, S("NQL\n")); + return 1; + + case OPCODE_LSS: + write_text(w, S("LSS\n")); + return 1; + + case OPCODE_GRT: + write_text(w, S("GRT\n")); + return 1; + + case OPCODE_ADD: + write_text(w, S("ADD\n")); + return 1; + + case OPCODE_SUB: + write_text(w, S("SUB\n")); + return 1; + + case OPCODE_MUL: + write_text(w, S("MUL\n")); + return 1; + + case OPCODE_DIV: + write_text(w, S("DIV\n")); + return 1; + + case OPCODE_MOD: + write_text(w, S("MOD\n")); + return 1; + + case OPCODE_APPEND: + write_text(w, S("APPEND\n")); + return 1; + + case OPCODE_INSERT1: + write_text(w, S("INSERT1\n")); + return 1; + + case OPCODE_INSERT2: + write_text(w, S("INSERT2\n")); + return 1; + + 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; +} + +static int write_program(WL_Program program, char *dst, int cap) +{ + if ((uint32_t) program.len < 3 * sizeof(uint32_t)) + return -1; + + uint32_t magic; + uint32_t code_len; + uint32_t data_len; + + memcpy(&magic , program.ptr + 0, sizeof(uint32_t)); + memcpy(&code_len, program.ptr + 4, sizeof(uint32_t)); + memcpy(&data_len, program.ptr + 8, sizeof(uint32_t)); + + if (magic != WL_MAGIC) + return -1; + + if (code_len + data_len + 3 * sizeof(uint32_t) != (uint32_t) program.len) + return -1; + + String code = { program.ptr + 3 * sizeof(uint32_t) , code_len }; + String data = { program.ptr + 3 * sizeof(uint32_t) + code_len, data_len }; + + Writer w = { dst, cap, 0 }; + + int cur = 0; + while (cur < code.len) { + write_text_s64(&w, cur); + write_text(&w, S(": ")); + int ret = write_instr(&w, code.ptr + cur, code.len - cur, data); + if (ret < 0) return -1; + cur += ret; + } + + 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 +///////////////////////////////////////////////////////////////////////// + +#define FILE_LIMIT 128 + +typedef struct { + String file; + Node* root; + Node* includes; +} CompiledFile; + +struct WL_Compiler { + + WL_Arena* arena; + CompiledFile files[FILE_LIMIT]; + int num_files; + String waiting_file; + + bool err; + char msg[1<<8]; +}; + +WL_Compiler *wl_compiler_init(WL_Arena *arena) +{ + WL_Compiler *compiler = alloc(arena, SIZEOF(WL_Compiler), _Alignof(WL_Compiler)); + if (compiler == NULL) + return NULL; + compiler->arena = arena; + compiler->num_files = 0; + compiler->waiting_file = (String) { NULL, 0 }; + compiler->err = false; + return compiler; +} + +WL_AddResult wl_compiler_add(WL_Compiler *compiler, WL_String content) +{ + if (compiler->err) + return (WL_AddResult) { .type=WL_ADD_ERROR }; + + ParseResult pres = parse((String) { content.ptr, content.len }, compiler->arena, compiler->msg, SIZEOF(compiler->msg)); + if (pres.node == NULL) { + compiler->err = true; + return (WL_AddResult) { .type=WL_ADD_ERROR }; + } + + CompiledFile compiled_file = { + .file = compiler->waiting_file, + .root = pres.node, + .includes = pres.includes, + }; + compiler->files[compiler->num_files++] = compiled_file; + compiler->waiting_file = (String) { NULL, 0 }; + + for (int i = 0; i < compiler->num_files; i++) { + + Node *include = compiler->files[i].includes; + while (include) { + + ASSERT(include->type == NODE_INCLUDE); + + if (include->include_root == NULL) { + for (int j = 0; j < compiler->num_files; j++) { + if (streq(include->include_path, compiler->files[j].file)) { + include->include_root = compiler->files[j].root; + break; + } + } + } + + if (include->include_root == NULL) { + + if (compiler->num_files == FILE_LIMIT) { + ASSERT(0); // TODO + } + + // TODO: Make the path relative to the compiled file + + compiler->waiting_file = include->include_path; + return (WL_AddResult) { .type=WL_ADD_AGAIN, .path={ include->include_path.ptr, include->include_path.len } }; + } + + include = include->include_next; + } + } + + return (WL_AddResult) { .type=WL_ADD_LINK }; +} + +int wl_compiler_link(WL_Compiler *compiler, WL_Program *program) +{ + if (compiler->err) return -1; + + if (compiler->num_files == 0 || compiler->waiting_file.len > 0) { + int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Missing files in compilation unit"); + if (len > SIZEOF(compiler->msg)) + len = SIZEOF(compiler->msg)-1; + compiler->msg[len] = '\0'; + compiler->err = true; + return -1; + } + + char *dst = compiler->arena->ptr + compiler->arena->cur; + int cap = compiler->arena->len - compiler->arena->cur; + + int len = codegen(compiler->files[0].root, dst, cap, compiler->msg, SIZEOF(compiler->msg)); + if (len < 0) { + compiler->err = true; + return -1; + } + if (len > cap) { + int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Out of memory"); + if (len > SIZEOF(compiler->msg)) + len = SIZEOF(compiler->msg)-1; + compiler->msg[len] = '\0'; + compiler->err = true; + return -1; + } + + *program = (WL_Program) { dst, len }; + + compiler->arena->cur += len; + return 0; +} + +WL_String wl_compiler_error(WL_Compiler *compiler) +{ + return compiler->err + ? (WL_String) { compiler->msg, strlen(compiler->msg) } + : (WL_String) { NULL, 0 }; +} + +int wl_dump_ast(WL_Compiler *compiler, char *dst, int cap) +{ + Writer w = { dst, cap, 0 }; + for (int i = 0; i < compiler->num_files; i++) { + write_text(&w, S("(file \"")); + write_text(&w, compiler->files[i].file); + write_text(&w, S("\" ")); + write_node(&w, compiler->files[i].root); + write_text(&w, S(")")); + } + return w.len; +} + +///////////////////////////////////////////////////////////////////////// +// OBJECT MODEL +///////////////////////////////////////////////////////////////////////// + +typedef enum { + TYPE_NONE, + TYPE_BOOL, + TYPE_INT, + TYPE_FLOAT, + TYPE_STRING, + TYPE_ARRAY, + TYPE_MAP, + TYPE_ERROR, +} Type; + +#define TAG_ERROR 0 +#define TAG_POSITIVE_INT 1 +#define TAG_NEGATIVE_INT 2 +#define TAG_BOOL 3 +#define TAG_NONE 4 +#define TAG_PTR 5 + +#define VALUE_NONE ((0 << 3) | TAG_NONE) +#define VALUE_TRUE ((0 << 3) | TAG_BOOL) +#define VALUE_FALSE ((1 << 3) | TAG_BOOL) +#define VALUE_ERROR ((0 << 3) | TAG_ERROR) + +typedef uint64_t Value; + +typedef struct Extension Extension; +struct Extension { + Extension *next; + int count; + int capacity; + Value vals[]; +}; + +typedef struct { + Type type; + int count; + int capacity; + Extension *ext; + Value vals[]; +} AggregateValue; + +typedef struct { + Type type; + double raw; +} FloatValue; + +typedef struct { + Type type; + int64_t raw; +} IntValue; + +typedef struct { + Type type; + int len; + char data[]; +} StringValue; + +static int value_convert_to_str(Value v, char *dst, int cap); + +static Type value_type(Value v) +{ + switch (v & 7) { + case TAG_ERROR: return TYPE_ERROR; + case TAG_POSITIVE_INT: return TYPE_INT; + case TAG_NEGATIVE_INT: return TYPE_INT; + case TAG_BOOL : return TYPE_BOOL; + case TAG_NONE : return TYPE_NONE; + case TAG_PTR : return *(Type*) (v & ~(Value) 7); break; + } + return TAG_ERROR; +} + +static int64_t value_to_s64(Value v) +{ + ASSERT(value_type(v) == TYPE_INT); + + if ((v & 7) == TAG_POSITIVE_INT) + return (int64_t) (v >> 3); + + if ((v & 7) == TAG_NEGATIVE_INT) + return (int64_t) ((v >> 3) | ((Value) 7 << 61)); + + IntValue *p = (IntValue*) (v & ~(Value) 7); + return p->raw; +} + +static double value_to_f64(Value v) +{ + ASSERT(value_type(v) == TYPE_FLOAT); + + FloatValue *p = (FloatValue*) (v & ~(Value) 7); + return p->raw; +} + +static String value_to_str(Value v) +{ + ASSERT(value_type(v) == TYPE_STRING); + + StringValue *p = (StringValue*) (v & ~(Value) 7); + return (String) { p->data, p->len }; +} + +/* + +2 bits -> 2^2 = 4 + +00000 0 . +00001 1 . +00010 2 . +00011 3 . +00100 4 . +00101 5 . +00110 6 . +00111 7 . +01000 8 +01001 9 +01010 10 +01011 11 +01100 12 +01101 13 +01110 14 +01111 15 +10000 -16 +10001 -15 +10010 -14 +10011 -13 +10100 -12 +10101 -11 +10110 -10 +10111 -9 +11000 -8 . +11001 -7 . +11010 -6 . +11011 -5 . +11100 -4 . +11101 -3 . +11110 -2 . +11111 -1 . + +*/ + +static Value value_from_s64(int64_t x, WL_Arena *arena, Error *err) +{ + Value v = (Value) x; + Value upper3bits = v >> 61; + + if (upper3bits == 0) + return (v << 3) | TAG_POSITIVE_INT; + + if (upper3bits == 7) + return (v << 3) | TAG_NEGATIVE_INT; + + IntValue *p = alloc(arena, SIZEOF(IntValue), _Alignof(IntValue)); + if (p == NULL) { + REPORT(err, "Out of memory"); + return VALUE_ERROR; + } + + p->type = TYPE_INT; + p->raw = x; + + ASSERT(((Value) p & 7) == 0); + return ((Value) p) | TAG_PTR; +} + +static Value value_from_f64(double x, WL_Arena *arena, Error *err) +{ + FloatValue *v = alloc(arena, SIZEOF(FloatValue), _Alignof(FloatValue)); + if (v == NULL) { + REPORT(err, "Out of memory"); + return VALUE_ERROR; + } + + v->type = TYPE_FLOAT; + v->raw = x; + + ASSERT(((uintptr_t) v & 7) == 0); + return ((Value) v) | TAG_PTR; +} + +static Value value_from_str(String x, WL_Arena *arena, Error *err) +{ + StringValue *v = alloc(arena, SIZEOF(StringValue) + x.len, 8); + if (v == NULL) { + REPORT(err, "Out of memory"); + return VALUE_ERROR; + } + + v->type = TYPE_STRING; + v->len = x.len; + memcpy(v->data, x.ptr, x.len); + + ASSERT(((uintptr_t) v & 7) == 0); + return ((Value) v) | TAG_PTR; +} + +static Value aggregate_empty(bool map, uint32_t cap, WL_Arena *arena, Error *err) +{ + AggregateValue *v = alloc(arena, SIZEOF(AggregateValue) + 2 * cap * SIZEOF(Value), MAX(_Alignof(AggregateValue), 8)); + if (v == NULL) { + REPORT(err, "Out of memory"); + return VALUE_ERROR; + } + + v->type = map ? TYPE_MAP : TYPE_ARRAY; + v->count = 0; + v->capacity = cap; + v->ext = NULL; + + ASSERT(((uintptr_t) v & 7) == 0); + return ((Value) v) | TAG_PTR; +} + +static int64_t aggregate_length(AggregateValue *agg) +{ + int64_t n = agg->count; + + Extension *ext = agg->ext; + while (ext) { + n += ext->count; + ext = ext->next; + } + + return n; +} + +static Value *aggregate_select_by_raw_index(AggregateValue *agg, int64_t idx) +{ + ASSERT(agg->type == TYPE_ARRAY || agg->type == TYPE_MAP); + + if (idx < 0 || idx >= aggregate_length(agg)) + return NULL; + + if (idx < agg->count) + return &agg->vals[idx]; + + idx -= agg->count; + Extension *ext = agg->ext; + while (ext) { + if (idx < ext->count) + return &ext->vals[idx]; + idx -= ext->count; + ext = ext->next; + } + + UNREACHABLE; + return NULL; +} + +static bool value_eql(Value a, Value b); + +static Value *aggregate_select(AggregateValue *agg, Value key) +{ + if (agg->type == TYPE_MAP) { + + for (int i = 0; i < agg->count; i += 2) + if (value_eql(agg->vals[i], key)) + return &agg->vals[i+1]; + + Extension *ext = agg->ext; + while (ext) { + for (int i = 0; i < ext->count; i += 2) + if (value_eql(ext->vals[i], key)) { + return &ext->vals[i+1]; + } + ext = ext->next; + } + + return NULL; + + } else { + + ASSERT(agg->type == TYPE_ARRAY); + + if (value_type(key) != TYPE_INT) + return NULL; + int64_t idx = value_to_s64(key); + + return aggregate_select_by_raw_index(agg, idx); + } +} + +static bool aggregate_append(AggregateValue *agg, Value v1, Value v2, WL_Arena *arena) +{ + if (agg->count < agg->capacity) { + agg->vals[agg->count++] = v1; + if (v2 != VALUE_ERROR) + agg->vals[agg->count++] = v2; + return true; + } + + Extension *tail = agg->ext; + if (tail) + while (tail->next) + tail = tail->next; + + Extension *ext; + if (tail == NULL || tail->count == tail->capacity) { + + int cap = 8; + ext = alloc(arena, SIZEOF(Extension) + cap * sizeof(Value), ALIGNOF(Extension)); + if (ext == NULL) + return false; + + ext->count = 0; + ext->capacity = cap; + ext->next = NULL; + + if (tail) + tail->next = ext; + else + agg->ext = ext; + + } else + ext = tail; + + ext->vals[ext->count++] = v1; + if (v2 != VALUE_ERROR) + ext->vals[ext->count++] = v2; + return true; +} + +static Value value_empty_map(uint32_t cap, WL_Arena *arena, Error *err) +{ + return aggregate_empty(true, 2 * cap, arena, err); +} + +static Value value_empty_array(uint32_t cap, WL_Arena *arena, Error *err) +{ + return aggregate_empty(false, cap, arena, err); +} + +static int64_t value_length(Value set) +{ + ASSERT(value_type(set) == TYPE_MAP || value_type(set) == TYPE_ARRAY); + AggregateValue *agg = (void*) (set & ~(Value) 7); + int64_t len = aggregate_length(agg); + if (agg->type == TYPE_MAP) + len /= 2; + return len; +} + +static bool value_insert(Value set, Value key, Value val, WL_Arena *arena, Error *err) +{ + Type t = value_type(set); + if (t != TYPE_MAP && t != TYPE_ARRAY) { + REPORT(err, "Invalid insertion on non-map and non-array value"); + return false; + } + AggregateValue *agg = (void*) (set & ~(Value) 7); + + Value *dst = aggregate_select(agg, key); + if (dst != NULL) { + *dst = val; + return true; + } + + if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) { + REPORT(err, "Invalid index used in array access"); + return false; + } + + if (!aggregate_append(agg, key, val, arena)) { + REPORT(err, "Out of memory"); + return false; + } + + return true; +} + +static Value value_select(Value set, Value key, Error *err) +{ + Type t = value_type(set); + if (t != TYPE_MAP && t != TYPE_ARRAY) { + REPORT(err, "Invalid selection from non-map and non-array value"); + return VALUE_ERROR; + } + AggregateValue *agg = (void*) (set & ~(Value) 7); + + Value *dst = aggregate_select(agg, key); + if (dst) return *dst; + + if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) { + REPORT(err, "Invalid index used in array access"); + return VALUE_ERROR; + } + + 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 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'", key_buf, set_buf); + return VALUE_ERROR; +} + +static Value value_select_by_index(Value set, int64_t idx, Error *err) +{ + Type t = value_type(set); + if (t != TYPE_MAP && t != TYPE_ARRAY) { + REPORT(err, "Invalid selection from non-map and non-array value"); + return VALUE_ERROR; + } + AggregateValue *agg = (void*) (set & ~(Value) 7); + + if (agg->type == TYPE_MAP) + idx *= 2; + + Value *src = aggregate_select_by_raw_index(agg, idx); + if (src == NULL) { + REPORT(err, "Invalid selection from non-map and non-array value"); + return VALUE_ERROR; + } + + return *src; +} + +static bool value_append(Value set, Value val, WL_Arena *arena, Error *err) +{ + Type t = value_type(set); + if (t != TYPE_ARRAY) { + REPORT(err, "Invalid append on non-array value"); + return false; + } + AggregateValue *agg = (void*) (set & ~(Value) 7); + + if (!aggregate_append(agg, val, VALUE_ERROR, arena)) { + REPORT(err, "Out of memory"); + return false; + } + + return true; +} + +static bool value_eql(Value a, Value b) +{ + Type t1 = value_type(a); + Type t2 = value_type(b); + + if (t1 != t2) + return false; + + switch (t1) { + + case TYPE_NONE: + return true; + + case TYPE_BOOL: + return a == b; + + case TYPE_INT: + return value_to_s64(a) == value_to_s64(b); + + case TYPE_FLOAT: + return value_to_f64(a) == value_to_f64(b); + + case TYPE_MAP: + return false; // TODO + + case TYPE_ARRAY: + return false; // TODO + + case TYPE_STRING: + return streq(value_to_str(a), value_to_str(b)); + + case TYPE_ERROR: + return true; + } + + return false; +} + +static bool value_nql(Value a, Value b) +{ + return !value_eql(a, b); +} + +#define TYPE_PAIR(X, Y) (((uint16_t) (X) << 16) | (uint16_t) (Y)) + +bool value_greater(Value a, Value b, Error *err) +{ + Type t1 = value_type(a); + Type t2 = value_type(b); + switch (TYPE_PAIR(t1, t2)) { + case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) > value_to_s64(b); + case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) > value_to_f64(b); + case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) > value_to_s64(b); + case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) > value_to_f64(b); + default:break; + } + REPORT(err, "Invalid '>' operation on non-numeric type"); + return false; +} + +bool value_lower(Value a, Value b, Error *err) +{ + Type t1 = value_type(a); + Type t2 = value_type(b); + switch (TYPE_PAIR(t1, t2)) { + case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) < value_to_s64(b); + case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) < value_to_f64(b); + case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) < value_to_s64(b); + case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) < value_to_f64(b); + default:break; + } + REPORT(err, "Invalid '<' operation on non-numeric type"); + return false; +} + +static Value value_neg(Value v, WL_Arena *arena, Error *err) +{ + Type t = value_type(v); + if (t == TYPE_INT) + return value_from_s64(-value_to_s64(v), arena, err); // TODO: overflow + + if (t == TYPE_FLOAT) + return value_from_f64(-value_to_f64(v), arena, err); + + REPORT(err, "Invalid '-' operation on non-numeric type"); + return VALUE_ERROR; +} + +static Value value_add(Value v1, Value v2, WL_Arena *arena, Error *err) +{ + Type t1 = value_type(v1); + Type t2 = value_type(v2); + + Value r; + switch (TYPE_PAIR(t1, t2)) { + + case TYPE_PAIR(TYPE_INT, TYPE_INT): + { + int64_t u = value_to_s64(v1); + int64_t v = value_to_s64(v2); + // TODO: check overflow and underflow + r = value_from_s64(u + v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): + { + double u = (double) value_to_s64(v1); + double v = value_to_f64(v2); + r = value_from_f64(u + v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): + { + double u = value_to_f64(v1); + double v = (double) value_to_s64(v2); + r = value_from_f64(u + v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): + { + double u = value_to_f64(v1); + double v = value_to_f64(v2); + // TODO: check overflow and underflow + r = value_from_f64(u + v, arena, err); + } + break; + + default: + REPORT(err, "Invalid operation '+' on non-numeric value"); + return VALUE_ERROR; + } + + return r; +} + +static Value value_sub(Value v1, Value v2, WL_Arena *arena, Error *err) +{ + Type t1 = value_type(v1); + Type t2 = value_type(v2); + + Value r; + switch (TYPE_PAIR(t1, t2)) { + + case TYPE_PAIR(TYPE_INT, TYPE_INT): + { + int64_t u = value_to_s64(v1); + int64_t v = value_to_s64(v2); + // TODO: check overflow and underflow + r = value_from_s64(u - v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): + { + double u = (double) value_to_s64(v1); + double v = value_to_f64(v2); + r = value_from_f64(u - v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): + { + double u = value_to_f64(v1); + double v = (double) value_to_s64(v2); + r = value_from_f64(u - v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): + { + double u = value_to_f64(v1); + double v = value_to_f64(v2); + // TODO: check overflow and underflow + r = value_from_f64(u - v, arena, err); + } + break; + + default: + REPORT(err, "Invalid operation '-' on non-numeric value"); + return VALUE_ERROR; + } + + return r; +} + +static Value value_mul(Value v1, Value v2, WL_Arena *arena, Error *err) +{ + Type t1 = value_type(v1); + Type t2 = value_type(v2); + + Value r; + switch (TYPE_PAIR(t1, t2)) { + + case TYPE_PAIR(TYPE_INT, TYPE_INT): + { + int64_t u = value_to_s64(v1); + int64_t v = value_to_s64(v2); + // TODO: check overflow and underflow + r = value_from_s64(u * v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): + { + double u = (double) value_to_s64(v1); + double v = value_to_f64(v2); + r = value_from_f64(u * v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): + { + double u = value_to_f64(v1); + double v = (double) value_to_s64(v2); + r = value_from_f64(u * v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): + { + double u = value_to_f64(v1); + double v = value_to_f64(v2); + // TODO: check overflow and underflow + r = value_from_f64(u * v, arena, err); + } + break; + + default: + REPORT(err, "Invalid operation '*' on non-numeric value"); + return VALUE_ERROR; + } + + return r; +} + +static Value value_div(Value v1, Value v2, WL_Arena *arena, Error *err) +{ + Type t1 = value_type(v1); + Type t2 = value_type(v2); + + Value r; + switch (TYPE_PAIR(t1, t2)) { + + case TYPE_PAIR(TYPE_INT, TYPE_INT): + { + // TODO: check division by 0 + + int64_t u = value_to_s64(v1); + int64_t v = value_to_s64(v2); + r = value_from_s64(u / v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): + { + // TODO: check division by 0 + + double u = (double) value_to_s64(v1); + double v = value_to_f64(v2); + r = value_from_f64(u / v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): + { + // TODO: check division by 0 + + double u = value_to_f64(v1); + double v = (double) value_to_s64(v2); + r = value_from_f64(u / v, arena, err); + } + break; + + case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): + { + double u = value_to_f64(v1); + double v = value_to_f64(v2); + r = value_from_f64(u / v, arena, err); + } + break; + + default: + REPORT(err, "Invalid operation '/' on non-numeric value"); + return VALUE_ERROR; + } + + return r; +} + +static Value value_mod(Value v1, Value v2, WL_Arena *arena, Error *err) +{ + Type t1 = value_type(v1); + Type t2 = value_type(v2); + + if (t1 != TYPE_INT || t2 != TYPE_INT) { + REPORT(err, "Invalid operation '%%' on non-integer value"); + return VALUE_ERROR; + } + + int64_t u = value_to_s64(v1); + int64_t v = value_to_s64(v2); + Value r = value_from_s64(u % v, arena, err); + return r; +} + +static void value_convert_to_str_inner(Writer *w, Value v) +{ + Type t = value_type(v); + switch (t) { + + case TYPE_NONE: + break; + + case TYPE_BOOL: + write_text(w, v == VALUE_TRUE ? S("true") : S("false")); + break; + + case TYPE_INT: + write_text_s64(w, value_to_s64(v)); + break; + + case TYPE_FLOAT: + write_text_f64(w, value_to_f64(v)); + break; + + case TYPE_STRING: + write_text(w, value_to_str(v)); + break; + + case TYPE_ARRAY: + { + AggregateValue *agg = (void*) (v & ~(Value) 7); + for (int i = 0; i < agg->count; i++) + value_convert_to_str_inner(w, agg->vals[i]); + Extension *ext = agg->ext; + while (ext) { + for (int i = 0; i < ext->count; i++) + value_convert_to_str_inner(w, ext->vals[i]); + ext = ext->next; + } + } + break; + + case TYPE_MAP: + { + write_text(w, S("{")); + AggregateValue *agg = (void*) (v & ~(Value) 7); + for (int i = 0; i < agg->count; i += 2) { + value_convert_to_str_inner(w, agg->vals[i+0]); + write_text(w, S(": ")); + value_convert_to_str_inner(w, agg->vals[i+1]); + if (i+2 < agg->count || agg->ext) + write_text(w, S(", ")); + } + Extension *ext = agg->ext; + while (ext) { + for (int i = 0; i < ext->count; i += 2) { + value_convert_to_str_inner(w, ext->vals[i+0]); + write_text(w, S(": ")); + value_convert_to_str_inner(w, ext->vals[i+1]); + if (i+2 < ext->count || ext->next) + write_text(w, S(", ")); + } + ext = ext->next; + } + write_text(w, S("}")); + } + break; + + case TYPE_ERROR: + break; + } +} + +static int value_convert_to_str(Value v, char *dst, int cap) +{ + Writer w = { dst, cap, 0}; + value_convert_to_str_inner(&w, v); + return w.len; +} + +#undef TYPE_PAIR + +///////////////////////////////////////////////////////////////////////// +// RUNTIME +///////////////////////////////////////////////////////////////////////// + +#define MAX_STACK 1024 +#define MAX_FRAMES 1024 +#define MAX_GROUPS 8 + +typedef struct { + int retaddr; + int varbase; +} Frame; + +typedef enum { + RUNTIME_BEGIN, + RUNTIME_LOOP, + RUNTIME_DONE, + RUNTIME_ERROR, + RUNTIME_OUTPUT, + RUNTIME_SYSVAR, + RUNTIME_SYSCALL, +} RuntimeState; + +struct WL_Runtime { + + RuntimeState state; + + String code; + String data; + int off; + + int vars; + int stack; + Value values[MAX_STACK]; + + int num_frames; + Frame frames[MAX_FRAMES]; + + int num_groups; + int groups[MAX_GROUPS]; + + WL_Arena *arena; + + char msg[128]; + Error err; + + int stack_before_user; + String str_for_user; + int num_output; + int cur_output; + char buf[128]; +}; + +WL_Runtime *wl_runtime_init(WL_Arena *arena, WL_Program program) +{ + if ((uint32_t) program.len < 3 * sizeof(uint32_t)) + return NULL; + + uint32_t magic; + uint32_t code_len; + uint32_t data_len; + + memcpy(&magic , program.ptr + 0, sizeof(uint32_t)); + memcpy(&code_len, program.ptr + 4, sizeof(uint32_t)); + memcpy(&data_len, program.ptr + 8, sizeof(uint32_t)); + + if (magic != WL_MAGIC) + return NULL; + + String code = { program.ptr + sizeof(uint32_t) * 3 , code_len }; + String data = { program.ptr + sizeof(uint32_t) * 3 + code_len, data_len }; + + WL_Runtime *rt = alloc(arena, SIZEOF(WL_Runtime), ALIGNOF(WL_Runtime)); + if (rt == NULL) + return NULL; + + *rt = (WL_Runtime) { + .state = RUNTIME_BEGIN, + .code = code, + .data = data, + .off = 0, + .stack = 0, + .vars = MAX_STACK-1, + .num_frames = 0, + .arena = arena, + .err = { NULL, 0, false }, + }; + rt->err.buf = rt->msg; + rt->err.cap = SIZEOF(rt->msg); + + rt->frames[rt->num_frames++] = (Frame) { + .retaddr = 0, + .varbase = rt->vars, + }; + + return rt; +} + +WL_String wl_runtime_error(WL_Runtime *rt) +{ + return rt->err.yes + ? (WL_String) { rt->msg, strlen(rt->msg) } + : (WL_String) { NULL, 0 }; +} + +static void rt_read_mem(WL_Runtime *r, void *dst, int len) +{ + ASSERT(r->off + len <= r->code.len); + memcpy(dst, r->code.ptr + r->off, len); + r->off += len; +} + +static uint8_t rt_read_u8(WL_Runtime *rt) +{ + ASSERT(rt->state == RUNTIME_LOOP); + + uint8_t x; + rt_read_mem(rt, &x, SIZEOF(x)); + + return x; +} + +static uint32_t rt_read_u32(WL_Runtime *rt) +{ + ASSERT(rt->state == RUNTIME_LOOP); + + uint32_t x; + rt_read_mem(rt, &x, SIZEOF(x)); + + return x; +} + +static int64_t rt_read_s64(WL_Runtime *rt) +{ + ASSERT(rt->state == RUNTIME_LOOP); + + int64_t x; + rt_read_mem(rt, &x, SIZEOF(x)); + + return x; +} + +static double rt_read_f64(WL_Runtime *rt) +{ + ASSERT(rt->state == RUNTIME_LOOP); + + double x; + rt_read_mem(rt, &x, SIZEOF(x)); + + return x; +} + +static String rt_read_str(WL_Runtime *rt) +{ + ASSERT(rt->state == RUNTIME_LOOP); + uint32_t off = rt_read_u32(rt); + uint32_t len = rt_read_u32(rt); + ASSERT(off + len <= (uint32_t) rt->data.len); + return (String) { rt->data.ptr + off, len }; +} + +static Value *rt_variable(WL_Runtime *rt, uint8_t x) +{ + ASSERT(rt->num_frames > 0); + + Frame *frame = &rt->frames[rt->num_frames-1]; + + ASSERT(frame->varbase - x >= 0 + && frame->varbase - x < MAX_STACK); + + return &rt->values[frame->varbase - x]; +} + +static int values_usage(WL_Runtime *rt) +{ + int num_vars = (MAX_STACK - rt->vars - 1); + return rt->stack + num_vars; +} + +static bool rt_check_stack(WL_Runtime *rt, int min) +{ + if (MAX_STACK - values_usage(rt) < min) { + REPORT(&rt->err, "Out of stack"); + rt->state = RUNTIME_ERROR; + return false; + } + return true; +} + +static bool rt_push_frame(WL_Runtime *rt, uint8_t args) +{ + if (rt->num_frames == MAX_FRAMES) { + REPORT(&rt->err, "Call stack limit reached"); + rt->state = RUNTIME_ERROR; + return false; + } + + if (MAX_STACK - values_usage(rt) < args) { + REPORT(&rt->err, "Stack limit reached"); + rt->state = RUNTIME_ERROR; + return false; + } + + Frame *frame = &rt->frames[rt->num_frames++]; + frame->retaddr = rt->off; + frame->varbase = rt->vars; + + for (int i = 0; i < args; i++) + rt->values[rt->vars--] = rt->values[--rt->stack]; + + return true; +} + +static void rt_pop_frame(WL_Runtime *rt) +{ + ASSERT(rt->num_frames > 0); + Frame *frame = &rt->frames[rt->num_frames-1]; + rt->off = frame->retaddr; + rt->vars = frame->varbase; + rt->num_frames--; +} + +static void rt_set_frame_vars(WL_Runtime *rt, uint8_t num) +{ + ASSERT(rt->num_frames > 0); + Frame *frame = &rt->frames[rt->num_frames-1]; + int num_vars = frame->varbase - rt->vars; + if (num_vars < num) + for (int i = 0; i < num - num_vars; i++) + rt->values[rt->vars - i] = VALUE_NONE; + rt->vars = frame->varbase - num; +} + +static void rt_push_group(WL_Runtime *rt) +{ + if (rt->num_groups == MAX_GROUPS) { + REPORT(&rt->err, "Out of memory"); + rt->state = RUNTIME_ERROR; + return; + } + rt->groups[rt->num_groups++] = rt->stack; +} + +static void rt_pack_group(WL_Runtime *rt) +{ + if (!rt_check_stack(rt, 1)) + return; + + ASSERT(rt->num_groups > 0); + int start = rt->groups[--rt->num_groups]; + int end = rt->stack; + + if (end - start > 1) { + + Value set = value_empty_array(end - start, rt->arena, &rt->err); + if (set == VALUE_ERROR) + return; + + for (int i = start; i < end; i++) + if (!value_append(set, rt->values[i], rt->arena, &rt->err)) + return; + + rt->stack = start; + rt->values[rt->stack++] = set; + } +} + +static void rt_pop_group(WL_Runtime *rt) +{ + ASSERT(rt->num_groups > 0); + 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)) { + + Type t; + Value v1; + Value v2; + Value v3; + uint32_t o; + uint8_t b1; + uint8_t b2; + uint8_t b3; + int64_t i; + double f; + String s; + + case OPCODE_NOPE: + break; + + case OPCODE_JUMP: + rt->off = rt_read_u32(rt); + break; + + case OPCODE_JIFP: + ASSERT(rt->stack > 0); + o = rt_read_u32(rt); + v1 = rt->values[--rt->stack]; + if (v1 == VALUE_FALSE) + rt->off = o; + else if (value_type(v1) != TYPE_BOOL) { + REPORT(&rt->err, "Invalid non-boolean condition"); + rt->state = RUNTIME_ERROR; + break; + } + break; + + case OPCODE_VARS: + b1 = rt_read_u8(rt); + rt_set_frame_vars(rt, b1); + break; + + case OPCODE_OUTPUT: + if (rt->stack > 0) { + rt->cur_output = 0; + rt->num_output = rt->stack; + rt->state = RUNTIME_OUTPUT; + } + break; + + case OPCODE_SYSVAR: + s = rt_read_str(rt); + rt_push_frame(rt, 0); + rt->stack_before_user = rt->stack; + rt->str_for_user = s; + rt->state = RUNTIME_SYSVAR; + break; + + case OPCODE_SYSCALL: + b1 = rt_read_u8(rt); + s = rt_read_str(rt); + rt_push_frame(rt, b1); + rt->stack_before_user = rt->stack; + rt->str_for_user = s; + rt->state = RUNTIME_SYSCALL; + break; + + case OPCODE_CALL: + b1 = rt_read_u8(rt); + o = rt_read_u32(rt); + rt_push_frame(rt, b1); + rt->off = o; + break; + + case OPCODE_RET: + rt_pop_frame(rt); + break; + + case OPCODE_GROUP: + rt_push_group(rt); + break; + + case OPCODE_PACK: + rt_pack_group(rt); + break; + + case OPCODE_GPOP: + rt_pop_group(rt); + break; + + case OPCODE_FOR: + b1 = rt_read_u8(rt); + b2 = rt_read_u8(rt); + b3 = rt_read_u8(rt); + o = rt_read_u32(rt); + + v1 = *rt_variable(rt, b3); + ASSERT(value_type(v1) == TYPE_INT); + i = value_to_s64(v1); + + v2 = *rt_variable(rt, b1); + + if (value_length(v2)-1 == i) { + rt->off = o; + break; + } + i++; + + v1 = value_select_by_index(v2, i, &rt->err); + if (v1 == VALUE_ERROR) break; + + *rt_variable(rt, b2) = v1; + + v1 = value_from_s64(i, rt->arena, &rt->err); // TODO: this could be in-place + *rt_variable(rt, b3) = v1; + break; + + case OPCODE_EXIT: + rt->state = RUNTIME_DONE; + break; + + case OPCODE_POP: + ASSERT(rt->stack > 0); + rt->stack--; + break; + + case OPCODE_SETV: + ASSERT(rt->stack > 0); + b1 = rt_read_u8(rt); + *rt_variable(rt, b1) = rt->values[rt->stack-1]; + break; + + case OPCODE_PUSHV: + if (!rt_check_stack(rt, 1)) break; + b1 = rt_read_u8(rt); + rt->values[rt->stack++] = *rt_variable(rt, b1); + break; + + case OPCODE_PUSHI: + if (!rt_check_stack(rt, 1)) break; + i = rt_read_s64(rt); + v1 = value_from_s64(i, rt->arena, &rt->err); + rt->values[rt->stack++] = v1; + break; + + case OPCODE_PUSHF: + if (!rt_check_stack(rt, 1)) break; + f = rt_read_f64(rt); + v1 = value_from_f64(f, rt->arena, &rt->err); + rt->values[rt->stack++] = v1; + break; + + case OPCODE_PUSHS: + if (!rt_check_stack(rt, 1)) break; + s = rt_read_str(rt); + v1 = value_from_str(s, rt->arena, &rt->err); + rt->values[rt->stack++] = v1; + break; + + case OPCODE_PUSHA: + if (!rt_check_stack(rt, 1)) break; + o = rt_read_u32(rt); + v1 = value_empty_array(o, rt->arena, &rt->err); + rt->values[rt->stack++] = v1; + break; + + case OPCODE_PUSHM: + if (!rt_check_stack(rt, 1)) break; + o = rt_read_u32(rt); + v1 = value_empty_map(o, rt->arena, &rt->err); + rt->values[rt->stack++] = v1; + break; + + case OPCODE_PUSHN: + if (!rt_check_stack(rt, 1)) break; + rt->values[rt->stack++] = VALUE_NONE; + break; + + case OPCODE_PUSHT: + if (!rt_check_stack(rt, 1)) break; + rt->values[rt->stack++] = VALUE_TRUE; + break; + + case OPCODE_PUSHFL: + if (!rt_check_stack(rt, 1)) break; + rt->values[rt->stack++] = VALUE_FALSE; + break; + + case OPCODE_LEN: + ASSERT(rt->stack > 0); + v1 = rt->values[rt->stack-1]; + t = value_type(v1); + if (t != TYPE_ARRAY && t != TYPE_MAP) { + REPORT(&rt->err, "Invalid operation 'len' on non-aggregate value"); + rt->state = RUNTIME_ERROR; + break; + } + v2 = value_from_s64(value_length(v1), rt->arena, &rt->err); + rt->values[rt->stack-1] = v2; + break; + + case OPCODE_NEG: + ASSERT(rt->stack > 0); + v1 = rt->values[rt->stack-1]; + v2 = value_neg(v1, rt->arena, &rt->err); + rt->values[rt->stack-1] = v2; + break; + + case OPCODE_EQL: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_eql(v2, v1) ? VALUE_TRUE : VALUE_FALSE; + rt->values[rt->stack++] = v3; + break; + + case OPCODE_NQL: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_nql(v2, v1) ? VALUE_TRUE : VALUE_FALSE; + rt->values[rt->stack++] = v3; + break; + + case OPCODE_LSS: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_lower(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE; + rt->values[rt->stack++] = v3; + break; + + case OPCODE_GRT: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_greater(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE; + rt->values[rt->stack++] = v3; + break; + + case OPCODE_ADD: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_add(v2, v1, rt->arena, &rt->err); + rt->values[rt->stack++] = v3; + break; + + case OPCODE_SUB: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_sub(v2, v1, rt->arena, &rt->err); + rt->values[rt->stack++] = v3; + break; + + case OPCODE_MUL: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_mul(v2, v1, rt->arena, &rt->err); + rt->values[rt->stack++] = v3; + break; + + case OPCODE_DIV: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_div(v2, v1, rt->arena, &rt->err); + rt->values[rt->stack++] = v3; + break; + + case OPCODE_MOD: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_mod(v2, v1, rt->arena, &rt->err); + rt->values[rt->stack++] = v3; + break; + + case OPCODE_APPEND: + ASSERT(rt->stack > 1); + v2 = rt->values[--rt->stack]; + v1 = rt->values[rt->stack-1]; + value_append(v1, v2, rt->arena, &rt->err); + break; + + case OPCODE_INSERT1: + ASSERT(rt->stack > 2); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = rt->values[rt->stack-1]; + value_insert(v3, v1, v2, rt->arena, &rt->err); + break; + + case OPCODE_INSERT2: + ASSERT(rt->stack > 2); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = rt->values[rt->stack-1]; + value_insert(v2, v1, v3, rt->arena, &rt->err); + break; + + case OPCODE_SELECT: + ASSERT(rt->stack > 1); + v1 = rt->values[--rt->stack]; + v2 = rt->values[--rt->stack]; + v3 = value_select(v2, v1, &rt->err); + rt->values[rt->stack++] = v3; + break; + + default: + UNREACHABLE; + } +} + +WL_EvalResult wl_runtime_eval(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_OUTPUT || rt->cur_output == rt->num_output) { + + switch (rt->state) { + + case RUNTIME_BEGIN: + break; + + case RUNTIME_DONE: + return (WL_EvalResult) { .type=WL_EVAL_DONE }; + + case RUNTIME_ERROR: + return (WL_EvalResult) { .type=WL_EVAL_ERROR }; + + case RUNTIME_OUTPUT: + rt->stack -= rt->num_output; + break; + + 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"); + rt->state = RUNTIME_ERROR; + return (WL_EvalResult) { .type=WL_EVAL_ERROR }; + } + + if (rt->stack == rt->stack_before_user) { + // User didn't push anything on the stack + if (!rt_check_stack(rt, 1)) + return (WL_EvalResult) { .type=WL_EVAL_ERROR }; + rt->values[rt->stack++] = VALUE_NONE; + } + + rt_pop_frame(rt); + } + break; + + case RUNTIME_SYSCALL: + ASSERT(rt->stack >= rt->stack_before_user); + rt_pop_frame(rt); + break; + + default: + UNREACHABLE; + } + + rt->state = RUNTIME_LOOP; + + do { + + step(rt); + + if (rt->err.yes) + rt->state = RUNTIME_ERROR; + + } while (rt->state == RUNTIME_LOOP); + + } + + switch (rt->state) { + + case RUNTIME_BEGIN: + case RUNTIME_LOOP: + UNREACHABLE; + + case RUNTIME_DONE: + break; + + case RUNTIME_ERROR: + return (WL_EvalResult) { .type=WL_EVAL_ERROR }; + + case RUNTIME_OUTPUT: + { + ASSERT(rt->cur_output < rt->num_output); + + Value v = rt->values[rt->stack - rt->num_output + rt->cur_output]; + Type type = value_type(v); + + String str; + if (type == TYPE_STRING) + str = value_to_str(v); + else { + int len = value_convert_to_str(v, rt->buf, SIZEOF(rt->buf)); + if (len > SIZEOF(rt->buf)) { + 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 } }; + } + + case RUNTIME_SYSVAR: + return (WL_EvalResult) { .type=WL_EVAL_SYSVAR, .str=(WL_String) { rt->str_for_user.ptr, rt->str_for_user.len } }; + + case RUNTIME_SYSCALL: + return (WL_EvalResult) { .type=WL_EVAL_SYSCALL, .str=(WL_String) { rt->str_for_user.ptr, rt->str_for_user.len } }; + } + + return (WL_EvalResult) { .type=WL_EVAL_DONE }; +} + +bool wl_streq(WL_String a, char *b, int blen) +{ + if (b == NULL) b = ""; + if (blen < 0) blen = strlen(b); + return streq((String) { a.ptr, a.len }, (String) { b, blen }); +} + +int wl_arg_count(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return -1; + + ASSERT(rt->num_frames > 0); + return rt->frames[rt->num_frames-1].varbase - rt->vars; // TODO: is this right? +} + +static Value user_arg(WL_Runtime *rt, int idx, Type type) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return -1; + + int tot = wl_arg_count(rt); + if (idx < 0 || idx >= tot) + return false; + + Value v = *rt_variable(rt, tot - idx - 1); + if (value_type(v) != type) + return VALUE_ERROR; + + return v; +} + +bool wl_arg_none(WL_Runtime *rt, int idx) +{ + Value v = user_arg(rt, idx, TYPE_NONE); + if (v == VALUE_ERROR) + return false; + return true; +} + +bool wl_arg_bool(WL_Runtime *rt, int idx, bool *x) +{ + Value v = user_arg(rt, idx, TYPE_BOOL); + if (v == VALUE_ERROR) + return false; + *x = (v == VALUE_TRUE); + return true; +} + +bool wl_arg_s64(WL_Runtime *rt, int idx, int64_t *x) +{ + Value v = user_arg(rt, idx, TYPE_INT); + if (v == VALUE_ERROR) + return false; + *x = value_to_s64(v); + return true; +} + +bool wl_arg_f64(WL_Runtime *rt, int idx, double *x) +{ + Value v = user_arg(rt, idx, TYPE_FLOAT); + if (v == VALUE_ERROR) + return false; + *x = value_to_f64(v); + return true; +} + +bool wl_arg_str(WL_Runtime *rt, int idx, WL_String *x) +{ + Value v = user_arg(rt, idx, TYPE_STRING); + if (v == VALUE_ERROR) + return false; + String s = value_to_str(v); + *x = (WL_String) { s.ptr, s.len }; + return true; +} + +bool wl_arg_array(WL_Runtime *rt, int idx) +{ + Value v = user_arg(rt, idx, TYPE_ARRAY); + if (v == VALUE_ERROR) + return false; + return true; +} + +bool wl_arg_map(WL_Runtime *rt, int idx) +{ + Value v = user_arg(rt, idx, TYPE_MAP); + if (v == VALUE_ERROR) + return false; + return true; +} + +static Value user_peek(WL_Runtime *rt, int off, Type type) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return VALUE_ERROR; + + if (rt->stack + off < rt->stack_before_user || off >= 0) + return VALUE_ERROR; + + Value v = rt->values[rt->stack + off]; + if (value_type(v) != type) + return VALUE_ERROR; + + return v; +} + +bool wl_peek_none(WL_Runtime *rt, int off) +{ + Value v = user_peek(rt, off, TYPE_NONE); + if (v == VALUE_ERROR) + return false; + return true; +} + +bool wl_peek_bool(WL_Runtime *rt, int off, bool *x) +{ + Value v = user_peek(rt, off, TYPE_BOOL); + if (v == VALUE_ERROR) + return false; + *x = (v == VALUE_TRUE); + return true; +} + +bool wl_peek_s64(WL_Runtime *rt, int off, int64_t *x) +{ + Value v = user_peek(rt, off, TYPE_INT); + if (v == VALUE_ERROR) + return false; + *x = value_to_s64(v); + return true; +} + +bool wl_peek_f64(WL_Runtime *rt, int off, double *x) +{ + Value v = user_peek(rt, off, TYPE_FLOAT); + if (v == VALUE_ERROR) + return false; + *x = value_to_f64(v); + return true; +} + +bool wl_peek_str(WL_Runtime *rt, int off, WL_String *x) +{ + Value v = user_peek(rt, off, TYPE_STRING); + if (v == VALUE_ERROR) + return false; + String s = value_to_str(v); + *x = (WL_String) { s.ptr, s.len }; + return true; +} + +bool wl_pop_any(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return VALUE_ERROR; + + if (rt->stack == rt->stack_before_user) + return false; + + ASSERT(rt->stack > 0); + rt->stack--; + return true; +} + +static Value user_pop(WL_Runtime *rt, Type type) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return VALUE_ERROR; + + if (rt->stack == rt->stack_before_user) + return VALUE_ERROR; + + ASSERT(rt->stack > 0); + Value v = rt->values[rt->stack-1]; + if (value_type(v) != type) + return VALUE_ERROR; + + rt->stack--; + return v; +} + +bool wl_pop_none(WL_Runtime *rt) +{ + Value v = user_pop(rt, TYPE_NONE); + if (v == VALUE_ERROR) + return false; + return true; +} + +bool wl_pop_bool(WL_Runtime *rt, bool *x) +{ + Value v = user_pop(rt, TYPE_BOOL); + if (v == VALUE_ERROR) + return false; + *x = (v == VALUE_TRUE); + return true; +} + +bool wl_pop_s64(WL_Runtime *rt, int64_t *x) +{ + Value v = user_pop(rt, TYPE_INT); + if (v == VALUE_ERROR) + return false; + *x = value_to_s64(v); + return true; +} + +bool wl_pop_f64(WL_Runtime *rt, double *x) +{ + Value v = user_pop(rt, TYPE_FLOAT); + if (v == VALUE_ERROR) + return false; + *x = value_to_f64(v); + return true; +} + +bool wl_pop_str(WL_Runtime *rt, WL_String *x) +{ + Value v = user_pop(rt, TYPE_STRING); + if (v == VALUE_ERROR) + return false; + String s = value_to_str(v); + *x = (WL_String) { s.ptr, s.len }; + return true; +} + +void wl_push_none(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + rt->values[rt->stack++] = VALUE_NONE; +} + +void wl_push_true(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + rt->values[rt->stack++] = VALUE_TRUE; +} + +void wl_push_false(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + rt->values[rt->stack++] = VALUE_FALSE; +} + +void wl_push_s64(WL_Runtime *rt, int64_t x) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + Value v = value_from_s64(x, rt->arena, &rt->err); + if (v == VALUE_ERROR) { + rt->state = RUNTIME_ERROR; + return; + } + + rt->values[rt->stack++] = v; +} + +void wl_push_f64(WL_Runtime *rt, double x) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + Value v = value_from_f64(x, rt->arena, &rt->err); + if (v == VALUE_ERROR) { + rt->state = RUNTIME_ERROR; + return; + } + + rt->values[rt->stack++] = v; +} + +void wl_push_str(WL_Runtime *rt, WL_String x) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + Value v = value_from_str((String) { x.ptr, x.len }, rt->arena, &rt->err); + if (v == VALUE_ERROR) { + rt->state = RUNTIME_ERROR; + return; + } + + rt->values[rt->stack++] = v; +} + +void wl_push_array(WL_Runtime *rt, int cap) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + Value v = value_empty_array(cap, rt->arena, &rt->err); + if (v == VALUE_ERROR) { + rt->state = RUNTIME_ERROR; + return; + } + + rt->values[rt->stack++] = v; +} + +void wl_push_map(WL_Runtime *rt, int cap) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + Value v = value_empty_map(cap, rt->arena, &rt->err); + if (v == VALUE_ERROR) { + rt->state = RUNTIME_ERROR; + return; + } + + rt->values[rt->stack++] = v; +} + +void wl_push_arg(WL_Runtime *rt, int idx) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (!rt_check_stack(rt, 1)) + return; + + int tot = wl_arg_count(rt); + if (idx < 0 || idx >= tot) { + REPORT(&rt->err, "Invalid API usagge"); + rt->state = RUNTIME_ERROR; + return; + } + + rt->values[rt->stack++] = *rt_variable(rt, tot - idx - 1); +} + +void wl_insert(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (rt->stack - rt->stack_before_user < 3) { + REPORT(&rt->err, "Invalid API usagge"); + rt->state = RUNTIME_ERROR; + return; + } + + Value key = rt->values[--rt->stack]; + Value val = rt->values[--rt->stack]; + Value set = rt->values[rt->stack-1]; + + if (!value_insert(set, key, val, rt->arena, &rt->err)) { + rt->state = RUNTIME_ERROR; + return; + } +} + +void wl_append(WL_Runtime *rt) +{ + if (rt->state != RUNTIME_SYSVAR && + rt->state != RUNTIME_SYSCALL) + return; + + if (rt->stack - rt->stack_before_user < 2) { + REPORT(&rt->err, "Invalid API usagge"); + rt->state = RUNTIME_ERROR; + return; + } + + Value val = rt->values[--rt->stack]; + Value set = rt->values[rt->stack-1]; + + if (!value_append(set, val, rt->arena, &rt->err)) { + rt->state = RUNTIME_ERROR; + 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 ceebb50..4dec6bb 100644 --- a/3p/WL.h +++ b/3p/WL.h @@ -1,90 +1,90 @@ -#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); +#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); diff --git a/3p/crypt_blowfish.c b/3p/crypt_blowfish.c index 9d3f3be..5d0062c 100644 --- a/3p/crypt_blowfish.c +++ b/3p/crypt_blowfish.c @@ -1,907 +1,907 @@ -/* - * The crypt_blowfish homepage is: - * - * http://www.openwall.com/crypt/ - * - * This code comes from John the Ripper password cracker, with reentrant - * and crypt(3) interfaces added, but optimizations specific to password - * cracking removed. - * - * Written by Solar Designer in 1998-2014. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * It is my intent that you should be able to use this on your system, - * as part of a software package, or anywhere else to improve security, - * ensure compatibility, or for any other purpose. I would appreciate - * it if you give credit where it is due and keep your modifications in - * the public domain as well, but I don't require that in order to let - * you place this code and any modifications you make under a license - * of your choice. - * - * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix - * "$2b$", originally by Niels Provos , and it uses - * some of his ideas. The password hashing algorithm was designed by David - * Mazieres . For information on the level of - * compatibility for bcrypt hash prefixes other than "$2b$", please refer to - * the comments in BF_set_key() below and to the included crypt(3) man page. - * - * There's a paper on the algorithm that explains its design decisions: - * - * http://www.usenix.org/events/usenix99/provos.html - * - * Some of the tricks in BF_ROUND might be inspired by Eric Young's - * Blowfish library (I can't be sure if I would think of something if I - * hadn't seen his code). - */ - -#include - -#include -#ifndef __set_errno -#define __set_errno(val) errno = (val) -#endif - -/* Just to make sure the prototypes match the actual definitions */ -#include "crypt_blowfish.h" - -#ifdef __i386__ -#define BF_ASM 1 -#define BF_SCALE 1 -#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) -#define BF_ASM 0 -#define BF_SCALE 1 -#else -#define BF_ASM 0 -#define BF_SCALE 0 -#endif - -typedef unsigned int BF_word; -typedef signed int BF_word_signed; - -/* Number of Blowfish rounds, this is also hardcoded into a few places */ -#define BF_N 16 - -typedef BF_word BF_key[BF_N + 2]; - -typedef struct { - BF_word S[4][0x100]; - BF_key P; -} BF_ctx; - -/* - * Magic IV for 64 Blowfish encryptions that we do at the end. - * The string is "OrpheanBeholderScryDoubt" on big-endian. - */ -static BF_word BF_magic_w[6] = { - 0x4F727068, 0x65616E42, 0x65686F6C, - 0x64657253, 0x63727944, 0x6F756274 -}; - -/* - * P-box and S-box tables initialized with digits of Pi. - */ -static BF_ctx BF_init_state = { - { - { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a - }, { - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 - }, { - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 - }, { - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - } - }, { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - } -}; - -static unsigned char BF_itoa64[64 + 1] = - "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - -static unsigned char BF_atoi64[0x60] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, - 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, - 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 -}; - -#define BF_safe_atoi64(dst, src) \ -{ \ - tmp = (unsigned char)(src); \ - if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ - tmp = BF_atoi64[tmp]; \ - if (tmp > 63) return -1; \ - (dst) = tmp; \ -} - -static int BF_decode(BF_word *dst, const char *src, int size) -{ - unsigned char *dptr = (unsigned char *)dst; - unsigned char *end = dptr + size; - const unsigned char *sptr = (const unsigned char *)src; - unsigned int tmp, c1, c2, c3, c4; - - do { - BF_safe_atoi64(c1, *sptr++); - BF_safe_atoi64(c2, *sptr++); - *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); - if (dptr >= end) break; - - BF_safe_atoi64(c3, *sptr++); - *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); - if (dptr >= end) break; - - BF_safe_atoi64(c4, *sptr++); - *dptr++ = ((c3 & 0x03) << 6) | c4; - } while (dptr < end); - - return 0; -} - -static void BF_encode(char *dst, const BF_word *src, int size) -{ - const unsigned char *sptr = (const unsigned char *)src; - const unsigned char *end = sptr + size; - unsigned char *dptr = (unsigned char *)dst; - unsigned int c1, c2; - - do { - c1 = *sptr++; - *dptr++ = BF_itoa64[c1 >> 2]; - c1 = (c1 & 0x03) << 4; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 4; - *dptr++ = BF_itoa64[c1]; - c1 = (c2 & 0x0f) << 2; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 6; - *dptr++ = BF_itoa64[c1]; - *dptr++ = BF_itoa64[c2 & 0x3f]; - } while (sptr < end); -} - -static void BF_swap(BF_word *x, int count) -{ - static int endianness_check = 1; - char *is_little_endian = (char *)&endianness_check; - BF_word tmp; - - if (*is_little_endian) - do { - tmp = *x; - tmp = (tmp << 16) | (tmp >> 16); - *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); - } while (--count); -} - -#if BF_SCALE -/* Architectures which can shift addresses left by 2 bits with no extra cost */ -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp2 = L >> 8; \ - tmp2 &= 0xFF; \ - tmp3 = L >> 16; \ - tmp3 &= 0xFF; \ - tmp4 = L >> 24; \ - tmp1 = data.ctx.S[3][tmp1]; \ - tmp2 = data.ctx.S[2][tmp2]; \ - tmp3 = data.ctx.S[1][tmp3]; \ - tmp3 += data.ctx.S[0][tmp4]; \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#else -/* Architectures with no complicated addressing modes supported */ -#define BF_INDEX(S, i) \ - (*((BF_word *)(((unsigned char *)S) + (i)))) -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp1 <<= 2; \ - tmp2 = L >> 6; \ - tmp2 &= 0x3FC; \ - tmp3 = L >> 14; \ - tmp3 &= 0x3FC; \ - tmp4 = L >> 22; \ - tmp4 &= 0x3FC; \ - tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ - tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ - tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ - tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#endif - -/* - * Encrypt one block, BF_N is hardcoded here. - */ -#define BF_ENCRYPT \ - L ^= data.ctx.P[0]; \ - BF_ROUND(L, R, 0); \ - BF_ROUND(R, L, 1); \ - BF_ROUND(L, R, 2); \ - BF_ROUND(R, L, 3); \ - BF_ROUND(L, R, 4); \ - BF_ROUND(R, L, 5); \ - BF_ROUND(L, R, 6); \ - BF_ROUND(R, L, 7); \ - BF_ROUND(L, R, 8); \ - BF_ROUND(R, L, 9); \ - BF_ROUND(L, R, 10); \ - BF_ROUND(R, L, 11); \ - BF_ROUND(L, R, 12); \ - BF_ROUND(R, L, 13); \ - BF_ROUND(L, R, 14); \ - BF_ROUND(R, L, 15); \ - tmp4 = R; \ - R = L; \ - L = tmp4 ^ data.ctx.P[BF_N + 1]; - -#if BF_ASM -#define BF_body() \ - _BF_body_r(&data.ctx); -#else -#define BF_body() \ - L = R = 0; \ - ptr = data.ctx.P; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.P[BF_N + 2]); \ -\ - ptr = data.ctx.S[0]; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.S[3][0xFF]); -#endif - -static void BF_set_key(const char *key, BF_key expanded, BF_key initial, - unsigned char flags) -{ - const char *ptr = key; - unsigned int bug, i, j; - BF_word safety, sign, diff, tmp[2]; - -/* - * There was a sign extension bug in older revisions of this function. While - * we would have liked to simply fix the bug and move on, we have to provide - * a backwards compatibility feature (essentially the bug) for some systems and - * a safety measure for some others. The latter is needed because for certain - * multiple inputs to the buggy algorithm there exist easily found inputs to - * the correct algorithm that produce the same hash. Thus, we optionally - * deviate from the correct algorithm just enough to avoid such collisions. - * While the bug itself affected the majority of passwords containing - * characters with the 8th bit set (although only a percentage of those in a - * collision-producing way), the anti-collision safety measure affects - * only a subset of passwords containing the '\xff' character (not even all of - * those passwords, just some of them). This character is not found in valid - * UTF-8 sequences and is rarely used in popular 8-bit character encodings. - * Thus, the safety measure is unlikely to cause much annoyance, and is a - * reasonable tradeoff to use when authenticating against existing hashes that - * are not reliably known to have been computed with the correct algorithm. - * - * We use an approach that tries to minimize side-channel leaks of password - * information - that is, we mostly use fixed-cost bitwise operations instead - * of branches or table lookups. (One conditional branch based on password - * length remains. It is not part of the bug aftermath, though, and is - * difficult and possibly unreasonable to avoid given the use of C strings by - * the caller, which results in similar timing leaks anyway.) - * - * For actual implementation, we set an array index in the variable "bug" - * (0 means no bug, 1 means sign extension bug emulation) and a flag in the - * variable "safety" (bit 16 is set when the safety measure is requested). - * Valid combinations of settings are: - * - * Prefix "$2a$": bug = 0, safety = 0x10000 - * Prefix "$2b$": bug = 0, safety = 0 - * Prefix "$2x$": bug = 1, safety = 0 - * Prefix "$2y$": bug = 0, safety = 0 - */ - bug = (unsigned int)flags & 1; - safety = ((BF_word)flags & 2) << 15; - - sign = diff = 0; - - for (i = 0; i < BF_N + 2; i++) { - tmp[0] = tmp[1] = 0; - for (j = 0; j < 4; j++) { - tmp[0] <<= 8; - tmp[0] |= (unsigned char)*ptr; /* correct */ - tmp[1] <<= 8; - tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ -/* - * Sign extension in the first char has no effect - nothing to overwrite yet, - * and those extra 24 bits will be fully shifted out of the 32-bit word. For - * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign - * extension in tmp[1] occurs. Once this flag is set, it remains set. - */ - if (j) - sign |= tmp[1] & 0x80; - if (!*ptr) - ptr = key; - else - ptr++; - } - diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ - - expanded[i] = tmp[bug]; - initial[i] = BF_init_state.P[i] ^ tmp[bug]; - } - -/* - * At this point, "diff" is zero iff the correct and buggy algorithms produced - * exactly the same result. If so and if "sign" is non-zero, which indicates - * that there was a non-benign sign extension, this means that we have a - * collision between the correctly computed hash for this password and a set of - * passwords that could be supplied to the buggy algorithm. Our safety measure - * is meant to protect from such many-buggy to one-correct collisions, by - * deviating from the correct algorithm in such cases. Let's check for this. - */ - diff |= diff >> 16; /* still zero iff exact match */ - diff &= 0xffff; /* ditto */ - diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ - sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ - sign &= ~diff & safety; /* action needed? */ - -/* - * If we have determined that we need to deviate from the correct algorithm, - * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but - * let's stick to it now. It came out of the approach we used above, and it's - * not any worse than any other choice we could make.) - * - * It is crucial that we don't do the same to the expanded key used in the main - * Eksblowfish loop. By doing it to only one of these two, we deviate from a - * state that could be directly specified by a password to the buggy algorithm - * (and to the fully correct one as well, but that's a side-effect). - */ - initial[0] ^= sign; -} - -static const unsigned char flags_by_subtype[26] = - {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; - -static char *BF_crypt(const char *key, const char *setting, - char *output, int size, - BF_word min) -{ -#if BF_ASM - extern void _BF_body_r(BF_ctx *ctx); -#endif - struct { - BF_ctx ctx; - BF_key expanded_key; - union { - BF_word salt[4]; - BF_word output[6]; - } binary; - } data; - BF_word L, R; - BF_word tmp1, tmp2, tmp3, tmp4; - BF_word *ptr; - BF_word count; - int i; - - if (size < 7 + 22 + 31 + 1) { - __set_errno(ERANGE); - return NULL; - } - - if (setting[0] != '$' || - setting[1] != '2' || - setting[2] < 'a' || setting[2] > 'z' || - !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || - setting[3] != '$' || - setting[4] < '0' || setting[4] > '3' || - setting[5] < '0' || setting[5] > '9' || - (setting[4] == '3' && setting[5] > '1') || - setting[6] != '$') { - __set_errno(EINVAL); - return NULL; - } - - count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); - if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { - __set_errno(EINVAL); - return NULL; - } - BF_swap(data.binary.salt, 4); - - BF_set_key(key, data.expanded_key, data.ctx.P, - flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); - - memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); - - L = R = 0; - for (i = 0; i < BF_N + 2; i += 2) { - L ^= data.binary.salt[i & 2]; - R ^= data.binary.salt[(i & 2) + 1]; - BF_ENCRYPT; - data.ctx.P[i] = L; - data.ctx.P[i + 1] = R; - } - - ptr = data.ctx.S[0]; - do { - ptr += 4; - L ^= data.binary.salt[(BF_N + 2) & 3]; - R ^= data.binary.salt[(BF_N + 3) & 3]; - BF_ENCRYPT; - *(ptr - 4) = L; - *(ptr - 3) = R; - - L ^= data.binary.salt[(BF_N + 4) & 3]; - R ^= data.binary.salt[(BF_N + 5) & 3]; - BF_ENCRYPT; - *(ptr - 2) = L; - *(ptr - 1) = R; - } while (ptr < &data.ctx.S[3][0xFF]); - - do { - int done; - - for (i = 0; i < BF_N + 2; i += 2) { - data.ctx.P[i] ^= data.expanded_key[i]; - data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; - } - - done = 0; - do { - BF_body(); - if (done) - break; - done = 1; - - tmp1 = data.binary.salt[0]; - tmp2 = data.binary.salt[1]; - tmp3 = data.binary.salt[2]; - tmp4 = data.binary.salt[3]; - for (i = 0; i < BF_N; i += 4) { - data.ctx.P[i] ^= tmp1; - data.ctx.P[i + 1] ^= tmp2; - data.ctx.P[i + 2] ^= tmp3; - data.ctx.P[i + 3] ^= tmp4; - } - data.ctx.P[16] ^= tmp1; - data.ctx.P[17] ^= tmp2; - } while (1); - } while (--count); - - for (i = 0; i < 6; i += 2) { - L = BF_magic_w[i]; - R = BF_magic_w[i + 1]; - - count = 64; - do { - BF_ENCRYPT; - } while (--count); - - data.binary.output[i] = L; - data.binary.output[i + 1] = R; - } - - memcpy(output, setting, 7 + 22 - 1); - output[7 + 22 - 1] = BF_itoa64[(int) - BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; - -/* This has to be bug-compatible with the original implementation, so - * only encode 23 of the 24 bytes. :-) */ - BF_swap(data.binary.output, 6); - BF_encode(&output[7 + 22], data.binary.output, 23); - output[7 + 22 + 31] = '\0'; - - return output; -} - -int _crypt_output_magic(const char *setting, char *output, int size) -{ - if (size < 3) - return -1; - - output[0] = '*'; - output[1] = '0'; - output[2] = '\0'; - - if (setting[0] == '*' && setting[1] == '0') - output[1] = '1'; - - return 0; -} - -/* - * Please preserve the runtime self-test. It serves two purposes at once: - * - * 1. We really can't afford the risk of producing incompatible hashes e.g. - * when there's something like gcc bug 26587 again, whereas an application or - * library integrating this code might not also integrate our external tests or - * it might not run them after every build. Even if it does, the miscompile - * might only occur on the production build, but not on a testing build (such - * as because of different optimization settings). It is painful to recover - * from incorrectly-computed hashes - merely fixing whatever broke is not - * enough. Thus, a proactive measure like this self-test is needed. - * - * 2. We don't want to leave sensitive data from our actual password hash - * computation on the stack or in registers. Previous revisions of the code - * would do explicit cleanups, but simply running the self-test after hash - * computation is more reliable. - * - * The performance cost of this quick self-test is around 0.6% at the "$2a$08" - * setting. - */ -char *_crypt_blowfish_rn(const char *key, const char *setting, - char *output, int size) -{ - const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; - const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; - static const char * const test_hashes[2] = - {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ - "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ - const char *test_hash = test_hashes[0]; - char *retval; - const char *p; - int save_errno, ok; - struct { - char s[7 + 22 + 1]; - char o[7 + 22 + 31 + 1 + 1 + 1]; - } buf; - -/* Hash the supplied password */ - _crypt_output_magic(setting, output, size); - retval = BF_crypt(key, setting, output, size, 16); - save_errno = errno; - -/* - * Do a quick self-test. It is important that we make both calls to BF_crypt() - * from the same scope such that they likely use the same stack locations, - * which makes the second call overwrite the first call's sensitive data on the - * stack and makes it more likely that any alignment related issues would be - * detected by the self-test. - */ - memcpy(buf.s, test_setting, sizeof(buf.s)); - if (retval) { - unsigned int flags = flags_by_subtype[ - (unsigned int)(unsigned char)setting[2] - 'a']; - test_hash = test_hashes[flags & 1]; - buf.s[2] = setting[2]; - } - memset(buf.o, 0x55, sizeof(buf.o)); - buf.o[sizeof(buf.o) - 1] = 0; - p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); - - ok = (p == buf.o && - !memcmp(p, buf.s, 7 + 22) && - !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); - - { - const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; - BF_key ae, ai, ye, yi; - BF_set_key(k, ae, ai, 2); /* $2a$ */ - BF_set_key(k, ye, yi, 4); /* $2y$ */ - ai[0] ^= 0x10000; /* undo the safety (for comparison) */ - ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && - !memcmp(ae, ye, sizeof(ae)) && - !memcmp(ai, yi, sizeof(ai)); - } - - __set_errno(save_errno); - if (ok) - return retval; - -/* Should not happen */ - _crypt_output_magic(setting, output, size); - __set_errno(EINVAL); /* pretend we don't support this hash type */ - return NULL; -} - -char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - if (size < 16 || output_size < 7 + 22 + 1 || - (count && (count < 4 || count > 31)) || - prefix[0] != '$' || prefix[1] != '2' || - (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); - return NULL; - } - - if (!count) count = 5; - - output[0] = '$'; - output[1] = '2'; - output[2] = prefix[2]; - output[3] = '$'; - output[4] = '0' + count / 10; - output[5] = '0' + count % 10; - output[6] = '$'; - - BF_encode(&output[7], (const BF_word *)input, 16); - output[7 + 22] = '\0'; - - return output; -} +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos , and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres . For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#include "crypt_blowfish.h" + +#ifdef __i386__ +#define BF_ASM 1 +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#if BF_ASM +#define BF_body() \ + _BF_body_r(&data.ctx); +#else +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); +#endif + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ +#if BF_ASM + extern void _BF_body_r(BF_ctx *ctx); +#endif + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + __set_errno(ERANGE); + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + __set_errno(EINVAL); + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + __set_errno(EINVAL); + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int save_errno, ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + save_errno = errno; + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + __set_errno(save_errno); + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +} + +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} diff --git a/3p/crypt_blowfish.h b/3p/crypt_blowfish.h index 2ee0d8c..d289eb3 100644 --- a/3p/crypt_blowfish.h +++ b/3p/crypt_blowfish.h @@ -1,27 +1,27 @@ -/* - * Written by Solar Designer in 2000-2011. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * See crypt_blowfish.c for more information. - */ - -#ifndef _CRYPT_BLOWFISH_H -#define _CRYPT_BLOWFISH_H - -extern int _crypt_output_magic(const char *setting, char *output, int size); -extern char *_crypt_blowfish_rn(const char *key, const char *setting, - char *output, int size); -extern char *_crypt_gensalt_blowfish_rn(const char *prefix, - unsigned long count, - const char *input, int size, char *output, int output_size); - -#endif +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#ifndef _CRYPT_BLOWFISH_H +#define _CRYPT_BLOWFISH_H + +extern int _crypt_output_magic(const char *setting, char *output, int size); +extern char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size); +extern char *_crypt_gensalt_blowfish_rn(const char *prefix, + unsigned long count, + const char *input, int size, char *output, int output_size); + +#endif diff --git a/Makefile b/Makefile index 616047e..b325198 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,23 @@ -ifeq ($(OS),Windows_NT) - EXT = .exe - LFLAGS = -lws2_32 -lbcrypt -else - EXT = .out - LFLAGS = -endif - -CFLAGS = -Wall -Wextra -O0 -g3 -I3p - -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 +ifeq ($(OS),Windows_NT) + EXT = .exe + LFLAGS = -lws2_32 -lbcrypt +else + EXT = .out + LFLAGS = +endif + +CFLAGS = -Wall -Wextra -O0 -g3 -I3p -fsanitize=address,undefined + +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 diff --git a/TODO b/TODO index f57b812..13dbf0e 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ -bcrypt passwords -session management using proper random tokens -CSRF protection -input validation to avoid XSS and injection -only allow signup, login ecc over HTTPS +input validation to avoid XSS and injection +only allow signup, login ecc over HTTPS +session expiration +mark cookies as secure \ No newline at end of file diff --git a/misc/schema.sql b/misc/schema.sql index a5d3e17..b50a9d7 100644 --- a/misc/schema.sql +++ b/misc/schema.sql @@ -1,29 +1,29 @@ -CREATE TABLE IF NOT EXISTS Users ( - id INTEGER PRIMARY KEY, - username TEXT NOT NULL UNIQUE, - hash TEXT NOT NULL, - email TEXT NOT NULL UNIQUE, - signup_time DATETIME DEFAULT CURRENT_TIMESTAMP -); - -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, - FOREIGN KEY (author) REFERENCES Users(id) -); - -CREATE TABLE IF NOT EXISTS Comments ( - id INTEGER PRIMARY KEY, - parent_post INTEGER NOT NULL, - parent_comment INTEGER, - author INTEGER NOT NULL, - content TEXT NOT NULL, - submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (author) REFERENCES Users(id), - FOREIGN KEY (parent_post) REFERENCES Posts(id), - FOREIGN KEY (parent_comment) REFERENCES Comments(id) -); +CREATE TABLE IF NOT EXISTS Users ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + hash TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + signup_time DATETIME DEFAULT CURRENT_TIMESTAMP +); + +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, + FOREIGN KEY (author) REFERENCES Users(id) +); + +CREATE TABLE IF NOT EXISTS Comments ( + id INTEGER PRIMARY KEY, + parent_post INTEGER NOT NULL, + parent_comment INTEGER, + author INTEGER NOT NULL, + content TEXT NOT NULL, + submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (author) REFERENCES Users(id), + FOREIGN KEY (parent_post) REFERENCES Posts(id), + FOREIGN KEY (parent_comment) REFERENCES Comments(id) +); diff --git a/old/login.wl b/old/login.wl index 587b638..cdfcf51 100644 --- a/old/login.wl +++ b/old/login.wl @@ -1,147 +1,147 @@ - - - cozis news - login - - - - -
- -
-
- Made with love by cozis -
- - + + + cozis news - login + + + + +
+ +
+
+ Made with love by cozis +
+ + diff --git a/old/new.wl b/old/new.wl index 78a189a..a5b7ef8 100644 --- a/old/new.wl +++ b/old/new.wl @@ -1,121 +1,121 @@ - -fun page(title, style, content) - - - CN - \title - - \(style) - - - -
- \content -
-
- Made with love by cozis -
- - - -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 style = - - -let content = -
- \for post in posts: -
- -
- \post.date | \post.num_comments comments -
-
-
- -page("new", style, content) + +fun page(title, style, content) + + + CN - \title + + \(style) + + + +
+ \content +
+
+ Made with love by cozis +
+ + + +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 style = + + +let content = +
+ \for post in posts: +
+ +
+ \post.date | \post.num_comments comments +
+
+
+ +page("new", style, content) diff --git a/old/notfound.wl b/old/notfound.wl index bf98285..4f2f397 100644 --- a/old/notfound.wl +++ b/old/notfound.wl @@ -1,114 +1,114 @@ - - - cozis news - not found - - - - -
-
-
404
-
Page Not Found
-
- Looks like this page wandered off somewhere. -
-
-
-
- Made with love by cozis -
- + + + cozis news - not found + + + + +
+
+
404
+
Page Not Found
+
+ Looks like this page wandered off somewhere. +
+
+
+
+ Made with love by cozis +
+ \ No newline at end of file diff --git a/old/signup.wl b/old/signup.wl index c08c6a1..b393175 100644 --- a/old/signup.wl +++ b/old/signup.wl @@ -1,21 +1,21 @@ -include "pages/page.wl" -include "pages/login_and_signup_style.wl" - -let main = -
- Welcome! -
- - - - - - -
-
- -page("Log-In", none, style, main) +include "pages/page.wl" +include "pages/login_and_signup_style.wl" + +let main = +
+ Welcome! +
+ + + + + + +
+
+ +page("Log-In", none, style, main) diff --git a/old/thread.wl b/old/thread.wl index c6f3380..2b9af51 100644 --- a/old/thread.wl +++ b/old/thread.wl @@ -1,340 +1,340 @@ - - - - cozis news - thread - - - - -
-
- -
- submitted 3 hours ago by kittenlover42 | 127 comments -
-
- Hey HN! I've been working on Kitten TTS for the past 6 months - a tiny text-to-speech model that runs entirely on CPU. At just 25MB, it's perfect for embedded applications and privacy-focused projects where you don't want to send text to external services. -

- The model is trained on a diverse dataset and supports multiple languages. Performance is surprisingly good for the size - would love to hear your thoughts! -
-
- reply -
-
- -
- - -
- -
- -
-
- techgeek99 2 hours ago -
-
- This is incredible! I've been looking for something exactly like this for my IoT project. The fact that it's only 25MB and runs on CPU is a game changer. How does the voice quality compare to larger models? -
-
- reply | flag -
-
-
- -
- -
-
- kittenlover42 2 hours ago -
-
- Thanks! The voice quality is obviously not as good as larger models like Tacotron2 or the commercial APIs, but it's surprisingly decent for most use cases. I'd say it's about 75% of the quality at 1% of the size. Perfect for things like reading notifications aloud or simple voice interfaces. -
-
- reply | flag -
-
-
- -
- -
-
- embedded_dev 1 hour ago -
-
- That sounds perfect for embedded applications! What's the inference speed like on something like a Raspberry Pi? -
-
- reply | flag -
-
-
- -
- -
-
- ml_researcher 2 hours ago -
-
- Very impressive work! I'm curious about the architecture - are you using knowledge distillation from a larger model, or did you train this from scratch? The 25MB constraint must have required some clever optimizations. -
-
- reply | flag -
-
-
- -
- -
-
- kittenlover42 1 hour ago -
-
- Great question! It's a combination approach. I started with a modified Tacotron architecture but with much smaller hidden dimensions, then used knowledge distillation from WaveNet to get the vocoder down to size. Also heavily quantized the weights and used some pruning techniques. Happy to share more technical details if you're interested! -
-
- reply | flag -
-
-
- -
- -
-
- privacy_advocate 1 hour ago -
-
- Love that this runs locally! So tired of having to send my text to Google/Amazon/etc just to get speech synthesis. This is exactly what we need more of in the open source community. -
-
- reply | flag -
-
-
- -
- -
- -
-
- Made with love by cozis -
- + + + + cozis news - thread + + + + +
+
+ +
+ submitted 3 hours ago by kittenlover42 | 127 comments +
+
+ Hey HN! I've been working on Kitten TTS for the past 6 months - a tiny text-to-speech model that runs entirely on CPU. At just 25MB, it's perfect for embedded applications and privacy-focused projects where you don't want to send text to external services. +

+ The model is trained on a diverse dataset and supports multiple languages. Performance is surprisingly good for the size - would love to hear your thoughts! +
+
+ reply +
+
+ +
+ + +
+ +
+ +
+
+ techgeek99 2 hours ago +
+
+ This is incredible! I've been looking for something exactly like this for my IoT project. The fact that it's only 25MB and runs on CPU is a game changer. How does the voice quality compare to larger models? +
+
+ reply | flag +
+
+
+ +
+ +
+
+ kittenlover42 2 hours ago +
+
+ Thanks! The voice quality is obviously not as good as larger models like Tacotron2 or the commercial APIs, but it's surprisingly decent for most use cases. I'd say it's about 75% of the quality at 1% of the size. Perfect for things like reading notifications aloud or simple voice interfaces. +
+
+ reply | flag +
+
+
+ +
+ +
+
+ embedded_dev 1 hour ago +
+
+ That sounds perfect for embedded applications! What's the inference speed like on something like a Raspberry Pi? +
+
+ reply | flag +
+
+
+ +
+ +
+
+ ml_researcher 2 hours ago +
+
+ Very impressive work! I'm curious about the architecture - are you using knowledge distillation from a larger model, or did you train this from scratch? The 25MB constraint must have required some clever optimizations. +
+
+ reply | flag +
+
+
+ +
+ +
+
+ kittenlover42 1 hour ago +
+
+ Great question! It's a combination approach. I started with a modified Tacotron architecture but with much smaller hidden dimensions, then used knowledge distillation from WaveNet to get the vocoder down to size. Also heavily quantized the weights and used some pruning techniques. Happy to share more technical details if you're interested! +
+
+ reply | flag +
+
+
+ +
+ +
+
+ privacy_advocate 1 hour ago +
+
+ Love that this runs locally! So tired of having to send my text to Google/Amazon/etc just to get speech synthesis. This is exactly what we need more of in the open source community. +
+
+ reply | flag +
+
+
+ +
+ +
+ +
+
+ Made with love by cozis +
+ \ No newline at end of file diff --git a/pages/index.wl b/pages/index.wl index 1476628..073ffdd 100644 --- a/pages/index.wl +++ b/pages/index.wl @@ -1,67 +1,67 @@ -include "pages/page.wl" - -let posts = $query("SELECT P.id, P.title, P.is_link, P.content, (SELECT COUNT(*) FROM Comments as C WHERE c.parent_post=P.id) as num_comments, CURRENT_TIMESTAMP as date FROM Posts as P") - -if posts == none: - posts = [] - -let style = - - -let main = -
- - \if len(posts) == 0: -
There are no posts yet!
- - \for post in posts: { - - let link - if post.is_link != 0: - link = post.content - else - link = ["/post?id=", post.id] - -
- -
- \post.date | \post.num_comments comments -
-
- } -
- +include "pages/page.wl" + +let posts = $query("SELECT P.id, P.title, P.is_link, P.content, (SELECT COUNT(*) FROM Comments as C WHERE c.parent_post=P.id) as num_comments, CURRENT_TIMESTAMP as date FROM Posts as P") + +if posts == none: + posts = [] + +let style = + + +let main = +
+ + \if len(posts) == 0: +
There are no posts yet!
+ + \for post in posts: { + + let link + if post.is_link != 0: + link = post.content + else + link = ["/post?id=", post.id] + +
+ +
+ \post.date | \post.num_comments comments +
+
+ } +
+ page("Index", $login_user_id, style, main) \ No newline at end of file diff --git a/pages/login.wl b/pages/login.wl index 6a5913b..22cc335 100644 --- a/pages/login.wl +++ b/pages/login.wl @@ -1,22 +1,22 @@ -include "pages/page.wl" -include "pages/login_and_signup_style.wl" - -let main = -
- Welcome back! - -
- -
- - - - -
-
- -page("Log-In", none, style, main) +include "pages/page.wl" +include "pages/login_and_signup_style.wl" + +let main = +
+ Welcome back! + +
+ +
+ + + + +
+
+ +page("Log-In", none, style, main) diff --git a/pages/login_and_signup_style.wl b/pages/login_and_signup_style.wl index 5a20fd6..f1dbd44 100644 --- a/pages/login_and_signup_style.wl +++ b/pages/login_and_signup_style.wl @@ -1,74 +1,74 @@ -let style = - \ No newline at end of file diff --git a/pages/notfound.wl b/pages/notfound.wl index 70d6ea4..bcc1fe7 100644 --- a/pages/notfound.wl +++ b/pages/notfound.wl @@ -1,60 +1,60 @@ -include "pages/page.wl" - -let style = - - -let main = -
-
-
404
-
Page Not Found
-
- Looks like this page wandered off somewhere. -
-
-
- -page("Not Found", $login_user_id, style, main) +include "pages/page.wl" + +let style = + + +let main = +
+
+
404
+
Page Not Found
+
+ Looks like this page wandered off somewhere. +
+
+
+ +page("Not Found", $login_user_id, style, main) diff --git a/pages/page.wl b/pages/page.wl index 38599dd..ce90bf9 100644 --- a/pages/page.wl +++ b/pages/page.wl @@ -1,75 +1,75 @@ - -procedure page(title, login_user_id, style, main) - - - - CN - \title - - \(style) - - - - - \main -
- Made with love by cozis -
- - + +procedure page(title, login_user_id, style, main) + + + + CN - \title + + \(style) + + + + + \main +
+ Made with love by cozis +
+ + diff --git a/pages/post.wl b/pages/post.wl index fa4f091..c55fcdc 100644 --- a/pages/post.wl +++ b/pages/post.wl @@ -1,246 +1,246 @@ -include "pages/page.wl" - -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 = - - -let main = -
-
-
- \post.title -
-
- submitted 3 hours ago by \post.username | \len comments -
-
-
\post.content
-
-
- - reply - -
-
- - - -
-
-
-
- - \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) -
- +include "pages/page.wl" + +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 = + + +let main = +
+
+
+ \post.title +
+
+ submitted 3 hours ago by \post.username | \len comments +
+
+
\post.content
+
+
+ + reply + +
+
+ + + +
+
+
+
+ + \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 index 6984736..6c61e35 100644 --- a/pages/post_old.wl +++ b/pages/post_old.wl @@ -1,73 +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) -
- +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/signup.wl b/pages/signup.wl index d8e6569..28300b7 100644 --- a/pages/signup.wl +++ b/pages/signup.wl @@ -1,24 +1,24 @@ -include "pages/page.wl" -include "pages/login_and_signup_style.wl" - -let main = -
- Welcome! - -
- -
- - - - - - -
-
- -page("Log-In", none, style, main) +include "pages/page.wl" +include "pages/login_and_signup_style.wl" + +let main = +
+ Welcome! + +
+ +
+ + + + + + +
+
+ +page("Log-In", none, style, main) diff --git a/pages/write.wl b/pages/write.wl index c3ad323..a5e97aa 100644 --- a/pages/write.wl +++ b/pages/write.wl @@ -1,93 +1,95 @@ -include "pages/page.wl" - -let style = - - -let main = -
-
- - -
- - -
- - - - - - -
- - -
- +include "pages/page.wl" + +let style = + + +let main = +
+
+ + + + +
+ + +
+ + + + + + +
+ + +
+ page("Write Post", $login_user_id, style, main) \ No newline at end of file diff --git a/src/bcrypt.c b/src/bcrypt.c index 7cec8fb..ab422a8 100644 --- a/src/bcrypt.c +++ b/src/bcrypt.c @@ -1,45 +1,45 @@ -#include -#include -#include -#include "bcrypt.h" -#include "random.h" - -int hash_password(char *pass, int passlen, int cost, PasswordHash *hash) -{ - char passzt[128]; - if (passlen >= (int) sizeof(passzt)) - return -1; - memcpy(passzt, pass, passlen); - passzt[passlen] = '\0'; - - char random[16]; - int ret = generate_random_bytes(random, (int) sizeof(random)); - if (ret) return -1; - - char salt[30]; - if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL) - return -1; - - if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL) - return -1; - - return 0; -} - -int check_password(char *pass, int passlen, PasswordHash hash) -{ - char passzt[128]; - if (passlen >= (int) sizeof(passzt)) - return -1; - memcpy(passzt, pass, passlen); - passzt[passlen] = '\0'; - - PasswordHash new_hash; - if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL) - return -1; - - if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time - return 1; - - return 0; -} +#include +#include +#include +#include "bcrypt.h" +#include "random.h" + +int hash_password(char *pass, int passlen, int cost, PasswordHash *hash) +{ + char passzt[128]; + if (passlen >= (int) sizeof(passzt)) + return -1; + memcpy(passzt, pass, passlen); + passzt[passlen] = '\0'; + + char random[16]; + int ret = generate_random_bytes(random, (int) sizeof(random)); + if (ret) return -1; + + char salt[30]; + if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL) + return -1; + + if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL) + return -1; + + return 0; +} + +int check_password(char *pass, int passlen, PasswordHash hash) +{ + char passzt[128]; + if (passlen >= (int) sizeof(passzt)) + return -1; + memcpy(passzt, pass, passlen); + passzt[passlen] = '\0'; + + PasswordHash new_hash; + if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL) + return -1; + + if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time + return 1; + + return 0; +} diff --git a/src/bcrypt.h b/src/bcrypt.h index a9505e1..e375cda 100644 --- a/src/bcrypt.h +++ b/src/bcrypt.h @@ -1,11 +1,11 @@ -#ifndef BCRYPT_INCLUDED -#define BCRYPT_INCLUDED - -typedef struct { - char data[61]; -} PasswordHash; - -int hash_password(char *pass, int passlen, int cost, PasswordHash *hash); -int check_password(char *pass, int passlen, PasswordHash hash); - -#endif // BCRYPT_INCLUDED +#ifndef BCRYPT_INCLUDED +#define BCRYPT_INCLUDED + +typedef struct { + char data[61]; +} PasswordHash; + +int hash_password(char *pass, int passlen, int cost, PasswordHash *hash); +int check_password(char *pass, int passlen, PasswordHash hash); + +#endif // BCRYPT_INCLUDED diff --git a/src/main.c b/src/main.c index 0a85312..03d4bf0 100644 --- a/src/main.c +++ b/src/main.c @@ -1,1095 +1,987 @@ -#include -#include -#include -#include -#include -#include -#include "chttp.h" -#include "bcrypt.h" -#include "sqlite3.h" -#include "sqlite3utils.h" -#include "template.h" - -#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1}) - -#define HTML_STR(X) html_str(HTTP_STR(#X)) - -static HTTP_String html_str(HTTP_String str) -{ - str = http_trim(str); - if (str.len > 0 && str.ptr[0] == '(') { - str.ptr++; - str.len--; - } - if (str.len > 0 && str.ptr[str.len-1] == ')') - str.len--; - return str; -} - -int load_file(char *file, char **data, long *size) -{ - FILE *f = fopen(file, "rb"); - if (f == NULL) return -1; - - fseek(f, 0, SEEK_END); - *size = ftell(f); - fseek(f, 0, SEEK_SET); - - *data = malloc(*size + 1); - - fread(*data, 1, *size, f); - (*data)[*size] = '\0'; - - fclose(f); - return 0; -} - -int create_user(SQLiteCache *dbcache, HTTP_String name, HTTP_String email, HTTP_String pass) -{ - PasswordHash hash; - int ret = hash_password(pass.ptr, pass.len, 12, &hash); - if (ret) return -500; - - sqlite3_stmt *stmt; - ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, - "INSERT INTO Users(username, email, hash) VALUES (?, ?, ?)", name, email, ((HTTP_String) { hash.data, strlen(hash.data) })); - if (ret != SQLITE_OK) - return -500; - - ret = sqlite3_step(stmt); - if (ret != SQLITE_DONE) { - fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache))); - // TODO: What if the user exists? - sqlite3_reset(stmt); - return -500; - } - - int64_t tmp = sqlite3_last_insert_rowid(sqlite_cache_getdb(dbcache)); - if (tmp < 0 || tmp > INT_MAX) { - sqlite3_reset(stmt); - return -500; - } - int user_id = (int) tmp; - - ret = sqlite3_reset(stmt); - if (ret != SQLITE_OK) { - fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache))); - sqlite3_reset(stmt); - return -500; - } - - return user_id; -} - -int user_exists(SQLiteCache *dbcache, HTTP_String name, HTTP_String pass) -{ - sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, - "SELECT id, hash FROM Users WHERE username=?", name); - if (ret != SQLITE_OK) - return -500; - - ret = sqlite3_step(stmt); - if (ret == SQLITE_DONE) { - sqlite3_reset(stmt); - return -404; - } - if (ret != SQLITE_ROW) { - sqlite3_reset(stmt); - return -500; - } - - int user_id = sqlite3_column_int(stmt, 0); - if (user_id < 0) { - sqlite3_reset(stmt); - return -500; - } - - const char *rawhash = (const char*) sqlite3_column_text(stmt, 1); - if (rawhash == NULL) { - sqlite3_reset(stmt); - return -500; - } - int rawhashlen = sqlite3_column_bytes(stmt, 1); - - PasswordHash hash; - if (rawhashlen >= (int) sizeof(hash.data)) { - sqlite3_reset(stmt); - return -500; - } - strcpy(hash.data, rawhash); - - ret = check_password(pass.ptr, pass.len, hash); - if (ret < 0) { - sqlite3_reset(stmt); - return -500; - } - if (ret > 0) { - sqlite3_reset(stmt); - return -400; - } - - ret = sqlite3_reset(stmt); - if (ret != SQLITE_OK) - return -500; - - return user_id; -} - -HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name) -{ - // TODO: best-effort implementation - - for (int i = 0; i < req->num_headers; i++) { - - if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie"))) - continue; - - char *src = req->headers[i].value.ptr; - int len = req->headers[i].value.len; - int cur = 0; - - // Cookie: name1=value1; name2=value2; name3=value3 - - for (;;) { - - while (cur < len && src[cur] == ' ') - cur++; - - int off = cur; - while (cur < len && src[cur] != '=') - cur++; - - HTTP_String cookie_name = { src + off, cur - off }; - - if (cur == len) - break; - cur++; - - off = cur; - while (cur < len && src[cur] != ';') - cur++; - - HTTP_String cookie_value = { src + off, cur - off }; - - if (http_streq(name, cookie_name)) - return cookie_value; - - if (cur == len) - break; - cur++; - } - } - - return HTTP_STR(""); -} - -static void *alloc(WL_Arena *arena, int num, int align) -{ - int pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1); - if (arena->len - arena->cur < num + pad) - return NULL; - - void *ptr = arena->ptr + arena->cur + pad; - arena->cur += num + pad; - - return ptr; -} - -static bool is_hex_digit(char c) -{ - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); -} - -static int hex_digit_to_int(char c) -{ - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - return c - '0'; -} - -HTTP_String http_getparam(HTTP_String body, HTTP_String str, WL_Arena *arena) -{ - // This is just a best-effort implementation - - char *src = body.ptr; - int len = body.len; - int cur = 0; - - if (cur < len && src[cur] == '?') - cur++; - - while (cur < len) { - - HTTP_String name; - { - int off = cur; - while (cur < len && src[cur] != '=' && src[cur] != '&') - cur++; - name = (HTTP_String) { src + off, cur - off }; - } - - HTTP_String body = HTTP_STR(""); - if (cur < len) { - cur++; - if (src[cur-1] == '=') { - int off = cur; - while (cur < len && src[cur] != '&') - cur++; - body = (HTTP_String) { src + off, cur - off }; - - if (cur < len) - cur++; - } - } - - if (http_streq(str, name)) { - - bool percent_encoded = false; - for (int i = 0; i < body.len; i++) - if (body.ptr[i] == '+' || body.ptr[i] == '%') { - percent_encoded = true; - break; - } - - if (!percent_encoded) - return body; - - HTTP_String decoded = { alloc(arena, body.len, 1), 0 }; - if (decoded.ptr == NULL) - return (HTTP_String) { NULL, 0 }; - - for (int i = 0; i < body.len; i++) { - - char c = body.ptr[i]; - if (c == '+') - c = ' '; - else { - if (body.ptr[i] == '%') { - if (body.len - i < 3 - || !is_hex_digit(body.ptr[i+1]) - || !is_hex_digit(body.ptr[i+2])) - return (HTTP_String) { NULL, 0 }; - - int h = hex_digit_to_int(body.ptr[i+1]); - int l = hex_digit_to_int(body.ptr[i+2]); - c = (h << 4) | l; - - i += 2; - } - } - - decoded.ptr[decoded.len++] = c; - } - - return decoded; - } - } - - return HTTP_STR(""); -} - -static bool is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -int http_getparami(HTTP_String body, HTTP_String str, WL_Arena *arena) -{ - HTTP_String out = http_getparam(body, str, arena); - if (out.len == 0 || !is_digit(out.ptr[0])) - return -1; - int cur = 0; - int buf = 0; - do { - int d = out.ptr[cur++] - '0'; - if (buf > (INT_MAX - d) / 10) - return -1; - buf = buf * 10 + d; - } while (cur < out.len && is_digit(out.ptr[cur])); - - return buf; -} - -#define SESSION_LIMIT 1024 -#define USERNAME_LIMIT 64 - -#define RAW_SESSION_TOKEN_SIZE 32 -#define RAW_CSRF_TOKEN_SIZE 32 - -#define SESSION_TOKEN_SIZE (2 * RAW_SESSION_TOKEN_SIZE) -#define CSRF_TOKEN_SIZE (2 * RAW_CSRF_TOKEN_SIZE) - -typedef struct { - int user_id; - char raw_sess_token[RAW_SESSION_TOKEN_SIZE]; - char raw_csrf_token[RAW_CSRF_TOKEN_SIZE]; -} Session; - -typedef struct { - Session sessions[SESSION_LIMIT]; - int count; -} SessionSet; - -#include "random.h" - -void session_set_init(SessionSet *set) -{ - set->count = 0; -} - -typedef struct { - char data[SESSION_TOKEN_SIZE]; -} SessionToken; - -typedef struct { - char data[CSRF_TOKEN_SIZE]; -} CSRFToken; - -static void unpack_token(char *src, int srclen, char *dst, int dstlen) -{ - assert(srclen == 2 * dstlen); - - for (int i = 0; i < srclen; i++) { - static const char table[] = "0123456789abcdef"; - int low = (src[i] & 0x0F) >> 0; - int high = (src[i] & 0xF0) >> 4; - dst[(i << 1) | 0] = table[high]; - dst[(i << 1) | 1] = table[low]; - } -} - -static int pack_token(char *src, int srclen, char *dst, int dstlen) -{ - if (srclen & 1) - return -1; - - assert(2 * srclen == dstlen); - - for (int i = 0; i < srclen; i += 2) { - int high = src[i+0]; - int low = src[i+1]; - if (!is_hex_digit(high) || !is_hex_digit(low)) - return -1; - dst[i] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0); - } - - return 0; -} - -int session_set_add(SessionSet *set, int user_id, SessionToken *sess_token, CSRFToken *csrf_token) -{ - if (set->count == SESSION_LIMIT) - return -1; - - Session *session = &set->sessions[set->count]; - - int ret; - ret = generate_random_bytes(session->raw_sess_token, (int) sizeof(session->raw_sess_token)); - if (ret) return -1; - - ret = generate_random_bytes(session->raw_csrf_token, (int) sizeof(session->raw_csrf_token)); - if (ret) return -1; - - session->user_id = user_id; - - unpack_token( - session->raw_csrf_token, (int) sizeof(session->raw_csrf_token), - csrf_token->data, (int) sizeof(csrf_token->data) - ); - - unpack_token( - session->raw_sess_token, (int) sizeof(session->raw_sess_token), - sess_token->data, (int) sizeof(sess_token->data) - ); - - set->count++; - return 0; -} - -void session_set_remove(SessionSet *set, SessionToken sess_token) -{ - char raw_sess_token[RAW_SESSION_TOKEN_SIZE]; - pack_token( - sess_token.data, (int) sizeof(sess_token.data), - raw_sess_token, (int) sizeof(raw_sess_token) - ); - - int i = 0; - while (i < set->count && memcmp(set->sessions[i].raw_sess_token, raw_sess_token, RAW_SESSION_TOKEN_SIZE)) - i++; - if (i == set->count) - return; - set->sessions[i] = set->sessions[--set->count]; -} - -int session_set_find(SessionSet *set, SessionToken sess_token) -{ -char raw_sess_token[RAW_SESSION_TOKEN_SIZE]; - pack_token( - sess_token.data, (int) sizeof(sess_token.data), - raw_sess_token, (int) sizeof(raw_sess_token) - ); - - int i = 0; - while (i < set->count && memcmp(set->sessions[i].raw_sess_token, raw_sess_token, RAW_SESSION_TOKEN_SIZE)) - i++; - if (i == set->count) - return -1; - return set->sessions[i].user_id; -} - -bool valid_name(HTTP_String str) -{ - (void) str; // TODO - return true; -} - -bool valid_email(HTTP_String str) -{ - (void) str; // TODO - return true; -} - -bool valid_pass(HTTP_String str) -{ - (void) str; // TODO - return true; -} - -bool valid_post_title(HTTP_String str) -{ - (void) str; // TODO - return true; -} - -bool valid_post_content(HTTP_String str) -{ - (void) str; // TODO - return true; -} - -bool valid_link(HTTP_String str) -{ - (void) str; - return true; -} - -bool valid_comment_content(HTTP_String str) -{ - (void) str; - return true; -} - -int main(void) -{ - http_global_init(); - - printf("%s\n", sqlite3_libversion()); - - sqlite3 *db; - int ret = sqlite3_open(":memory:", &db); - if (ret != SQLITE_OK) { - fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); - sqlite3_close(db); - return -1; - } - - char *schema_data; - long schema_size; - ret = load_file("misc/schema.sql", &schema_data, &schema_size); - if (ret < 0) { - printf("Couldn't load schema\n"); - return -1; - } - - ret = sqlite3_exec(db, schema_data, NULL, NULL, NULL); - if (ret != SQLITE_OK) { - fprintf(stderr, "Cannot run schema: %s\n", sqlite3_errmsg(db)); - sqlite3_close(db); - return -1; - } - - free(schema_data); - - TemplateCache *tpcache = template_cache_init(4); - SQLiteCache *dbcache = sqlite_cache_init(db, 5); - - - HTTP_String addr = HTTP_STR("127.0.0.1"); - uint16_t port = 8080; - - HTTP_Server *server = http_server_init(addr, port); - if (server == NULL) return -1; - - http_server_set_trace(server, false); - - int pool_cap = 1<<20; - char *pool = malloc(pool_cap); - - SessionSet sessions; - session_set_init(&sessions); - - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - - int ret = http_server_wait(server, &req, &builder); - if (ret < 0) return -1; - - WL_Arena arena = { pool, pool_cap, 0 }; - - //printf("%.*s\n", req->raw.len, req->raw.ptr); // TODO - - // If logged in, these are set to non-negative values - SessionToken sess_token; - int user_id = -1; - - { - HTTP_String str = http_getcookie(req, HTTP_STR("sess_id")); - if (str.len == SESSION_TOKEN_SIZE) { - char tmp[RAW_SESSION_TOKEN_SIZE]; - int ret = pack_token(str.ptr, str.len, tmp, (int) sizeof(tmp)); - if (ret) { - memcpy(sess_token.data, str.ptr, str.len); - user_id = session_set_find(&sessions, sess_token); - } - } - } - - HTTP_String path = req->url.path; - if (http_streq(path, HTTP_STR("/api/login"))) { - - if (req->method != HTTP_METHOD_POST) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid request method -
- ))); - http_response_builder_done(builder); - continue; - } - - if (user_id != -1) { - // Already logged in - http_response_builder_status(builder, 200); - http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); - http_response_builder_body(builder, HTML_STR(( -
- You are already logged in -
- ))); - http_response_builder_done(builder); - continue; - } - - HTTP_String name = http_getparam(req->body, HTTP_STR("username"), &arena); - HTTP_String pass = http_getparam(req->body, HTTP_STR("password"), &arena); - - name = http_trim(name); - pass = http_trim(pass); - - if (!valid_name(name) || !valid_pass(pass)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid credentials -
- ))); - http_response_builder_done(builder); - continue; - } - - int ret = user_exists(dbcache, name, pass); - if (ret < 0) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid credentials -
- ))); - http_response_builder_done(builder); - continue; - } - int user_id = ret; - - CSRFToken csrf_token; - SessionToken sess_token; - if (session_set_add(&sessions, user_id, &sess_token, &csrf_token) < 0) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - char cookie[1<<9]; - int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%.*s; Path=/; HttpOnly", (int) sizeof(sess_token.data)-1, sess_token.data); - if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - http_response_builder_status(builder, 200); // TODO: Whats the correct code here? - http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len }); - http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); - http_response_builder_body(builder, HTML_STR(( -
- Welcome back! -
- ))); - http_response_builder_done(builder); - - } else if (http_streq(path, HTTP_STR("/api/signup"))) { - - if (req->method != HTTP_METHOD_POST) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid request method -
- ))); - http_response_builder_done(builder); - continue; - } - - if (user_id != -1) { - // Already logged in - http_response_builder_status(builder, 200); - http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); - http_response_builder_body(builder, HTML_STR(( -
- You are already logged in -
- ))); - http_response_builder_done(builder); - continue; - } - - HTTP_String name = http_getparam(req->body, HTTP_STR("username"), &arena); - HTTP_String email = http_getparam(req->body, HTTP_STR("email"), &arena); - HTTP_String pass1 = http_getparam(req->body, HTTP_STR("password1"), &arena); - HTTP_String pass2 = http_getparam(req->body, HTTP_STR("password2"), &arena); - - if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid credentials -
- ))); - http_response_builder_done(builder); - continue; - } - - if (!http_streq(pass1, pass2)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- The password was repeated incorrectly -
- ))); - http_response_builder_done(builder); - continue; - } - - int ret = create_user(dbcache, name, email, pass1); - if (ret < 0) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - user_id = ret; - - CSRFToken csrf_token; - SessionToken sess_token; - if (session_set_add(&sessions, user_id, &sess_token, &csrf_token) < 0) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - char cookie[1<<9]; - int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%.*s; Path=/; HttpOnly", (int) sizeof(sess_token.data)-1, sess_token.data); - if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - http_response_builder_status(builder, 200); // TODO: Whats the correct code here? - http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len }); - http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); - http_response_builder_body(builder, HTML_STR(( -
- Welcome! -
- ))); - http_response_builder_done(builder); - - } else if (http_streq(path, HTTP_STR("/api/logout"))) { - - if (user_id != -1) - session_set_remove(&sessions, sess_token); - - http_response_builder_status(builder, 303); // TODO: Whats the correct code here? - http_response_builder_header(builder, HTTP_STR("Set-Cookie: sess_id=; Path=/; HttpOnly")); - http_response_builder_header(builder, HTTP_STR("Location: /index")); - http_response_builder_done(builder); - - } else if (http_streq(path, HTTP_STR("/api/post"))) { - - if (req->method != HTTP_METHOD_POST) { - http_response_builder_status(builder, 405); - http_response_builder_done(builder); - continue; - } - - if (user_id == -1) { - 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"), &arena); - HTTP_String link = http_getparam(req->body, HTTP_STR("link"), &arena); - HTTP_String content = http_getparam(req->body, HTTP_STR("content"), &arena); - - title = http_trim(title); - link = http_trim(link); - content = http_trim(content); - - if (!valid_post_title(title)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid title -
- ))); - http_response_builder_done(builder); - continue; - } - - if (!valid_link(link)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid link -
- ))); - http_response_builder_done(builder); - continue; - } - - if (!valid_post_content(content)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid content -
- ))); - http_response_builder_done(builder); - continue; - } - - bool is_link = false; - if (link.len > 0) { - is_link = true; - content = link; - } - - sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content); - if (ret != SQLITE_OK) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - ret = sqlite3_step(stmt); - if (ret != SQLITE_DONE) { - sqlite3_reset(stmt); - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - int64_t tmp = sqlite3_last_insert_rowid(db); - if (tmp < 0 || tmp > INT_MAX) { - sqlite3_reset(stmt); - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - int post_id = (int) tmp; - - ret = sqlite3_reset(stmt); - if (ret != SQLITE_OK) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - char location[1<<9]; - ret = snprintf(location, sizeof(location), "Location: /post?id=%d", post_id); - if (ret < 0 || ret >= (int) sizeof(location)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - 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("/api/comment"))) { - - if (req->method != HTTP_METHOD_POST) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid request method -
- ))); - http_response_builder_done(builder); - continue; - } - - if (user_id == -1) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- You are not logged in -
- ))); - http_response_builder_done(builder); - continue; - } - - int parent_post = http_getparami(req->body, HTTP_STR("parent_post"), &arena); - int parent_comment = http_getparami(req->body, HTTP_STR("parent_comment"), &arena); - HTTP_String content = http_getparam (req->body, HTTP_STR("content"), &arena); - - content = http_trim(content); - if (!valid_comment_content(content)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Invalid content -
- ))); - http_response_builder_done(builder); - continue; - } - - int ret; - sqlite3_stmt *stmt; - if (parent_comment == -1) - ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post); - else - ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment); - if (ret != SQLITE_OK) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - ret = sqlite3_step(stmt); - if (ret != SQLITE_DONE) { - sqlite3_reset(stmt); - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - ret = sqlite3_reset(stmt); - if (ret != SQLITE_OK) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - char location[1<<9]; - ret = snprintf(location, sizeof(location), "Location: /post?id=%d", parent_post); - if (ret < 0 || ret >= (int) sizeof(location)) { - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTML_STR(( -
- Internal error -
- ))); - http_response_builder_done(builder); - continue; - } - - 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"))) { - - template_eval(builder, 200, WL_STR("pages/index.wl"), tpcache, &arena, dbcache, user_id, -1); - - } else if (http_streq(path, HTTP_STR("/write"))) { - - if (user_id == -1) { - // Not logged in - http_response_builder_status(builder, 303); - http_response_builder_header(builder, HTTP_STR("Location: /index")); - http_response_builder_done(builder); - continue; - } - - template_eval(builder, 200, WL_STR("pages/write.wl"), tpcache, &arena, dbcache, user_id, -1); - - } else if (http_streq(path, HTTP_STR("/login"))) { - - if (user_id != -1) { - // Already logged in - http_response_builder_status(builder, 303); - http_response_builder_header(builder, HTTP_STR("Location: /index")); - http_response_builder_done(builder); - continue; - } - - template_eval(builder, 200, WL_STR("pages/login.wl"), tpcache, &arena, dbcache, user_id, -1); - - } else if (http_streq(path, HTTP_STR("/signup"))) { - - if (user_id != -1) { - // Already logged in - http_response_builder_status(builder, 303); - http_response_builder_header(builder, HTTP_STR("Location: /index")); - http_response_builder_done(builder); - continue; - } - - template_eval(builder, 200, WL_STR("pages/signup.wl"), tpcache, &arena, dbcache, user_id, -1); - - } else if (http_streq(path, HTTP_STR("/post"))) { - - int post_id = http_getparami(req->url.query, HTTP_STR("id"), &arena); - if (post_id < 0) { - template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1); - continue; - } - - sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id); - if (ret != SQLITE_OK) { - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - continue; - } - - ret = sqlite3_step(stmt); - if (ret != SQLITE_ROW) { - sqlite3_reset(stmt); - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - continue; - } - int64_t num = sqlite3_column_int64(stmt, 0); - - ret = sqlite3_reset(stmt); - if (ret != SQLITE_OK) { - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - continue; - } - - if (num < 0) { - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - continue; - } - - if (num == 0) { - template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1); - continue; - } - - template_eval(builder, 200, WL_STR("pages/post.wl"), tpcache, &arena, dbcache, user_id, post_id); - - } else { - - template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1); - } - } - - sqlite_cache_free(dbcache); - template_cache_free(tpcache); - sqlite3_close(db); - http_server_free(server); - http_global_free(); - return 0; -} +#include +#include +#include +#include +#include +#include +#include "chttp.h" +#include "bcrypt.h" +#include "sqlite3.h" +#include "sqlite3utils.h" +#include "template.h" +#include "session.h" + +#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1}) + +#define HTML_STR(X) html_str(HTTP_STR(#X)) + +static HTTP_String html_str(HTTP_String str) +{ + str = http_trim(str); + if (str.len > 0 && str.ptr[0] == '(') { + str.ptr++; + str.len--; + } + if (str.len > 0 && str.ptr[str.len-1] == ')') + str.len--; + return str; +} + +int load_file(char *file, char **data, long *size) +{ + FILE *f = fopen(file, "rb"); + if (f == NULL) return -1; + + fseek(f, 0, SEEK_END); + *size = ftell(f); + fseek(f, 0, SEEK_SET); + + *data = malloc(*size + 1); + + fread(*data, 1, *size, f); + (*data)[*size] = '\0'; + + fclose(f); + return 0; +} + +int create_user(SQLiteCache *dbcache, HTTP_String name, HTTP_String email, HTTP_String pass) +{ + PasswordHash hash; + int ret = hash_password(pass.ptr, pass.len, 12, &hash); + if (ret) return -500; + + sqlite3_stmt *stmt; + ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, + "INSERT INTO Users(username, email, hash) VALUES (?, ?, ?)", name, email, ((HTTP_String) { hash.data, strlen(hash.data) })); + if (ret != SQLITE_OK) + return -500; + + ret = sqlite3_step(stmt); + if (ret != SQLITE_DONE) { + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache))); + // TODO: What if the user exists? + sqlite3_reset(stmt); + return -500; + } + + int64_t tmp = sqlite3_last_insert_rowid(sqlite_cache_getdb(dbcache)); + if (tmp < 0 || tmp > INT_MAX) { + sqlite3_reset(stmt); + return -500; + } + int user_id = (int) tmp; + + ret = sqlite3_reset(stmt); + if (ret != SQLITE_OK) { + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache))); + sqlite3_reset(stmt); + return -500; + } + + return user_id; +} + +int user_exists(SQLiteCache *dbcache, HTTP_String name, HTTP_String pass) +{ + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, + "SELECT id, hash FROM Users WHERE username=?", name); + if (ret != SQLITE_OK) + return -500; + + ret = sqlite3_step(stmt); + if (ret == SQLITE_DONE) { + sqlite3_reset(stmt); + return -404; + } + if (ret != SQLITE_ROW) { + sqlite3_reset(stmt); + return -500; + } + + int user_id = sqlite3_column_int(stmt, 0); + if (user_id < 0) { + sqlite3_reset(stmt); + return -500; + } + + const char *rawhash = (const char*) sqlite3_column_text(stmt, 1); + if (rawhash == NULL) { + sqlite3_reset(stmt); + return -500; + } + int rawhashlen = sqlite3_column_bytes(stmt, 1); + + PasswordHash hash; + if (rawhashlen >= (int) sizeof(hash.data)) { + sqlite3_reset(stmt); + return -500; + } + strcpy(hash.data, rawhash); + + ret = check_password(pass.ptr, pass.len, hash); + if (ret < 0) { + sqlite3_reset(stmt); + return -500; + } + if (ret > 0) { + sqlite3_reset(stmt); + return -400; + } + + ret = sqlite3_reset(stmt); + if (ret != SQLITE_OK) + return -500; + + return user_id; +} + +HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name) +{ + // TODO: best-effort implementation + + for (int i = 0; i < req->num_headers; i++) { + + if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie"))) + continue; + + char *src = req->headers[i].value.ptr; + int len = req->headers[i].value.len; + int cur = 0; + + // Cookie: name1=value1; name2=value2; name3=value3 + + for (;;) { + + while (cur < len && src[cur] == ' ') + cur++; + + int off = cur; + while (cur < len && src[cur] != '=') + cur++; + + HTTP_String cookie_name = { src + off, cur - off }; + + if (cur == len) + break; + cur++; + + off = cur; + while (cur < len && src[cur] != ';') + cur++; + + HTTP_String cookie_value = { src + off, cur - off }; + + if (http_streq(name, cookie_name)) + return cookie_value; + + if (cur == len) + break; + cur++; + } + } + + return HTTP_STR(""); +} + +static void *alloc(WL_Arena *arena, int num, int align) +{ + int pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1); + if (arena->len - arena->cur < num + pad) + return NULL; + + void *ptr = arena->ptr + arena->cur + pad; + arena->cur += num + pad; + + return ptr; +} + +static bool is_hex_digit(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +static int hex_digit_to_int(char c) +{ + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return c - '0'; +} + +HTTP_String http_getparam(HTTP_String body, HTTP_String str, WL_Arena *arena) +{ + // This is just a best-effort implementation + + char *src = body.ptr; + int len = body.len; + int cur = 0; + + if (cur < len && src[cur] == '?') + cur++; + + while (cur < len) { + + HTTP_String name; + { + int off = cur; + while (cur < len && src[cur] != '=' && src[cur] != '&') + cur++; + name = (HTTP_String) { src + off, cur - off }; + } + + HTTP_String body = HTTP_STR(""); + if (cur < len) { + cur++; + if (src[cur-1] == '=') { + int off = cur; + while (cur < len && src[cur] != '&') + cur++; + body = (HTTP_String) { src + off, cur - off }; + + if (cur < len) + cur++; + } + } + + if (http_streq(str, name)) { + + bool percent_encoded = false; + for (int i = 0; i < body.len; i++) + if (body.ptr[i] == '+' || body.ptr[i] == '%') { + percent_encoded = true; + break; + } + + if (!percent_encoded) + return body; + + HTTP_String decoded = { alloc(arena, body.len, 1), 0 }; + if (decoded.ptr == NULL) + return (HTTP_String) { NULL, 0 }; + + for (int i = 0; i < body.len; i++) { + + char c = body.ptr[i]; + if (c == '+') + c = ' '; + else { + if (body.ptr[i] == '%') { + if (body.len - i < 3 + || !is_hex_digit(body.ptr[i+1]) + || !is_hex_digit(body.ptr[i+2])) + return (HTTP_String) { NULL, 0 }; + + int h = hex_digit_to_int(body.ptr[i+1]); + int l = hex_digit_to_int(body.ptr[i+2]); + c = (h << 4) | l; + + i += 2; + } + } + + decoded.ptr[decoded.len++] = c; + } + + return decoded; + } + } + + return HTTP_STR(""); +} + +static bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +int http_getparami(HTTP_String body, HTTP_String str, WL_Arena *arena) +{ + HTTP_String out = http_getparam(body, str, arena); + if (out.len == 0 || !is_digit(out.ptr[0])) + return -1; + int cur = 0; + int buf = 0; + do { + int d = out.ptr[cur++] - '0'; + if (buf > (INT_MAX - d) / 10) + return -1; + buf = buf * 10 + d; + } while (cur < out.len && is_digit(out.ptr[cur])); + + return buf; +} + +#define USERNAME_LIMIT 64 + +bool valid_name(HTTP_String str) +{ + (void) str; // TODO + return true; +} + +bool valid_email(HTTP_String str) +{ + (void) str; // TODO + return true; +} + +bool valid_pass(HTTP_String str) +{ + (void) str; // TODO + return true; +} + +bool valid_post_title(HTTP_String str) +{ + (void) str; // TODO + return true; +} + +bool valid_post_content(HTTP_String str) +{ + (void) str; // TODO + return true; +} + +bool valid_link(HTTP_String str) +{ + (void) str; + return true; +} + +bool valid_comment_content(HTTP_String str) +{ + (void) str; + return true; +} + +int main(void) +{ + http_global_init(); + + printf("%s\n", sqlite3_libversion()); + + sqlite3 *db; + int ret = sqlite3_open(":memory:", &db); + if (ret != SQLITE_OK) { + fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + return -1; + } + + char *schema_data; + long schema_size; + ret = load_file("misc/schema.sql", &schema_data, &schema_size); + if (ret < 0) { + printf("Couldn't load schema\n"); + return -1; + } + + ret = sqlite3_exec(db, schema_data, NULL, NULL, NULL); + if (ret != SQLITE_OK) { + fprintf(stderr, "Cannot run schema: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + return -1; + } + + free(schema_data); + + TemplateCache *tpcache = template_cache_init(4); + SQLiteCache *dbcache = sqlite_cache_init(db, 5); + + + HTTP_String addr = HTTP_STR("127.0.0.1"); + uint16_t port = 8080; + + HTTP_Server *server = http_server_init(addr, port); + if (server == NULL) return -1; + + http_server_set_trace(server, false); + + int pool_cap = 1<<20; + char *pool = malloc(pool_cap); + + SessionStorage *session_storage = session_storage_init(1024); + if (session_storage == NULL) { + return -1; + } + + for (;;) { + + HTTP_Request *req; + HTTP_ResponseBuilder builder; + + int ret = http_server_wait(server, &req, &builder); + if (ret < 0) return -1; + + WL_Arena arena = { pool, pool_cap, 0 }; + + printf("%.*s\n", req->raw.len, req->raw.ptr); // TODO + + int user_id; + HTTP_String sess; + HTTP_String csrf; + + sess = http_getcookie(req, HTTP_STR("sess_token")); + if (find_session(session_storage, sess, &csrf, &user_id) < 0) { + user_id = -1; + sess = (HTTP_String) { NULL, 0 }; + csrf = (HTTP_String) { NULL, 0 }; + } + + HTTP_String path = req->url.path; + if (http_streq(path, HTTP_STR("/api/login"))) { + + if (req->method != HTTP_METHOD_POST) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid request method +
+ ))); + http_response_builder_done(builder); + continue; + } + + if (user_id != -1) { + // Already logged in + http_response_builder_status(builder, 200); + http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ You are already logged in +
+ ))); + http_response_builder_done(builder); + continue; + } + + HTTP_String name = http_getparam(req->body, HTTP_STR("username"), &arena); + HTTP_String pass = http_getparam(req->body, HTTP_STR("password"), &arena); + + name = http_trim(name); + pass = http_trim(pass); + + if (!valid_name(name) || !valid_pass(pass)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid credentials +
+ ))); + http_response_builder_done(builder); + continue; + } + + int ret = user_exists(dbcache, name, pass); + if (ret < 0) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid credentials +
+ ))); + http_response_builder_done(builder); + continue; + } + int user_id = ret; + + HTTP_String sess; + HTTP_String csrf; + if (create_session(session_storage, user_id, &sess, &csrf) < 0) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + // TODO: set cookie as secure + char cookie[1<<9]; + int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_token=%.*s; Path=/; HttpOnly", sess.len, sess.ptr); + if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + http_response_builder_status(builder, 200); // TODO: Whats the correct code here? + http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len }); + http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ Welcome back! +
+ ))); + http_response_builder_done(builder); + + } else if (http_streq(path, HTTP_STR("/api/signup"))) { + + if (req->method != HTTP_METHOD_POST) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid request method +
+ ))); + http_response_builder_done(builder); + continue; + } + + if (user_id != -1) { + // Already logged in + http_response_builder_status(builder, 200); + http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ You are already logged in +
+ ))); + http_response_builder_done(builder); + continue; + } + + HTTP_String name = http_getparam(req->body, HTTP_STR("username"), &arena); + HTTP_String email = http_getparam(req->body, HTTP_STR("email"), &arena); + HTTP_String pass1 = http_getparam(req->body, HTTP_STR("password1"), &arena); + HTTP_String pass2 = http_getparam(req->body, HTTP_STR("password2"), &arena); + + if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid credentials +
+ ))); + http_response_builder_done(builder); + continue; + } + + if (!http_streq(pass1, pass2)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ The password was repeated incorrectly +
+ ))); + http_response_builder_done(builder); + continue; + } + + int ret = create_user(dbcache, name, email, pass1); + if (ret < 0) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + user_id = ret; + + HTTP_String sess; + HTTP_String csrf; + if (create_session(session_storage, user_id, &sess, &csrf) < 0) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + // TODO: set cookie as secure + char cookie[1<<9]; + int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_token=%.*s; Path=/; HttpOnly", sess.len, sess.ptr); + if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + http_response_builder_status(builder, 200); // TODO: Whats the correct code here? + http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len }); + http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ Welcome! +
+ ))); + http_response_builder_done(builder); + + } else if (http_streq(path, HTTP_STR("/api/logout"))) { + + if (user_id != -1) + delete_session(session_storage, sess); + + // TODO: set cookie as secure + http_response_builder_status(builder, 303); // TODO: Whats the correct code here? + http_response_builder_header(builder, HTTP_STR("Set-Cookie: sess_token=; Path=/; HttpOnly")); + http_response_builder_header(builder, HTTP_STR("Location: /index")); + http_response_builder_done(builder); + + } else if (http_streq(path, HTTP_STR("/api/post"))) { + + if (req->method != HTTP_METHOD_POST) { + http_response_builder_status(builder, 405); + http_response_builder_done(builder); + continue; + } + + if (user_id == -1) { + 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"), &arena); + HTTP_String link = http_getparam(req->body, HTTP_STR("link"), &arena); + HTTP_String content = http_getparam(req->body, HTTP_STR("content"), &arena); + HTTP_String csrf2 = http_getparam(req->body, HTTP_STR("csrf"), &arena); + + title = http_trim(title); + link = http_trim(link); + content = http_trim(content); + + if (!http_streq(csrf, csrf2)) { + http_response_builder_status(builder, 400); + http_response_builder_body(builder, HTTP_STR("Invalid request")); + http_response_builder_done(builder); + continue; + } + + if (!valid_post_title(title)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid title +
+ ))); + http_response_builder_done(builder); + continue; + } + + if (!valid_link(link)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid link +
+ ))); + http_response_builder_done(builder); + continue; + } + + if (!valid_post_content(content)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid content +
+ ))); + http_response_builder_done(builder); + continue; + } + + bool is_link = false; + if (link.len > 0) { + is_link = true; + content = link; + } + + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content); + if (ret != SQLITE_OK) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + ret = sqlite3_step(stmt); + if (ret != SQLITE_DONE) { + sqlite3_reset(stmt); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + int64_t tmp = sqlite3_last_insert_rowid(db); + if (tmp < 0 || tmp > INT_MAX) { + sqlite3_reset(stmt); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + int post_id = (int) tmp; + + ret = sqlite3_reset(stmt); + if (ret != SQLITE_OK) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + char location[1<<9]; + ret = snprintf(location, sizeof(location), "Location: /post?id=%d", post_id); + if (ret < 0 || ret >= (int) sizeof(location)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + 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("/api/comment"))) { + + if (req->method != HTTP_METHOD_POST) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid request method +
+ ))); + http_response_builder_done(builder); + continue; + } + + if (user_id == -1) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ You are not logged in +
+ ))); + http_response_builder_done(builder); + continue; + } + + int parent_post = http_getparami(req->body, HTTP_STR("parent_post"), &arena); + int parent_comment = http_getparami(req->body, HTTP_STR("parent_comment"), &arena); + HTTP_String content = http_getparam (req->body, HTTP_STR("content"), &arena); + HTTP_String csrf2 = http_getparam(req->body, HTTP_STR("csrf"), &arena); + + if (!http_streq(csrf, csrf2)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTTP_STR("Invalid request")); + http_response_builder_done(builder); + continue; + } + + content = http_trim(content); + if (!valid_comment_content(content)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid content +
+ ))); + http_response_builder_done(builder); + continue; + } + + int ret; + sqlite3_stmt *stmt; + if (parent_comment == -1) + ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post); + else + ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment); + if (ret != SQLITE_OK) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + ret = sqlite3_step(stmt); + if (ret != SQLITE_DONE) { + sqlite3_reset(stmt); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + ret = sqlite3_reset(stmt); + if (ret != SQLITE_OK) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + char location[1<<9]; + ret = snprintf(location, sizeof(location), "Location: /post?id=%d", parent_post); + if (ret < 0 || ret >= (int) sizeof(location)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + + 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"))) { + + template_eval(builder, 200, WL_STR("pages/index.wl"), tpcache, &arena, dbcache, csrf, user_id, -1); + + } else if (http_streq(path, HTTP_STR("/write"))) { + + if (user_id == -1) { + // Not logged in + http_response_builder_status(builder, 303); + http_response_builder_header(builder, HTTP_STR("Location: /index")); + http_response_builder_done(builder); + continue; + } + + template_eval(builder, 200, WL_STR("pages/write.wl"), tpcache, &arena, dbcache, csrf, user_id, -1); + + } else if (http_streq(path, HTTP_STR("/login"))) { + + if (user_id != -1) { + // Already logged in + http_response_builder_status(builder, 303); + http_response_builder_header(builder, HTTP_STR("Location: /index")); + http_response_builder_done(builder); + continue; + } + + template_eval(builder, 200, WL_STR("pages/login.wl"), tpcache, &arena, dbcache, csrf, user_id, -1); + + } else if (http_streq(path, HTTP_STR("/signup"))) { + + if (user_id != -1) { + // Already logged in + http_response_builder_status(builder, 303); + http_response_builder_header(builder, HTTP_STR("Location: /index")); + http_response_builder_done(builder); + continue; + } + + template_eval(builder, 200, WL_STR("pages/signup.wl"), tpcache, &arena, dbcache, csrf, user_id, -1); + + } else if (http_streq(path, HTTP_STR("/post"))) { + + int post_id = http_getparami(req->url.query, HTTP_STR("id"), &arena); + if (post_id < 0) { + template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, csrf, user_id, -1); + continue; + } + + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id); + if (ret != SQLITE_OK) { + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; + } + + ret = sqlite3_step(stmt); + if (ret != SQLITE_ROW) { + sqlite3_reset(stmt); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; + } + int64_t num = sqlite3_column_int64(stmt, 0); + + ret = sqlite3_reset(stmt); + if (ret != SQLITE_OK) { + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; + } + + if (num < 0) { + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; + } + + if (num == 0) { + template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, csrf, user_id, -1); + continue; + } + + template_eval(builder, 200, WL_STR("pages/post.wl"), tpcache, &arena, dbcache, csrf, user_id, post_id); + + } else { + + template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, csrf, user_id, -1); + } + } + + session_storage_free(session_storage); + sqlite_cache_free(dbcache); + template_cache_free(tpcache); + sqlite3_close(db); + http_server_free(server); + http_global_free(); + return 0; +} diff --git a/src/random.c b/src/random.c index 3b87a6a..901f487 100644 --- a/src/random.c +++ b/src/random.c @@ -1,32 +1,32 @@ -#ifdef __linux__ -#include -#include -#endif - -#ifdef _WIN32 -#include -#endif - -#include "random.h" - -int generate_random_bytes(char *dst, int cap) -{ -#ifdef __linux__ - int copied = 0; - while (copied < cap) { - int ret = getrandom(dst, (size_t) cap, 0); - if (ret < 0) { - if (errno == EINTR) - continue; - return -1; - } - copied += ret; - } - return 0; -#endif - -#ifdef _WIN32 - NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG); - return BCRYPT_SUCCESS(status) ? 0 : -1; -#endif -} +#ifdef __linux__ +#include +#include +#endif + +#ifdef _WIN32 +#include +#endif + +#include "random.h" + +int generate_random_bytes(char *dst, int cap) +{ +#ifdef __linux__ + int copied = 0; + while (copied < cap) { + int ret = getrandom(dst, (size_t) cap, 0); + if (ret < 0) { + if (errno == EINTR) + continue; + return -1; + } + copied += ret; + } + return 0; +#endif + +#ifdef _WIN32 + NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + return BCRYPT_SUCCESS(status) ? 0 : -1; +#endif +} diff --git a/src/random.h b/src/random.h index 656e676..641e657 100644 --- a/src/random.h +++ b/src/random.h @@ -1,6 +1,6 @@ -#ifndef RANDOM_INCLUDED -#define RANDOM_INCLUDED - -int generate_random_bytes(char *dst, int cap); - +#ifndef RANDOM_INCLUDED +#define RANDOM_INCLUDED + +int generate_random_bytes(char *dst, int cap); + #endif // RANDOM_INCLUDED \ No newline at end of file diff --git a/src/session.c b/src/session.c new file mode 100644 index 0000000..2f27d2d --- /dev/null +++ b/src/session.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include +#include "random.h" +#include "session.h" + +#define CSRF_RAW_TOKEN_SIZE 32 +#define SESS_RAW_TOKEN_SIZE 32 + +#define CSRF_TOKEN_SIZE (2 * CSRF_RAW_TOKEN_SIZE) +#define SESS_TOKEN_SIZE (2 * SESS_RAW_TOKEN_SIZE) + +typedef struct { + int user; + char csrf[CSRF_TOKEN_SIZE]; + char sess[SESS_TOKEN_SIZE]; +} Session; + +struct SessionStorage { + int count; + int capacity; + Session items[]; +}; + +static bool is_hex_digit(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +static int hex_digit_to_int(char c) +{ + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return c - '0'; +} + +static void unpack_token(char *src, int srclen, char *dst, int dstlen) +{ + assert(2 * srclen == dstlen); + + for (int i = 0; i < srclen; i++) { + static const char table[] = "0123456789abcdef"; + int low = (src[i] & 0x0F) >> 0; + int high = (src[i] & 0xF0) >> 4; + dst[(i << 1) | 0] = table[high]; + dst[(i << 1) | 1] = table[low]; + } +} + +static int pack_token(char *src, int srclen, char *dst, int dstlen) +{ + if (srclen & 1) + return -1; + + assert(srclen == 2 * dstlen); + + for (int i = 0; i < srclen; i += 2) { + int high = src[i+0]; + int low = src[i+1]; + if (!is_hex_digit(high) || !is_hex_digit(low)) + return -1; + dst[i] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0); + } + + return 0; +} + +SessionStorage *session_storage_init(int max_sessions) +{ + int capacity = 2 * max_sessions; + SessionStorage *storage = malloc(sizeof(SessionStorage) + capacity * sizeof(Session)); + if (storage == NULL) + return NULL; + storage->count = 0; + storage->capacity = capacity; + for (int i = 0; i < capacity; i++) + storage->items[i].user = -1; + return storage; +} + +void session_storage_free(SessionStorage *storage) +{ + free(storage); +} + +#include // TODO + +static Session *lookup_session_slot(SessionStorage *storage, HTTP_String sess, bool find_unused) +{ + if (find_unused && 2 * storage->count + 2 > storage->capacity) + return NULL; + + if (sess.len != SESS_TOKEN_SIZE) + return NULL; + + uint64_t key; + if (sess.len < (int) (2 * sizeof(key))) + return NULL; + for (int i = 0; i < (int) sizeof(key); i++) { + + int high = sess.ptr[(i << 1) | 0]; + int low = sess.ptr[(i << 1) | 1]; + + if (!is_hex_digit(sess.ptr[i+0]) || + !is_hex_digit(sess.ptr[i+1])) + return NULL; + + key <<= 4; + key |= hex_digit_to_int(high); + + key <<= 4; + key |= hex_digit_to_int(low); + } + int i = key % storage->capacity; + + for (int j = 0; j < storage->capacity; j++) { + + if (find_unused) { + + if (storage->items[i].user < 0) + return &storage->items[i]; // Unused slot + + } else { + + if (storage->items[i].user == -1) + return NULL; + + if (storage->items[i].user != -2) + if (!memcmp(storage->items[i].sess, sess.ptr, SESS_TOKEN_SIZE)) + return &storage->items[i]; + } + + i++; + if (i == storage->capacity) + i = 0; + } + + return NULL; +} + +int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf) +{ + int ret; + char raw_sess[SESS_RAW_TOKEN_SIZE]; + char raw_csrf[CSRF_RAW_TOKEN_SIZE]; + + ret = generate_random_bytes(raw_sess, SESS_RAW_TOKEN_SIZE); + if (ret) return -1; + + ret = generate_random_bytes(raw_csrf, CSRF_RAW_TOKEN_SIZE); + if (ret) return -1; + + char sess[SESS_TOKEN_SIZE]; + char csrf[CSRF_TOKEN_SIZE]; + unpack_token(raw_sess, SESS_RAW_TOKEN_SIZE, sess, SESS_TOKEN_SIZE); + unpack_token(raw_csrf, CSRF_RAW_TOKEN_SIZE, csrf, CSRF_TOKEN_SIZE); + + Session *found = lookup_session_slot(storage, (HTTP_String) { sess, SESS_TOKEN_SIZE }, true); + if (found == NULL) return -1; + + found->user = user; + memcpy(found->sess, sess, SESS_TOKEN_SIZE); + memcpy(found->csrf, csrf, CSRF_TOKEN_SIZE); + + *psess = (HTTP_String) { found->sess, SESS_TOKEN_SIZE }; + *pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE }; + + storage->count++; + return 0; +} + +int delete_session(SessionStorage *storage, HTTP_String sess) +{ + char raw_sess[SESS_RAW_TOKEN_SIZE]; + if (sess.len != SESS_TOKEN_SIZE || pack_token(sess.ptr, sess.len, raw_sess, (int) sizeof(raw_sess)) < 0) + return -1; + Session *found = lookup_session_slot(storage, sess, false); + if (found == NULL) + return false; + assert(found->user >= 0); + found->user = -2; + storage->count--; + return 0; +} + +int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser) +{ + Session *found = lookup_session_slot(storage, sess, false); + if (found == NULL) + return -1; + assert(found->user >= 0); + *pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE }; + *puser = found->user; + return 0; +} diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..124a161 --- /dev/null +++ b/src/session.h @@ -0,0 +1,15 @@ +#ifndef SESSION_INCLUDED +#define SESSION_INCLUDED + +#include // Only for HTTP_String + +typedef struct SessionStorage SessionStorage; + +SessionStorage *session_storage_init(int max_sessions); +void session_storage_free(SessionStorage *storage); + +int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf); +int delete_session(SessionStorage *storage, HTTP_String sess); +int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser); + +#endif // SESSION_INCLUDED diff --git a/src/sqlite3utils.c b/src/sqlite3utils.c index 9985b5e..61360fb 100644 --- a/src/sqlite3utils.c +++ b/src/sqlite3utils.c @@ -1,159 +1,159 @@ -#include -#include -#include -#include -#include "sqlite3utils.h" - -typedef struct { - char *str; - int len; - sqlite3_stmt *stmt; -} Prepped; - -struct SQLiteCache { - sqlite3 *db; - int count; - int capacity_log2; - Prepped items[]; -}; - -SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2) -{ - SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped)); - if (cache == NULL) - return NULL; - - cache->db = db; - cache->count = 0; - cache->capacity_log2 = capacity_log2; - - for (int i = 0; i < (1 << capacity_log2); i++) - cache->items[i].stmt = NULL; - - return cache; -} - -void sqlite_cache_free(SQLiteCache *cache) -{ - for (int i = 0; i < (1 << cache->capacity_log2); i++) { - sqlite3_stmt *stmt = cache->items[i].stmt; - if (stmt) { - free(cache->items[i].str); - sqlite3_finalize(stmt); - } - } - free(cache); -} - -sqlite3 *sqlite_cache_getdb(SQLiteCache *cache) -{ - return cache->db; -} - -static unsigned long djb2(char *src, int len) -{ - char *ptr = src; - char *end = src + len; - - unsigned long hash = 5381; - int c; - while (ptr < end && (c = *ptr++)) - hash = ((hash << 5) + hash) + c; // hash * 33 + c - return hash; -} - -static int lookup(SQLiteCache *cache, char *fmt, int fmtlen) -{ - int mask = (1 << cache->capacity_log2) - 1; - int hash = djb2(fmt, fmtlen); - int i = hash & mask; - int perturb = hash; - for (;;) { - - if (cache->items[i].stmt == NULL) - return i; - - if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen)) - return i; - - perturb >>= 5; - i = (i * 5 + 1 + perturb) & mask; - } - - return -1; -} - -int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen) -{ - if (fmtlen < 0) - fmtlen = strlen(fmt); - - int i = lookup(cache, fmt, fmtlen); - if (cache->items[i].stmt == NULL) { - - printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO - - sqlite3_stmt *stmt; - int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL); - if (ret != SQLITE_OK) { - //fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__); - return ret; - } - - char *cpy = malloc(fmtlen); - if (cpy == NULL) { - sqlite3_finalize(stmt); - return SQLITE_NOMEM; - } - memcpy(cpy, fmt, fmtlen); - - cache->items[i].str = cpy; - cache->items[i].len = fmtlen; - cache->items[i].stmt = stmt; - } - sqlite3_stmt *stmt = cache->items[i].stmt; - - *pstmt = stmt; - return SQLITE_OK; -} - -int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, - sqlite3_stmt **pstmt, char *fmt, VArgs args) -{ - sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt)); - if (ret != SQLITE_OK) - return ret; - - for (int i = 0; i < args.len; i++) { - VArg arg = args.ptr[i]; - switch (arg.type) { - case VARG_TYPE_C : ret = sqlite3_bind_text (stmt, i+1, &arg.c, 1, NULL); break; - case VARG_TYPE_S : ret = sqlite3_bind_int (stmt, i+1, arg.s); break; - case VARG_TYPE_I : ret = sqlite3_bind_int (stmt, i+1, arg.i); break; - case VARG_TYPE_L : ret = sqlite3_bind_int64 (stmt, i+1, arg.l); break; - case VARG_TYPE_LL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ll); break; - case VARG_TYPE_SC : ret = sqlite3_bind_int (stmt, i+1, arg.sc); break; - case VARG_TYPE_SS : ret = sqlite3_bind_int (stmt, i+1, arg.ss); break; - case VARG_TYPE_SI : ret = sqlite3_bind_int (stmt, i+1, arg.si); break; - case VARG_TYPE_SL : ret = sqlite3_bind_int64 (stmt, i+1, arg.sl); break; - case VARG_TYPE_SLL: ret = sqlite3_bind_int (stmt, i+1, arg.sll); break; - case VARG_TYPE_UC : ret = sqlite3_bind_int (stmt, i+1, arg.uc); break; - case VARG_TYPE_US : ret = sqlite3_bind_int (stmt, i+1, arg.us); break; - case VARG_TYPE_UI : ret = sqlite3_bind_int64 (stmt, i+1, arg.ui); break; - case VARG_TYPE_UL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ul); break; - case VARG_TYPE_ULL: ret = sqlite3_bind_int64 (stmt, i+1, arg.ull); break; - case VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break; - case VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break; - case VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break; - case VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break; - } - if (ret != SQLITE_OK) { - sqlite3_reset(stmt); - return ret; - } - } - - *pstmt = stmt; - return SQLITE_OK; +#include +#include +#include +#include +#include "sqlite3utils.h" + +typedef struct { + char *str; + int len; + sqlite3_stmt *stmt; +} Prepped; + +struct SQLiteCache { + sqlite3 *db; + int count; + int capacity_log2; + Prepped items[]; +}; + +SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2) +{ + SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped)); + if (cache == NULL) + return NULL; + + cache->db = db; + cache->count = 0; + cache->capacity_log2 = capacity_log2; + + for (int i = 0; i < (1 << capacity_log2); i++) + cache->items[i].stmt = NULL; + + return cache; +} + +void sqlite_cache_free(SQLiteCache *cache) +{ + for (int i = 0; i < (1 << cache->capacity_log2); i++) { + sqlite3_stmt *stmt = cache->items[i].stmt; + if (stmt) { + free(cache->items[i].str); + sqlite3_finalize(stmt); + } + } + free(cache); +} + +sqlite3 *sqlite_cache_getdb(SQLiteCache *cache) +{ + return cache->db; +} + +static unsigned long djb2(char *src, int len) +{ + char *ptr = src; + char *end = src + len; + + unsigned long hash = 5381; + int c; + while (ptr < end && (c = *ptr++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; +} + +static int lookup(SQLiteCache *cache, char *fmt, int fmtlen) +{ + int mask = (1 << cache->capacity_log2) - 1; + int hash = djb2(fmt, fmtlen); + int i = hash & mask; + int perturb = hash; + for (;;) { + + if (cache->items[i].stmt == NULL) + return i; + + if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen)) + return i; + + perturb >>= 5; + i = (i * 5 + 1 + perturb) & mask; + } + + return -1; +} + +int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen) +{ + if (fmtlen < 0) + fmtlen = strlen(fmt); + + int i = lookup(cache, fmt, fmtlen); + if (cache->items[i].stmt == NULL) { + + printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO + + sqlite3_stmt *stmt; + int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + //fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__); + return ret; + } + + char *cpy = malloc(fmtlen); + if (cpy == NULL) { + sqlite3_finalize(stmt); + return SQLITE_NOMEM; + } + memcpy(cpy, fmt, fmtlen); + + cache->items[i].str = cpy; + cache->items[i].len = fmtlen; + cache->items[i].stmt = stmt; + } + sqlite3_stmt *stmt = cache->items[i].stmt; + + *pstmt = stmt; + return SQLITE_OK; +} + +int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, + sqlite3_stmt **pstmt, char *fmt, VArgs args) +{ + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt)); + if (ret != SQLITE_OK) + return ret; + + for (int i = 0; i < args.len; i++) { + VArg arg = args.ptr[i]; + switch (arg.type) { + case VARG_TYPE_C : ret = sqlite3_bind_text (stmt, i+1, &arg.c, 1, NULL); break; + case VARG_TYPE_S : ret = sqlite3_bind_int (stmt, i+1, arg.s); break; + case VARG_TYPE_I : ret = sqlite3_bind_int (stmt, i+1, arg.i); break; + case VARG_TYPE_L : ret = sqlite3_bind_int64 (stmt, i+1, arg.l); break; + case VARG_TYPE_LL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ll); break; + case VARG_TYPE_SC : ret = sqlite3_bind_int (stmt, i+1, arg.sc); break; + case VARG_TYPE_SS : ret = sqlite3_bind_int (stmt, i+1, arg.ss); break; + case VARG_TYPE_SI : ret = sqlite3_bind_int (stmt, i+1, arg.si); break; + case VARG_TYPE_SL : ret = sqlite3_bind_int64 (stmt, i+1, arg.sl); break; + case VARG_TYPE_SLL: ret = sqlite3_bind_int (stmt, i+1, arg.sll); break; + case VARG_TYPE_UC : ret = sqlite3_bind_int (stmt, i+1, arg.uc); break; + case VARG_TYPE_US : ret = sqlite3_bind_int (stmt, i+1, arg.us); break; + case VARG_TYPE_UI : ret = sqlite3_bind_int64 (stmt, i+1, arg.ui); break; + case VARG_TYPE_UL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ul); break; + case VARG_TYPE_ULL: ret = sqlite3_bind_int64 (stmt, i+1, arg.ull); break; + case VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break; + case VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break; + case VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break; + case VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break; + } + if (ret != SQLITE_OK) { + sqlite3_reset(stmt); + return ret; + } + } + + *pstmt = stmt; + return SQLITE_OK; } \ No newline at end of file diff --git a/src/sqlite3utils.h b/src/sqlite3utils.h index 9deeab4..96f7720 100644 --- a/src/sqlite3utils.h +++ b/src/sqlite3utils.h @@ -1,21 +1,21 @@ -#ifndef SQLITE3UTILS_INCLUDED -#define SQLITE3UTILS_INCLUDED - -#include "sqlite3.h" -#include "variadic.h" - -#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__)) - -typedef struct SQLiteCache SQLiteCache; - -SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2); -void sqlite_cache_free(SQLiteCache *cache); -sqlite3* sqlite_cache_getdb(SQLiteCache *cache); - -int sqlite3utils_prepare(SQLiteCache *cache, - sqlite3_stmt **pstmt, char *fmt, int fmtlen); - -int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, - sqlite3_stmt **pstmt, char *fmt, VArgs args); - +#ifndef SQLITE3UTILS_INCLUDED +#define SQLITE3UTILS_INCLUDED + +#include "sqlite3.h" +#include "variadic.h" + +#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__)) + +typedef struct SQLiteCache SQLiteCache; + +SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2); +void sqlite_cache_free(SQLiteCache *cache); +sqlite3* sqlite_cache_getdb(SQLiteCache *cache); + +int sqlite3utils_prepare(SQLiteCache *cache, + sqlite3_stmt **pstmt, char *fmt, int fmtlen); + +int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, + sqlite3_stmt **pstmt, char *fmt, VArgs args); + #endif // SQLITE3UTILS_INCLUDED \ No newline at end of file diff --git a/src/template.c b/src/template.c index 4e1a687..5d8f180 100644 --- a/src/template.c +++ b/src/template.c @@ -1,412 +1,419 @@ -#include -#include -#include -#include -#include -#include "template.h" -#include "sqlite3utils.h" - -#define TRACE(...) {} -//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__); - -typedef struct CachedProgram CachedProgram; -struct CachedProgram { - char path[1<<8]; - int pathlen; - WL_Program program; -}; - -struct TemplateCache { - int count; - int capacity_log2; - CachedProgram pool[]; -}; - -TemplateCache *template_cache_init(int capacity_log2) -{ - TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram)); - if (cache == NULL) - return NULL; - - cache->count = 0; - cache->capacity_log2 = capacity_log2; - - for (int i = 0; i < (1 << capacity_log2); i++) - cache->pool[i].pathlen = -1; - return cache; -} - -void template_cache_free(TemplateCache *cache) -{ - free(cache); -} - -static unsigned long djb2(WL_String str) -{ - char *ptr = str.ptr; - char *end = str.ptr + str.len; - - unsigned long hash = 5381; - int c; - while (ptr < end && (c = *ptr++)) - hash = ((hash << 5) + hash) + c; // hash * 33 + c - return hash; -} - -static int lookup(TemplateCache *cache, WL_String path) -{ - int mask = (1 << cache->capacity_log2) - 1; - int hash = djb2(path); - int i = hash & mask; - int perturb = hash; - for (;;) { - - if (cache->pool[i].pathlen == -1) - return i; - - if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen)) - return i; - - perturb >>= 5; - i = (i * 5 + 1 + perturb) & mask; - } - - return -1; -} - -typedef struct LoadedFile LoadedFile; -struct LoadedFile { - LoadedFile* next; - int len; - char data[]; -}; - -static LoadedFile *load_file(WL_String path) -{ - char buf[1<<10]; - if (path.len >= (int) sizeof(buf)) - return NULL; - memcpy(buf, path.ptr, path.len); - buf[path.len] = '\0'; - - FILE *stream = fopen(buf, "rb"); - if (stream == NULL) - return NULL; - - int ret = fseek(stream, 0, SEEK_END); - if (ret) { - fclose(stream); - return NULL; - } - - long tmp = ftell(stream); - if (tmp < 0 || tmp > INT_MAX) { - fclose(stream); - return NULL; - } - int len = (int) tmp; - - ret = fseek(stream, 0, SEEK_SET); - if (ret) { - fclose(stream); - return NULL; - } - - LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); - if (result == NULL) { - fclose(stream); - return NULL; - } - result->next = NULL; - result->len = len; - - int read_len = fread(result->data, 1, len+1, stream); - if (read_len != len || ferror(stream) || !feof(stream)) { - fclose(stream); - free(result); - return NULL; - } - - fclose(stream); - return result; -} - -static void free_loaded_files(LoadedFile *loaded_file) -{ - while (loaded_file) { - LoadedFile *next = loaded_file->next; - free(loaded_file); - loaded_file = next; - } -} - -static int compile(WL_String path, WL_Program *program, WL_Arena *arena) -{ - WL_Compiler *compiler = wl_compiler_init(arena); - if (compiler == NULL) { - TRACE("Couldn't initialize WL compiler object"); - return -1; - } - - LoadedFile *loaded_file_head = NULL; - LoadedFile **loaded_file_tail = &loaded_file_head; - - for (int i = 0;; i++) { - - LoadedFile *loaded_file = load_file(path); - if (loaded_file == NULL) { - TRACE("Couldn't load file '%.*s'", path.len, path.ptr); - free_loaded_files(loaded_file_head); - return -1; - } - - *loaded_file_tail = loaded_file; - loaded_file_tail = &loaded_file->next; - - WL_String content = { loaded_file->data, loaded_file->len }; - WL_AddResult result = wl_compiler_add(compiler, content); - - if (result.type == WL_ADD_ERROR) { - TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); - free_loaded_files(loaded_file_head); - return -1; - } - - if (result.type == WL_ADD_LINK) break; - - assert(result.type == WL_ADD_AGAIN); - path = result.path; - } - - int ret = wl_compiler_link(compiler, program); - if (ret < 0) { - TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); - return -1; - } - - free_loaded_files(loaded_file_head); - - TRACE("Compilation succeded"); - return 0; -} - -static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) -{ - int num_args = wl_arg_count(rt); - if (num_args == 0) - return 0; - - WL_String format; - if (!wl_arg_str(rt, 0, &format)) - return -1; - - sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len); - if (ret != SQLITE_OK) - return -1; - - for (int i = 1; i < num_args; i++) { - - int64_t ival; - double fval; - WL_String str; - - if (wl_arg_none(rt, i)) - ret = sqlite3_bind_null (stmt, i); - else if (wl_arg_s64(rt, i, &ival)) - ret = sqlite3_bind_int64 (stmt, i, ival); - else if (wl_arg_f64(rt, i, &fval)) - ret = sqlite3_bind_double(stmt, i, fval); - else if (wl_arg_str(rt, i, &str)) - ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL); - else assert(0); - - if (ret != SQLITE_OK) { - sqlite3_reset(stmt); - return -1; - } - } - - wl_push_array(rt, 0); - - while (sqlite3_step(stmt) == SQLITE_ROW) { - - int num_cols = sqlite3_column_count(stmt); - if (num_cols < 0) { - sqlite3_reset(stmt); - return -1; - } - - wl_push_map(rt, num_cols); - - for (int i = 0; i < num_cols; i++) { - ret = sqlite3_column_type(stmt, i); - switch (ret) { - - case SQLITE_INTEGER: - { - int64_t x = sqlite3_column_int64(stmt, i); - wl_push_s64(rt, x); - } - break; - - case SQLITE_FLOAT: - { - double x = sqlite3_column_double(stmt, i); - wl_push_f64(rt, x); - } - break; - - case SQLITE_TEXT: - { - const void *x = sqlite3_column_text(stmt, i); - int n = sqlite3_column_bytes(stmt, i); - wl_push_str(rt, (WL_String) { (char*) x, n }); - } - break; - - case SQLITE_BLOB: - { - const void *x = sqlite3_column_blob(stmt, i); - int n = sqlite3_column_bytes(stmt, i); - wl_push_str(rt, (WL_String) { (char*) x, n }); - } - break; - - case SQLITE_NULL: - { - wl_push_none(rt); - } - break; - } - - const char *name = sqlite3_column_name(stmt, i); - - wl_push_str(rt, (WL_String) { (char*) name, strlen(name) }); - wl_insert(rt); - } - - wl_append(rt); - } - - sqlite3_reset(stmt); - return 0; -} - -static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, int user_id, int post_id) -{ - (void) dbcache; - - if (wl_streq(name, "login_user_id", -1)) { - - if (user_id < 0) - wl_push_none(rt); - else - wl_push_s64(rt, user_id); - - } else if (wl_streq(name, "post_id", -1)) { - - if (post_id < 0) - wl_push_none(rt); - else - wl_push_s64(rt, post_id); - } -} - -static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache) -{ - if (wl_streq(name, "query", -1)) { - query_routine(rt, dbcache); - return; - } -} - -static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program) -{ - if (cache == NULL) - return -1; - - int i = lookup(cache, path); - if (cache->pool[i].pathlen == -1) { - - WL_Program program; - int ret = compile(path, &program, arena); - if (ret < 0) return -1; - - void *p = malloc(program.len); - if (p == NULL) - return -1; - memcpy(p, program.ptr, program.len); - program.ptr = p; - - if ((int) sizeof(cache->pool->path) <= path.len) - return -1; - memcpy(cache->pool[i].path, path.ptr, path.len); - cache->pool[i].path[path.len] = '\0'; - cache->pool[i].pathlen = path.len; - cache->pool[i].program = program; - } - - *program = cache->pool[i].program; - return 0; -} - -void template_eval(HTTP_ResponseBuilder builder, int status, - WL_String path, TemplateCache *cache, WL_Arena *arena, - SQLiteCache *dbcache, int user_id, int post_id) -{ - http_response_builder_status(builder, status); - - WL_Program program; - int ret = get_or_create_program(cache, path, arena, &program); - if (ret < 0) { - http_response_builder_undo(builder); - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - return; - } - - //wl_dump_program(program); - - WL_Runtime *rt = wl_runtime_init(arena, program); - if (rt == NULL) { - http_response_builder_undo(builder); - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - return; - } - - for (bool done = false; !done; ) { - - WL_EvalResult result = wl_runtime_eval(rt); - switch (result.type) { - - case WL_EVAL_DONE: - http_response_builder_done(builder); - done = true; - break; - - case WL_EVAL_ERROR: - // wl_runtime_error(rt) - http_response_builder_undo(builder); - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - return; - - case WL_EVAL_SYSVAR: - push_sysvar(rt, result.str, dbcache, user_id, post_id); - break; - - case WL_EVAL_SYSCALL: - push_syscall(rt, result.str, dbcache); - break; - - case WL_EVAL_OUTPUT: - http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len }); - break; - - default: - break; - } - } -} +#include +#include +#include +#include +#include +#include "template.h" +#include "sqlite3utils.h" + +#define TRACE(...) {} +//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__); + +typedef struct CachedProgram CachedProgram; +struct CachedProgram { + char path[1<<8]; + int pathlen; + WL_Program program; +}; + +struct TemplateCache { + int count; + int capacity_log2; + CachedProgram pool[]; +}; + +TemplateCache *template_cache_init(int capacity_log2) +{ + TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram)); + if (cache == NULL) + return NULL; + + cache->count = 0; + cache->capacity_log2 = capacity_log2; + + for (int i = 0; i < (1 << capacity_log2); i++) + cache->pool[i].pathlen = -1; + return cache; +} + +void template_cache_free(TemplateCache *cache) +{ + free(cache); +} + +static unsigned long djb2(WL_String str) +{ + char *ptr = str.ptr; + char *end = str.ptr + str.len; + + unsigned long hash = 5381; + int c; + while (ptr < end && (c = *ptr++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; +} + +static int lookup(TemplateCache *cache, WL_String path) +{ + int mask = (1 << cache->capacity_log2) - 1; + int hash = djb2(path); + int i = hash & mask; + int perturb = hash; + for (;;) { + + if (cache->pool[i].pathlen == -1) + return i; + + if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen)) + return i; + + perturb >>= 5; + i = (i * 5 + 1 + perturb) & mask; + } + + return -1; +} + +typedef struct LoadedFile LoadedFile; +struct LoadedFile { + LoadedFile* next; + int len; + char data[]; +}; + +static LoadedFile *load_file(WL_String path) +{ + char buf[1<<10]; + if (path.len >= (int) sizeof(buf)) + return NULL; + memcpy(buf, path.ptr, path.len); + buf[path.len] = '\0'; + + FILE *stream = fopen(buf, "rb"); + if (stream == NULL) + return NULL; + + int ret = fseek(stream, 0, SEEK_END); + if (ret) { + fclose(stream); + return NULL; + } + + long tmp = ftell(stream); + if (tmp < 0 || tmp > INT_MAX) { + fclose(stream); + return NULL; + } + int len = (int) tmp; + + ret = fseek(stream, 0, SEEK_SET); + if (ret) { + fclose(stream); + return NULL; + } + + LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); + if (result == NULL) { + fclose(stream); + return NULL; + } + result->next = NULL; + result->len = len; + + int read_len = fread(result->data, 1, len+1, stream); + if (read_len != len || ferror(stream) || !feof(stream)) { + fclose(stream); + free(result); + return NULL; + } + + fclose(stream); + return result; +} + +static void free_loaded_files(LoadedFile *loaded_file) +{ + while (loaded_file) { + LoadedFile *next = loaded_file->next; + free(loaded_file); + loaded_file = next; + } +} + +static int compile(WL_String path, WL_Program *program, WL_Arena *arena) +{ + WL_Compiler *compiler = wl_compiler_init(arena); + if (compiler == NULL) { + TRACE("Couldn't initialize WL compiler object"); + return -1; + } + + LoadedFile *loaded_file_head = NULL; + LoadedFile **loaded_file_tail = &loaded_file_head; + + for (int i = 0;; i++) { + + LoadedFile *loaded_file = load_file(path); + if (loaded_file == NULL) { + TRACE("Couldn't load file '%.*s'", path.len, path.ptr); + free_loaded_files(loaded_file_head); + return -1; + } + + *loaded_file_tail = loaded_file; + loaded_file_tail = &loaded_file->next; + + WL_String content = { loaded_file->data, loaded_file->len }; + WL_AddResult result = wl_compiler_add(compiler, content); + + if (result.type == WL_ADD_ERROR) { + TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); + free_loaded_files(loaded_file_head); + return -1; + } + + if (result.type == WL_ADD_LINK) break; + + assert(result.type == WL_ADD_AGAIN); + path = result.path; + } + + int ret = wl_compiler_link(compiler, program); + if (ret < 0) { + TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); + return -1; + } + + free_loaded_files(loaded_file_head); + + TRACE("Compilation succeded"); + return 0; +} + +static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) +{ + int num_args = wl_arg_count(rt); + if (num_args == 0) + return 0; + + WL_String format; + if (!wl_arg_str(rt, 0, &format)) + return -1; + + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len); + if (ret != SQLITE_OK) + return -1; + + for (int i = 1; i < num_args; i++) { + + int64_t ival; + double fval; + WL_String str; + + if (wl_arg_none(rt, i)) + ret = sqlite3_bind_null (stmt, i); + else if (wl_arg_s64(rt, i, &ival)) + ret = sqlite3_bind_int64 (stmt, i, ival); + else if (wl_arg_f64(rt, i, &fval)) + ret = sqlite3_bind_double(stmt, i, fval); + else if (wl_arg_str(rt, i, &str)) + ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL); + else assert(0); + + if (ret != SQLITE_OK) { + sqlite3_reset(stmt); + return -1; + } + } + + wl_push_array(rt, 0); + + while (sqlite3_step(stmt) == SQLITE_ROW) { + + int num_cols = sqlite3_column_count(stmt); + if (num_cols < 0) { + sqlite3_reset(stmt); + return -1; + } + + wl_push_map(rt, num_cols); + + for (int i = 0; i < num_cols; i++) { + ret = sqlite3_column_type(stmt, i); + switch (ret) { + + case SQLITE_INTEGER: + { + int64_t x = sqlite3_column_int64(stmt, i); + wl_push_s64(rt, x); + } + break; + + case SQLITE_FLOAT: + { + double x = sqlite3_column_double(stmt, i); + wl_push_f64(rt, x); + } + break; + + case SQLITE_TEXT: + { + const void *x = sqlite3_column_text(stmt, i); + int n = sqlite3_column_bytes(stmt, i); + wl_push_str(rt, (WL_String) { (char*) x, n }); + } + break; + + case SQLITE_BLOB: + { + const void *x = sqlite3_column_blob(stmt, i); + int n = sqlite3_column_bytes(stmt, i); + wl_push_str(rt, (WL_String) { (char*) x, n }); + } + break; + + case SQLITE_NULL: + { + wl_push_none(rt); + } + break; + } + + const char *name = sqlite3_column_name(stmt, i); + + wl_push_str(rt, (WL_String) { (char*) name, strlen(name) }); + wl_insert(rt); + } + + wl_append(rt); + } + + sqlite3_reset(stmt); + return 0; +} + +static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id) +{ + (void) dbcache; + + if (wl_streq(name, "login_user_id", -1)) { + + if (user_id < 0) + wl_push_none(rt); + else + wl_push_s64(rt, user_id); + + } else if (wl_streq(name, "post_id", -1)) { + + if (post_id < 0) + wl_push_none(rt); + else + wl_push_s64(rt, post_id); + + } else if (wl_streq(name, "csrf", -1)) { + + if (csrf.len == 0) + wl_push_none(rt); + else + wl_push_str(rt, (WL_String) { csrf.ptr, csrf.len }); + } +} + +static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache) +{ + if (wl_streq(name, "query", -1)) { + query_routine(rt, dbcache); + return; + } +} + +static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program) +{ + if (cache == NULL) + return -1; + + int i = lookup(cache, path); + if (cache->pool[i].pathlen == -1) { + + WL_Program program; + int ret = compile(path, &program, arena); + if (ret < 0) return -1; + + void *p = malloc(program.len); + if (p == NULL) + return -1; + memcpy(p, program.ptr, program.len); + program.ptr = p; + + if ((int) sizeof(cache->pool->path) <= path.len) + return -1; + memcpy(cache->pool[i].path, path.ptr, path.len); + cache->pool[i].path[path.len] = '\0'; + cache->pool[i].pathlen = path.len; + cache->pool[i].program = program; + } + + *program = cache->pool[i].program; + return 0; +} + +void template_eval(HTTP_ResponseBuilder builder, int status, + WL_String path, TemplateCache *cache, WL_Arena *arena, + SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id) +{ + http_response_builder_status(builder, status); + + WL_Program program; + int ret = get_or_create_program(cache, path, arena, &program); + if (ret < 0) { + http_response_builder_undo(builder); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + return; + } + + //wl_dump_program(program); + + WL_Runtime *rt = wl_runtime_init(arena, program); + if (rt == NULL) { + http_response_builder_undo(builder); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + return; + } + + for (bool done = false; !done; ) { + + WL_EvalResult result = wl_runtime_eval(rt); + switch (result.type) { + + case WL_EVAL_DONE: + http_response_builder_done(builder); + done = true; + break; + + case WL_EVAL_ERROR: + // wl_runtime_error(rt) + http_response_builder_undo(builder); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + return; + + case WL_EVAL_SYSVAR: + push_sysvar(rt, result.str, dbcache, csrf, user_id, post_id); + break; + + case WL_EVAL_SYSCALL: + push_syscall(rt, result.str, dbcache); + break; + + case WL_EVAL_OUTPUT: + http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len }); + break; + + default: + break; + } + } +} diff --git a/src/template.h b/src/template.h index ddd085c..7fa4928 100644 --- a/src/template.h +++ b/src/template.h @@ -1,16 +1,16 @@ -#ifndef TEMPLATE_INCLUDED -#define TEMPLATE_INCLUDED - -#include "wl.h" -#include "chttp.h" -#include "sqlite3utils.h" - -typedef struct TemplateCache TemplateCache; -TemplateCache *template_cache_init(int capacity_log2); -void template_cache_free(TemplateCache *cache); - -void template_eval(HTTP_ResponseBuilder builder, int status, - WL_String path, TemplateCache *cache, WL_Arena *arena, - SQLiteCache *dbcache, int user_id, int post_id); - +#ifndef TEMPLATE_INCLUDED +#define TEMPLATE_INCLUDED + +#include "wl.h" +#include "chttp.h" +#include "sqlite3utils.h" + +typedef struct TemplateCache TemplateCache; +TemplateCache *template_cache_init(int capacity_log2); +void template_cache_free(TemplateCache *cache); + +void template_eval(HTTP_ResponseBuilder builder, int status, + WL_String path, TemplateCache *cache, WL_Arena *arena, + SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id); + #endif // TEMPLATE_INCLUDED \ No newline at end of file diff --git a/src/variadic.c b/src/variadic.c index 3754d4f..d04e40b 100644 --- a/src/variadic.c +++ b/src/variadic.c @@ -1,21 +1,21 @@ -#include "variadic.h" - -VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; } -VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; } -VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; } -VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; } -VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; } -VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; } -VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; } -VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; } -VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; } -VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; } -VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; } -VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; } -VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; } -VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; } -VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; } -VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; } -VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; } -VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; } -VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; } +#include "variadic.h" + +VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; } +VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; } +VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; } +VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; } +VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; } +VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; } +VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; } +VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; } +VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; } +VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; } +VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; } +VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; } +VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; } +VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; } +VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; } +VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; } +VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; } +VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; } +VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; } diff --git a/src/variadic.h b/src/variadic.h index 65ba096..c7733b7 100644 --- a/src/variadic.h +++ b/src/variadic.h @@ -1,110 +1,110 @@ -#ifndef VARIADIC_INCLUDED -#define VARIADIC_INCLUDED - -#include -#include "chttp.h" - -typedef enum { - VARG_TYPE_C, - VARG_TYPE_S, - VARG_TYPE_I, - VARG_TYPE_L, - VARG_TYPE_LL, - VARG_TYPE_SC, - VARG_TYPE_SS, - VARG_TYPE_SI, - VARG_TYPE_SL, - VARG_TYPE_SLL, - VARG_TYPE_UC, - VARG_TYPE_US, - VARG_TYPE_UI, - VARG_TYPE_UL, - VARG_TYPE_ULL, - VARG_TYPE_F, - VARG_TYPE_D, - VARG_TYPE_B, - VARG_TYPE_STR, -} VArgType; - -typedef struct { - VArgType type; - union { - char c; - short s; - int i; - long l; - long long ll; - signed char sc; - signed short ss; - signed int si; - signed long sl; - signed long long sll; - unsigned char uc; - unsigned short us; - unsigned int ui; - unsigned long ul; - unsigned long long ull; - float f; - double d; - bool b; - HTTP_String str; - }; -} VArg; - -VArg varg_from_c (char c); -VArg varg_from_s (short s); -VArg varg_from_i (int i); -VArg varg_from_l (long l); -VArg varg_from_ll (long long ll); -VArg varg_from_sc (char sc); -VArg varg_from_ss (short ss); -VArg varg_from_si (int si); -VArg varg_from_sl (long sl); -VArg varg_from_sll (long long sll); -VArg varg_from_uc (char uc); -VArg varg_from_us (short us); -VArg varg_from_ui (int ui); -VArg varg_from_ul (long ul); -VArg varg_from_ull (long long ull); -VArg varg_from_f (float f); -VArg varg_from_d (double d); -VArg varg_from_b (bool b); -VArg varg_from_str (HTTP_String str); - -#define VARG(X) (_Generic((X), \ - char : varg_from_c, \ - short : varg_from_s, \ - int : varg_from_i, \ - long : varg_from_l, \ - long long : varg_from_ll, \ - signed char : varg_from_sc, \ - /*signed short : varg_from_ss,*/ \ - /*signed int : varg_from_si,*/ \ - /*signed long : varg_from_sl,*/ \ - /*signed long long : varg_from_sll,*/ \ - unsigned char : varg_from_uc, \ - unsigned short : varg_from_us, \ - unsigned int : varg_from_ui, \ - unsigned long : varg_from_ul, \ - unsigned long long: varg_from_ull, \ - float : varg_from_f, \ - double : varg_from_d, \ - bool : varg_from_b, \ - HTTP_String : varg_from_str \ -))(X) - -typedef struct { - int len; - VArg *ptr; -} VArgs; - -#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } } -#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } } -#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } } -#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } } -#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } } - -#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME -#define VARGS(...) DISPATCH__(__VA_ARGS__, VARGS_5, VARGS_4, VARGS_3, VARGS_2, VARGS_1)(__VA_ARGS__) - +#ifndef VARIADIC_INCLUDED +#define VARIADIC_INCLUDED + +#include +#include "chttp.h" + +typedef enum { + VARG_TYPE_C, + VARG_TYPE_S, + VARG_TYPE_I, + VARG_TYPE_L, + VARG_TYPE_LL, + VARG_TYPE_SC, + VARG_TYPE_SS, + VARG_TYPE_SI, + VARG_TYPE_SL, + VARG_TYPE_SLL, + VARG_TYPE_UC, + VARG_TYPE_US, + VARG_TYPE_UI, + VARG_TYPE_UL, + VARG_TYPE_ULL, + VARG_TYPE_F, + VARG_TYPE_D, + VARG_TYPE_B, + VARG_TYPE_STR, +} VArgType; + +typedef struct { + VArgType type; + union { + char c; + short s; + int i; + long l; + long long ll; + signed char sc; + signed short ss; + signed int si; + signed long sl; + signed long long sll; + unsigned char uc; + unsigned short us; + unsigned int ui; + unsigned long ul; + unsigned long long ull; + float f; + double d; + bool b; + HTTP_String str; + }; +} VArg; + +VArg varg_from_c (char c); +VArg varg_from_s (short s); +VArg varg_from_i (int i); +VArg varg_from_l (long l); +VArg varg_from_ll (long long ll); +VArg varg_from_sc (char sc); +VArg varg_from_ss (short ss); +VArg varg_from_si (int si); +VArg varg_from_sl (long sl); +VArg varg_from_sll (long long sll); +VArg varg_from_uc (char uc); +VArg varg_from_us (short us); +VArg varg_from_ui (int ui); +VArg varg_from_ul (long ul); +VArg varg_from_ull (long long ull); +VArg varg_from_f (float f); +VArg varg_from_d (double d); +VArg varg_from_b (bool b); +VArg varg_from_str (HTTP_String str); + +#define VARG(X) (_Generic((X), \ + char : varg_from_c, \ + short : varg_from_s, \ + int : varg_from_i, \ + long : varg_from_l, \ + long long : varg_from_ll, \ + signed char : varg_from_sc, \ + /*signed short : varg_from_ss,*/ \ + /*signed int : varg_from_si,*/ \ + /*signed long : varg_from_sl,*/ \ + /*signed long long : varg_from_sll,*/ \ + unsigned char : varg_from_uc, \ + unsigned short : varg_from_us, \ + unsigned int : varg_from_ui, \ + unsigned long : varg_from_ul, \ + unsigned long long: varg_from_ull, \ + float : varg_from_f, \ + double : varg_from_d, \ + bool : varg_from_b, \ + HTTP_String : varg_from_str \ +))(X) + +typedef struct { + int len; + VArg *ptr; +} VArgs; + +#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } } +#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } } +#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } } +#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } } +#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } } + +#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME +#define VARGS(...) DISPATCH__(__VA_ARGS__, VARGS_5, VARGS_4, VARGS_3, VARGS_2, VARGS_1)(__VA_ARGS__) + #endif // VARIADIC_INCLUDED \ No newline at end of file