#include #include #include #include #include #ifndef WL_NOINCLUDE #include "wl.h" #endif ///////////////////////////////////////////////////////////////////////// // 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) #define COUNT(X) (int) (sizeof(X)/sizeof((X)[0])) #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_u32(Writer *w, uint32_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_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_KWORD_ESCAPE, 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_ESCAPE, 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_KWORD_ESCAPE : write_text(w, S("escape")); 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 }; if (streq(kword, S("escape"))) return (Token) { .type=TOKEN_KWORD_ESCAPE }; 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 quote = 0;;) { int off = s->cur; while (s->cur < s->len && s->src[s->cur] != '\\' && (quote || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) { if (quote) { if (quote == s->src[s->cur]) quote = 0; } else { char c = s->src[s->cur]; if (c == '"' || c == '\'') quote = c; } 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) { parser_report(p, "Unexpected end of source inside an HTML element"); return NULL; } 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] != '>') { parser_report(p, "Missing '>' after '/' inside closing HTML tag"); return NULL; } 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) { parser_report(p, "Unexpected end of source inside an HTML element"); return NULL; } 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) { parser_report(p, "Unexpected token where a tag name was expected inside closing HTML tag"); return NULL; } String closing_tagname = t.sval; if (!streq(closing_tagname, tagname)) { parser_report(p, "Closing tag name '%.*s' doesn't match the opening tag '%.*s'", closing_tagname.len, closing_tagname.ptr, tagname.len, tagname.ptr ); return NULL; } t = next_token(p); if (t.type != TOKEN_OPER_GRT) { parser_report(p, "Missing '>' after closing HTML tag name"); return NULL; } 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_KWORD_ESCAPE: { Node *child = parse_atom(p); if (child == NULL) return NULL; Node *parent = alloc_node(p); if (parent == NULL) return NULL; parent->type = NODE_OPER_ESCAPE; 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_ESCAPE: write_text(w, S("(escape ")); 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_ESCAPE, 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]; 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; } 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, bool inside_html); static void walk_expr_node(Codegen *cg, Node *node, bool one) { 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_ESCAPE: cg_write_opcode(cg, OPCODE_GROUP); walk_expr_node(cg, node->left, false); cg_write_opcode(cg, OPCODE_ESCAPE); 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, true); 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, true); 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, bool inside_html) { switch (node->type) { case NODE_GLOBAL: for (Node *child = node->left; child; child = child->next) { walk_node(cg, child, false); } break; case NODE_COMPOUND: cg_push_scope(cg, SCOPE_COMPOUND); for (Node *child = node->left; child; child = child->next) walk_node(cg, child, inside_html); 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, false); 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, inside_html); 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, inside_html); 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, inside_html); 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, inside_html); 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, inside_html); 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, false); break; default: walk_expr_node(cg, node, false); if (cg_global_scope(cg) && !inside_assignment(cg) && !inside_html) 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, false); 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_ESCAPE: write_text(w, S("ESCAPE\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 (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) != 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 path, 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 }; } // Make include paths relative to the parent file if (path.len > 0) { String parent = { path.ptr, path.len }; char sep = '/'; while (parent.len > 0 && parent.ptr[parent.len-1] != sep) parent.len--; if (parent.len > 0) { Node *include = pres.includes; while (include) { char *dst = alloc(compiler->arena, parent.len + include->include_path.len + 1, 1); if (dst == NULL) { // TODO: report error return (WL_AddResult) { .type=WL_ADD_ERROR }; } memcpy(dst, parent.ptr, parent.len); memcpy(dst + parent.len, include->include_path.ptr, include->include_path.len); include->include_path = (String) { dst, parent.len + include->include_path.len }; include = include->include_next; } } } 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) { // TODO: report error return (WL_AddResult) { .type=WL_ADD_ERROR }; } 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 }; } 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("")); 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; } static Value value_escape_packed(Value v, WL_Arena *arena, Error *err); static int array_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err) { Value v2 = value_empty_array(value_length(v), arena, err); if (v2 == VALUE_ERROR) return -1; AggregateValue *src = (void*) (v & ~(Value) 7); for (int i = 0; i < src->count; i++) { Value child = src->vals[i]; Value escaped_child = value_escape_packed(child, arena, err); if (escaped_child == VALUE_ERROR) return -1; if (!value_append(v2, escaped_child, arena, err)) return -1; } Extension *ext = src->ext; while (ext) { for (int i = 0; i < ext->count; i++) { Value child = src->vals[i]; Value escaped_child = value_escape_packed(child, arena, err); if (escaped_child == VALUE_ERROR) return -1; if (!value_append(v2, escaped_child, arena, err)) return -1; } ext = ext->next; } if (max == 0) return -1; out[0] = v2; return 1; } static int string_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err) { String s = value_to_str(v); int i = 0; int num = 0; for (;;) { int off = i; while (i < s.len && s.ptr[i] != '<' && s.ptr[i] != '>' && s.ptr[i] != '&' && s.ptr[i] != '"' && s.ptr[i] != '\'') i++; String substr = { s.ptr + off, i - off }; Value escaped_v = value_from_str(substr, arena, err); // TODO: don't copy the string if (escaped_v == VALUE_ERROR) return -1; if (num == max) { REPORT(err, "Escape buffer limit reached"); return -1; } out[num++] = escaped_v; if (i == s.len) break; switch (s.ptr[i++]) { case '<' : escaped_v = value_from_str(S("<"), arena, err); break; // TODO: don't come these strings case '>' : escaped_v = value_from_str(S(">"), arena, err); break; case '&' : escaped_v = value_from_str(S("&"), arena, err); break; case '"' : escaped_v = value_from_str(S("""), arena, err); break; case '\'': escaped_v = value_from_str(S("'"), arena, err); break; } if (escaped_v == VALUE_ERROR) return -1; if (num == max) { REPORT(err, "Escape buffer limit reached"); return -1; } out[num++] = escaped_v; } return num; } static int value_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err) { Type t = value_type(v); if (t == TYPE_ARRAY) return array_escape(v, out, max, arena, err); if (t == TYPE_STRING) return string_escape(v, out, max, arena, err); if (max < 1) return -1; out[0] = v; return 1; } static Value value_escape_packed(Value v, WL_Arena *arena, Error *err) { Value tmp[32]; int num = value_escape(v, tmp, COUNT(tmp), arena, err); if (num < 0) return VALUE_ERROR; Value escaped_v; if (num > 1) { Value packed = value_empty_array(num, arena, err); if (packed == VALUE_ERROR) return VALUE_ERROR; for (int j = 0; j < num; j++) if (!value_append(packed, tmp[j], arena, err)) return VALUE_ERROR; escaped_v = packed; } else { ASSERT(num == 1); escaped_v = tmp[0]; } return escaped_v; } #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 (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) { #if 0 { printf("vars = [\n"); int top = rt->vars; for (int i = 0; i < rt->num_frames; i++) { printf(" frame %d [ ", i); for (int j = 0; j < rt->frames[i].varbase - top; j++) { switch (value_type(rt->values[top + j + 1])) { case TYPE_NONE : printf("none"); break; case TYPE_BOOL : printf("bool"); break; case TYPE_INT : printf("int"); break; case TYPE_FLOAT : printf("float"); break; case TYPE_STRING: printf("string"); break; case TYPE_ARRAY : printf("array"); break; case TYPE_MAP : printf("map"); break; case TYPE_ERROR : printf("error"); break; } printf(" "); } printf("]\n"); top = rt->frames[i].varbase; } printf("]\n"); printf("stack = [\n"); for (int i = 0; i < rt->stack; i++) { printf(" "); switch (value_type(rt->values[i])) { case TYPE_NONE : printf("none"); break; case TYPE_BOOL : printf("bool"); break; case TYPE_INT : printf("int"); break; case TYPE_FLOAT : printf("float"); break; case TYPE_STRING: printf("string"); break; case TYPE_ARRAY : printf("array"); break; case TYPE_MAP : printf("map"); break; case TYPE_ERROR : printf("error"); break; } printf("\n"); } printf("]\n"); char buf[1<<9]; Writer w = { .dst=buf, .cap=sizeof(buf), .len=0 }; write_instr(&w, rt->code.ptr + rt->off, rt->code.len - rt->off, rt->data ); printf("%d: %.*s", rt->off, w.len, w.dst); printf("\n\n"); } #endif 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_ESCAPE: { ASSERT(rt->num_groups > 0); int start = rt->groups[--rt->num_groups]; int end = rt->stack; Value escaped[256]; int num_escaped = 0; for (int i = start; i < end; i++) { Value v = rt->values[i]; int num = value_escape(v, escaped + num_escaped, COUNT(escaped) - num_escaped, rt->arena, &rt->err); if (num < 0) break; num_escaped += num; } if (num_escaped > COUNT(escaped)) { REPORT(&rt->err, "Escape buffer limit reached"); rt->state = RUNTIME_ERROR; break; } rt->stack = start; if (!rt_check_stack(rt, num_escaped)) break; for (int i = 0; i < num_escaped; i++) rt->values[rt->stack + i] = escaped[i]; rt->stack += num_escaped; } 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"); }