Files
cozisnews/3p/WL.c
T

5635 lines
137 KiB
C

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "wl.h"
/////////////////////////////////////////////////////////////////////////
// BASIC
/////////////////////////////////////////////////////////////////////////
typedef struct {
char *ptr;
int len;
} String;
typedef struct {
char *buf;
int cap;
bool yes;
} Error;
#define S(X) (String) { (X), SIZEOF(X)-1 }
#ifdef _WIN32
#define LLD "lld"
#define LLU "llu"
#else
#define LLD "ld"
#define LLU "lu"
#endif
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
#define SIZEOF(X) (int) sizeof(X)
#define ALIGNOF(X) (int) _Alignof(X)
#ifndef NDEBUG
#define UNREACHABLE __builtin_trap()
#define ASSERT(X) if (!(X)) __builtin_trap();
#else
#define UNREACHABLE {}
#define ASSERT(X) {}
#endif
static bool is_space(char c)
{
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
static bool is_digit(char c)
{
return c >= '0' && c <= '9';
}
static bool is_alpha(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
static bool is_printable(char c)
{
return c >= ' ' && c <= '~';
}
static bool is_hex_digit(char c)
{
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
static char to_lower(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A' + 'a';
return c;
}
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;
}
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;
}
#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;
}
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 };
}
/////////////////////////////////////////////////////////////////////////
// WRITER
/////////////////////////////////////////////////////////////////////////
typedef struct {
char *dst;
int cap;
int len;
} Writer;
static void write_raw_mem(Writer *w, void *ptr, int len)
{
if (w->cap > w->len) {
int cpy = MIN(w->cap - w->len, len);
if (ptr && w->dst)
memcpy(w->dst + w->len, ptr, cpy);
}
w->len += len;
}
static void write_raw_u8 (Writer *w, uint8_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_u16(Writer *w, uint16_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_u32(Writer *w, uint32_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_u64(Writer *w, uint64_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s8 (Writer *w, int8_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s16(Writer *w, int16_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s32(Writer *w, int32_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s64(Writer *w, int64_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_f32(Writer *w, float x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_f64(Writer *w, double x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_text(Writer *w, String str)
{
write_raw_mem(w, str.ptr, str.len);
}
static void write_text_s64(Writer *w, int64_t n)
{
int len;
if (w->len < w->cap)
len = snprintf(w->dst + w->len, w->cap - w->len, "%" LLD, n);
else
len = snprintf(NULL, 0, "%" LLD, n);
ASSERT(len >= 0);
w->len += len;
}
static void write_text_f64(Writer *w, double n)
{
int len;
if (w->len < w->cap)
len = snprintf(w->dst + w->len, w->cap - w->len, "%2.2f", n);
else
len = snprintf(NULL, 0, "%2.2f", n);
ASSERT(len >= 0);
w->len += len;
}
static void patch_mem(Writer *w, void *src, int off, int len)
{
ASSERT(off + len <= w->len);
if (off < w->cap) {
int cpy = MIN(w->cap - off, len);
memcpy(w->dst + off, src, cpy);
}
}
/////////////////////////////////////////////////////////////////////////
// PARSER
/////////////////////////////////////////////////////////////////////////
typedef struct {
char *src;
int len;
int cur;
} Scanner;
typedef enum {
TOKEN_END,
TOKEN_ERROR,
TOKEN_IDENT,
TOKEN_KWORD_IF,
TOKEN_KWORD_ELSE,
TOKEN_KWORD_WHILE,
TOKEN_KWORD_FOR,
TOKEN_KWORD_IN,
TOKEN_KWORD_PROCEDURE,
TOKEN_KWORD_LET,
TOKEN_KWORD_NONE,
TOKEN_KWORD_TRUE,
TOKEN_KWORD_FALSE,
TOKEN_KWORD_INCLUDE,
TOKEN_KWORD_LEN,
TOKEN_VALUE_FLOAT,
TOKEN_VALUE_INT,
TOKEN_VALUE_STR,
TOKEN_OPER_EQL,
TOKEN_OPER_NQL,
TOKEN_OPER_LSS,
TOKEN_OPER_GRT,
TOKEN_OPER_ADD,
TOKEN_OPER_SUB,
TOKEN_OPER_MUL,
TOKEN_OPER_DIV,
TOKEN_OPER_MOD,
TOKEN_OPER_ASS,
TOKEN_OPER_SHOVEL,
TOKEN_PAREN_OPEN,
TOKEN_PAREN_CLOSE,
TOKEN_BRACKET_OPEN,
TOKEN_BRACKET_CLOSE,
TOKEN_CURLY_OPEN,
TOKEN_CURLY_CLOSE,
TOKEN_DOT,
TOKEN_COMMA,
TOKEN_COLON,
TOKEN_DOLLAR,
TOKEN_NEWLINE,
} TokType;
typedef struct {
TokType type;
int64_t ival;
double fval;
String sval;
} Token;
typedef enum {
NODE_PROCEDURE_DECL,
NODE_PROCEDURE_ARG,
NODE_PROCEDURE_CALL,
NODE_VAR_DECL,
NODE_COMPOUND,
NODE_GLOBAL,
NODE_IFELSE,
NODE_FOR,
NODE_WHILE,
NODE_INCLUDE,
NODE_SELECT,
NODE_NESTED,
NODE_OPER_LEN,
NODE_OPER_POS,
NODE_OPER_NEG,
NODE_OPER_ASS,
NODE_OPER_EQL,
NODE_OPER_NQL,
NODE_OPER_LSS,
NODE_OPER_GRT,
NODE_OPER_ADD,
NODE_OPER_SUB,
NODE_OPER_MUL,
NODE_OPER_DIV,
NODE_OPER_MOD,
NODE_OPER_SHOVEL,
NODE_VALUE_INT,
NODE_VALUE_FLOAT,
NODE_VALUE_STR,
NODE_VALUE_NONE,
NODE_VALUE_TRUE,
NODE_VALUE_FALSE,
NODE_VALUE_VAR,
NODE_VALUE_SYSVAR,
NODE_VALUE_HTML,
NODE_VALUE_ARRAY,
NODE_VALUE_MAP,
} NodeType;
typedef struct Node Node;
struct Node {
NodeType type;
Node *next;
Node *key;
Node *left;
Node *right;
Node *child;
uint64_t ival;
double fval;
String sval;
String html_tag;
Node* html_attr;
Node* html_child;
bool html_body;
Node *if_cond;
Node *if_branch1;
Node *if_branch2;
Node *while_cond;
Node *while_body;
String for_var1;
String for_var2;
Node* for_set;
String proc_name;
Node* proc_args;
Node* proc_body;
String var_name;
Node* var_value;
String include_path;
Node* include_next;
Node* include_root;
};
typedef struct {
Node *node;
Node *includes;
int errlen;
} ParseResult;
typedef struct {
Scanner s;
WL_Arena* arena;
char* errbuf;
int errmax;
int errlen;
Node* include_head;
Node** include_tail;
} Parser;
static bool consume_str(Scanner *s, String x)
{
if (x.len == 0)
return false;
if (x.len > s->len - s->cur)
return false;
for (int i = 0; i < x.len; i++)
if (s->src[s->cur+i] != x.ptr[i])
return false;
s->cur += x.len;
return true;
}
static void write_token(Writer *w, Token token)
{
switch (token.type) {
default : write_text(w, S("???")); break;
case TOKEN_END : write_text(w, S("<EOF>")); break;
case TOKEN_ERROR : write_text(w, S("<ERROR>")); break;
case TOKEN_IDENT : write_text(w, token.sval); break;
case TOKEN_KWORD_IF : write_text(w, S("if")); break;
case TOKEN_KWORD_ELSE : write_text(w, S("else")); break;
case TOKEN_KWORD_WHILE : write_text(w, S("while")); break;
case TOKEN_KWORD_FOR : write_text(w, S("for")); break;
case TOKEN_KWORD_IN : write_text(w, S("in")); break;
case TOKEN_KWORD_PROCEDURE: write_text(w, S("procedure")); break;
case TOKEN_KWORD_LET : write_text(w, S("let")); break;
case TOKEN_KWORD_NONE : write_text(w, S("none")); break;
case TOKEN_KWORD_TRUE : write_text(w, S("true")); break;
case TOKEN_KWORD_FALSE : write_text(w, S("false")); break;
case TOKEN_KWORD_INCLUDE: write_text(w, S("include")); break;
case TOKEN_KWORD_LEN : write_text(w, S("len")); break;
case TOKEN_VALUE_FLOAT : write_text_f64(w, token.fval); break;
case TOKEN_VALUE_INT : write_text_s64(w, token.ival); break;
case TOKEN_OPER_ASS : write_text(w, S("=")); break;
case TOKEN_OPER_EQL : write_text(w, S("==")); break;
case TOKEN_OPER_NQL : write_text(w, S("!=")); break;
case TOKEN_OPER_LSS : write_text(w, S("<")); break;
case TOKEN_OPER_GRT : write_text(w, S(">")); break;
case TOKEN_OPER_ADD : write_text(w, S("+")); break;
case TOKEN_OPER_SUB : write_text(w, S("-")); break;
case TOKEN_OPER_MUL : write_text(w, S("*")); break;
case TOKEN_OPER_DIV : write_text(w, S("/")); break;
case TOKEN_OPER_MOD : write_text(w, S("%")); break;
case TOKEN_OPER_SHOVEL : write_text(w, S("<<")); break;
case TOKEN_PAREN_OPEN : write_text(w, S("(")); break;
case TOKEN_PAREN_CLOSE : write_text(w, S(")")); break;
case TOKEN_BRACKET_OPEN : write_text(w, S("[")); break;
case TOKEN_BRACKET_CLOSE: write_text(w, S("]")); break;
case TOKEN_CURLY_OPEN : write_text(w, S("{")); break;
case TOKEN_CURLY_CLOSE : write_text(w, S("}")); break;
case TOKEN_DOT : write_text(w, S(".")); break;
case TOKEN_COMMA : write_text(w, S(",")); break;
case TOKEN_COLON : write_text(w, S(":")); break;
case TOKEN_DOLLAR : write_text(w, S("$")); break;
case TOKEN_NEWLINE : write_text(w, S("\\n")); break;
case TOKEN_VALUE_STR:
write_text(w, S("\""));
write_text(w, token.sval); // TODO: Escape
write_text(w, S("\""));
break;
}
}
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;
while (p->s.cur < p->s.len) {
if (consume_str(&p->s, S("-->")))
break;
p->s.cur++;
}
}
if (p->s.cur == p->s.len)
return (Token) { .type=TOKEN_END };
char c = p->s.src[p->s.cur];
if (is_alpha(c) || c == '_') {
int start = p->s.cur;
do
p->s.cur++;
while (p->s.cur < p->s.len && (is_alpha(p->s.src[p->s.cur]) || is_digit(p->s.src[p->s.cur]) || p->s.src[p->s.cur] == '_'));
String kword = {
p->s.src + start,
p->s.cur - start
};
if (streq(kword, S("if"))) return (Token) { .type=TOKEN_KWORD_IF };
if (streq(kword, S("else"))) return (Token) { .type=TOKEN_KWORD_ELSE };
if (streq(kword, S("while"))) return (Token) { .type=TOKEN_KWORD_WHILE };
if (streq(kword, S("for"))) return (Token) { .type=TOKEN_KWORD_FOR };
if (streq(kword, S("in"))) return (Token) { .type=TOKEN_KWORD_IN };
if (streq(kword, S("procedure"))) return (Token) { .type=TOKEN_KWORD_PROCEDURE };
if (streq(kword, S("let"))) return (Token) { .type=TOKEN_KWORD_LET };
if (streq(kword, S("none"))) return (Token) { .type=TOKEN_KWORD_NONE };
if (streq(kword, S("true"))) return (Token) { .type=TOKEN_KWORD_TRUE };
if (streq(kword, S("false"))) return (Token) { .type=TOKEN_KWORD_FALSE };
if (streq(kword, S("include"))) return (Token) { .type=TOKEN_KWORD_INCLUDE };
if (streq(kword, S("len"))) return (Token) { .type=TOKEN_KWORD_LEN };
return (Token) { .type=TOKEN_IDENT, .sval=kword };
}
if (is_digit(c)) {
int peek = p->s.cur;
do
peek++;
while (peek < p->s.len && is_digit(p->s.src[peek]));
if (p->s.len - peek > 1 && p->s.src[peek] == '.' && is_digit(p->s.src[peek+1])) {
double buf = 0;
do {
int d = p->s.src[p->s.cur++] - '0';
buf = buf * 10 + d;
} while (p->s.cur < p->s.len && p->s.src[p->s.cur] != '.');
p->s.cur++;
double q = 1;
do {
int d = p->s.src[p->s.cur++] - '0';
q /= 10;
buf += q * d;
} while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur]));
return (Token) { .type=TOKEN_VALUE_FLOAT, .fval=buf };
} else {
uint64_t buf = 0;
do {
int d = p->s.src[p->s.cur++] - '0';
if (buf > (UINT64_MAX - d) / 10) {
parser_report(p, "Integer literal overflow");
return (Token) { .type=TOKEN_ERROR };
}
buf = buf * 10 + d;
} while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur]));
return (Token) { .type=TOKEN_VALUE_INT, .ival=buf };
}
}
if (c == '\'' || c == '"') {
char f = c;
p->s.cur++;
char *buf = NULL;
int len = 0;
for (;;) {
int substr_off = p->s.cur;
while (p->s.cur < p->s.len && is_printable(p->s.src[p->s.cur]) && p->s.src[p->s.cur] != f && p->s.src[p->s.cur] != '\\')
p->s.cur++;
int substr_len = p->s.cur - substr_off;
if (buf == NULL)
buf = alloc(p->arena, substr_len+1, 1);
else
if (!grow_alloc(p->arena, buf, len + substr_len+1))
buf = NULL;
if (buf == NULL) {
parser_report(p, "Out of memory");
return (Token) { .type=TOKEN_ERROR };
}
if (substr_len > 0) {
memcpy(
buf + len,
p->s.src + substr_off,
p->s.cur - substr_off
);
len += substr_len;
}
if (p->s.cur == p->s.len) {
parser_report(p, "String literal wasn't closed");
return (Token) { .type=TOKEN_ERROR };
}
if (!is_printable(p->s.src[p->s.cur])) {
parser_report(p, "Invalid byte in string literal");
return (Token) { .type=TOKEN_ERROR };
}
if (p->s.src[p->s.cur] == f)
break;
p->s.cur++;
if (p->s.cur == p->s.len) {
parser_report(p, "Missing special character after escape character \\");
return (Token) { .type=TOKEN_ERROR };
}
switch (p->s.src[p->s.cur]) {
case 'n': buf[len++] = '\n'; break;
case 't': buf[len++] = '\t'; break;
case 'r': buf[len++] = '\r'; break;
case '"': buf[len++] = '"'; break;
case '\'': buf[len++] = '\''; break;
case '\\': buf[len++] = '\\'; break;
case 'x':
{
if (p->s.len - p->s.cur < 3
|| !is_hex_digit(p->s.src[p->s.cur+1])
|| !is_hex_digit(p->s.src[p->s.cur+2]))
return (Token) { .type=TOKEN_ERROR };
buf[len++]
= (hex_digit_to_int(p->s.src[p->s.cur+1]) << 4)
| (hex_digit_to_int(p->s.src[p->s.cur+2]) << 0);
p->s.cur += 2;
}
break;
default:
parser_report(p, "Invalid character after escape character \\");
return (Token) { .type=TOKEN_ERROR };
}
p->s.cur++;
}
p->s.cur++;
return (Token) { .type=TOKEN_VALUE_STR, .sval=(String) { .ptr=buf, .len=len } };
}
if (consume_str(&p->s, S("<<"))) return (Token) { .type=TOKEN_OPER_SHOVEL };
if (consume_str(&p->s, S("=="))) return (Token) { .type=TOKEN_OPER_EQL };
if (consume_str(&p->s, S("!="))) return (Token) { .type=TOKEN_OPER_NQL };
if (consume_str(&p->s, S("<"))) return (Token) { .type=TOKEN_OPER_LSS };
if (consume_str(&p->s, S(">"))) return (Token) { .type=TOKEN_OPER_GRT };
if (consume_str(&p->s, S("+"))) return (Token) { .type=TOKEN_OPER_ADD };
if (consume_str(&p->s, S("-"))) return (Token) { .type=TOKEN_OPER_SUB };
if (consume_str(&p->s, S("*"))) return (Token) { .type=TOKEN_OPER_MUL };
if (consume_str(&p->s, S("/"))) return (Token) { .type=TOKEN_OPER_DIV };
if (consume_str(&p->s, S("%"))) return (Token) { .type=TOKEN_OPER_MOD };
if (consume_str(&p->s, S("="))) return (Token) { .type=TOKEN_OPER_ASS };
if (consume_str(&p->s, S("("))) return (Token) { .type=TOKEN_PAREN_OPEN };
if (consume_str(&p->s, S(")"))) return (Token) { .type=TOKEN_PAREN_CLOSE };
if (consume_str(&p->s, S("["))) return (Token) { .type=TOKEN_BRACKET_OPEN };
if (consume_str(&p->s, S("]"))) return (Token) { .type=TOKEN_BRACKET_CLOSE };
if (consume_str(&p->s, S("{"))) return (Token) { .type=TOKEN_CURLY_OPEN };
if (consume_str(&p->s, S("}"))) return (Token) { .type=TOKEN_CURLY_CLOSE };
if (consume_str(&p->s, S("."))) return (Token) { .type=TOKEN_DOT };
if (consume_str(&p->s, S(","))) return (Token) { .type=TOKEN_COMMA };
if (consume_str(&p->s, S(":"))) return (Token) { .type=TOKEN_COLON };
if (consume_str(&p->s, S("$"))) return (Token) { .type=TOKEN_DOLLAR };
parser_report(p, "Invalid character '%c'", c);
return (Token) { .type=TOKEN_ERROR };
}
static Token next_token_or_newline(Parser *p)
{
int peek = p->s.cur;
while (peek < p->s.len && is_space(p->s.src[peek]) && p->s.src[peek] != '\n')
peek++;
if (peek < p->s.len && p->s.src[peek] == '\n') {
p->s.cur = peek+1;
return (Token) { .type=TOKEN_NEWLINE };
}
return next_token(p);
}
enum {
IGNORE_GRT = 1 << 0,
IGNORE_LSS = 1 << 1,
IGNORE_DIV = 1 << 2,
};
static Node *parse_stmt(Parser *p, int opflags);
static Node *parse_expr(Parser *p, int opflags);
static Node *parse_html(Parser *p)
{
// NOTE: The first < was already consumed
Token t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "HTML tag doesn't start with a name");
return NULL;
}
String tagname = t.sval;
Node *attr_head;
Node **attr_tail = &attr_head;
bool no_body = false;
Scanner *s = &p->s;
for (;;) {
int off = s->cur;
bool quotes = false;
while (s->cur < s->len && s->src[s->cur] != '\\' && (quotes || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) {
if (s->src[s->cur] == '"')
quotes = !quotes;
s->cur++;
}
if (s->cur > off) {
Node *child = alloc_node(p);
if (child == NULL)
return NULL;
child->type = NODE_VALUE_STR;
child->sval = (String) { p->s.src + off, p->s.cur - off };
*attr_tail = child;
attr_tail = &child->next;
}
if (s->cur == s->len) {
ASSERT(0); // TODO
}
s->cur++;
if (s->src[s->cur-1] == '>')
break;
if (s->src[s->cur-1] == '/') {
while (s->cur < s->len && is_space(s->src[s->cur]))
s->cur++;
if (s->cur == s->len || s->src[s->cur] != '>') {
ASSERT(0); // TODO
}
s->cur++;
no_body = true;
break;
}
ASSERT(s->src[s->cur-1] == '\\');
Node *child = parse_stmt(p, IGNORE_GRT | IGNORE_DIV);
if (child == NULL)
return NULL;
*attr_tail = child;
attr_tail = &child->next;
}
*attr_tail = NULL;
Node *child_head;
Node **child_tail = &child_head;
if (no_body == false)
for (;;) {
int off = s->cur;
while (s->cur < s->len && s->src[s->cur] != '\\' && s->src[s->cur] != '<')
s->cur++;
if (s->cur > off) {
Node *child = alloc_node(p);
if (child == NULL)
return NULL;
child->type = NODE_VALUE_STR;
child->sval = (String) { p->s.src + off, p->s.cur - off };
*child_tail = child;
child_tail = &child->next;
}
if (s->cur == s->len) {
ASSERT(0); // TODO
}
s->cur++;
if (s->src[s->cur-1] == '<') {
Scanner saved = *s;
t = next_token(p);
if (t.type == TOKEN_OPER_DIV) {
t = next_token(p);
if (t.type != TOKEN_IDENT) {
ASSERT(0); // TODO
}
String closing_tagname = t.sval;
if (!streq(closing_tagname, tagname)) {
ASSERT(0); // TODO
}
t = next_token(p);
if (t.type != TOKEN_OPER_GRT) {
ASSERT(0);
}
break;
}
*s = saved;
Node *child = parse_html(p);
if (child == NULL)
return NULL;
*child_tail = child;
child_tail = &child->next;
} else {
Node *child = parse_stmt(p, IGNORE_LSS);
if (child == NULL)
return NULL;
*child_tail = child;
child_tail = &child->next;
}
}
*child_tail = NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_VALUE_HTML;
parent->html_tag = tagname;
parent->html_attr = attr_head;
parent->html_child = child_head;
parent->html_body = !no_body;
return parent;
}
static Node *parse_array(Parser *p)
{
// Left bracket already consumed
Node *head;
Node **tail = &head;
Scanner saved = p->s;
Token t = next_token(p);
if (t.type != TOKEN_BRACKET_CLOSE) {
p->s = saved;
for (;;) {
Node *child = parse_expr(p, 0);
if (child == NULL)
return NULL;
*tail = child;
tail = &child->next;
saved = p->s;
t = next_token(p);
if (t.type == TOKEN_COMMA) {
saved = p->s;
t = next_token(p);
}
if (t.type == TOKEN_BRACKET_CLOSE)
break;
p->s = saved;
}
}
*tail = NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_VALUE_ARRAY;
parent->child = head;
return parent;
}
static Node *parse_map(Parser *p)
{
// Left bracket already consumed
Node *head;
Node **tail = &head;
Scanner saved = p->s;
Token t = next_token(p);
if (t.type != TOKEN_CURLY_CLOSE) {
p->s = saved;
for (;;) {
Node *key;
saved = p->s;
t = next_token(p);
if (t.type == TOKEN_IDENT) {
key = alloc_node(p);
if (key == NULL)
return NULL;
key->type = NODE_VALUE_STR;
key->sval = t.sval;
} else {
p->s = saved;
key = parse_expr(p, 0);
if (key == NULL)
return NULL;
}
t = next_token(p);
if (t.type != TOKEN_COLON) {
parser_report(p, "Missing ':' after key inside map literal");
return NULL;
}
Node *child = parse_expr(p, 0);
if (child == NULL)
return NULL;
child->key = key;
*tail = child;
tail = &child->next;
saved = p->s;
t = next_token(p);
if (t.type == TOKEN_COMMA) {
saved = p->s;
t = next_token(p);
}
if (t.type == TOKEN_CURLY_CLOSE)
break;
p->s = saved;
}
}
*tail = NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_VALUE_MAP;
parent->child = head;
return parent;
}
static int precedence(Token t, int flags)
{
switch (t.type) {
case TOKEN_OPER_ASS:
return 1;
case TOKEN_OPER_SHOVEL:
if (flags & IGNORE_LSS)
return -1;
return 1;
case TOKEN_OPER_EQL:
case TOKEN_OPER_NQL:
return 2;
case TOKEN_OPER_LSS:
if (flags & IGNORE_LSS)
return -1;
return 2;
case TOKEN_OPER_GRT:
if (flags & IGNORE_GRT)
return -1;
return 2;
case TOKEN_OPER_ADD:
case TOKEN_OPER_SUB:
return 3;
case TOKEN_OPER_MUL:
case TOKEN_OPER_MOD:
return 4;
case TOKEN_OPER_DIV:
if (flags & IGNORE_DIV)
return -1;
return 4;
default:
break;
}
return -1;
}
static bool right_associative(Token t)
{
return t.type == TOKEN_OPER_ASS;
}
static Node *parse_atom(Parser *p)
{
Token t = next_token(p);
Node *ret;
switch (t.type) {
case TOKEN_OPER_ADD:
{
Node *child = parse_atom(p);
if (child == NULL)
return NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_OPER_POS;
parent->left = child;
ret = parent;
}
break;
case TOKEN_OPER_SUB:
{
Node *child = parse_atom(p);
if (child == NULL)
return NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_OPER_NEG;
parent->left = child;
ret = parent;
}
break;
case TOKEN_KWORD_LEN:
{
Node *child = parse_atom(p);
if (child == NULL)
return NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_OPER_LEN;
parent->left = child;
ret = parent;
}
break;
case TOKEN_IDENT:
{
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_VAR;
node->sval = t.sval;
ret = node;
}
break;
case TOKEN_VALUE_INT:
{
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_INT;
node->ival = t.ival;
ret = node;
}
break;
case TOKEN_VALUE_FLOAT:
{
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_FLOAT;
node->fval = t.fval;
ret = node;
}
break;
case TOKEN_VALUE_STR:
{
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_STR;
node->sval = t.sval;
ret = node;
}
break;
case TOKEN_KWORD_NONE:
{
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_NONE;
node->sval = t.sval;
ret = node;
}
break;
case TOKEN_KWORD_TRUE:
{
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_TRUE;
node->sval = t.sval;
ret = node;
}
break;
case TOKEN_KWORD_FALSE:
{
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_FALSE;
node->sval = t.sval;
ret = node;
}
break;
case TOKEN_OPER_LSS:
{
Node *node = parse_html(p);
if (node == NULL)
return NULL;
ret = node;
}
break;
case TOKEN_PAREN_OPEN:
{
Node *node = parse_expr(p, 0);
if (node == NULL)
return NULL;
Token t = next_token(p);
if (t.type != TOKEN_PAREN_CLOSE) {
parser_report(p, "Missing ')' after expression");
return NULL;
}
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_NESTED;
parent->left = node;
ret = parent;
}
break;
case TOKEN_BRACKET_OPEN:
{
Node *node = parse_array(p);
if (node == NULL)
return NULL;
ret = node;
}
break;
case TOKEN_CURLY_OPEN:
{
Node *node = parse_map(p);
if (node == NULL)
return NULL;
ret = node;
}
break;
case TOKEN_DOLLAR:
{
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Missing identifier after '$'");
return NULL;
}
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_VALUE_SYSVAR;
node->sval = t.sval;
ret = node;
}
break;
default:
{
parser_report(p, "Invalid token inside expression");
}
return NULL;
}
for (;;) {
Scanner saved = p->s;
t = next_token(p);
if (t.type == TOKEN_DOT) {
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Invalid token after '.' where an identifier was expected");
return NULL;
}
Node *child = alloc_node(p);
if (child == NULL)
return NULL;
child->type = NODE_VALUE_STR;
child->sval = t.sval;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_SELECT;
parent->left = ret;
parent->right = child;
ret = parent;
} else if (t.type == TOKEN_BRACKET_OPEN) {
Node *child = parse_expr(p, 0);
if (child == NULL)
return NULL;
t = next_token(p);
if (t.type != TOKEN_BRACKET_CLOSE) {
parser_report(p, "Missing token ']'");
return NULL;
}
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_SELECT;
parent->left = ret;
parent->right = child;
ret = parent;
} else if (t.type == TOKEN_PAREN_OPEN && (ret->type == NODE_VALUE_VAR || ret->type == NODE_VALUE_SYSVAR)) {
Node *arg_head;
Node **arg_tail = &arg_head;
Scanner saved = p->s;
t = next_token(p);
if (t.type != TOKEN_PAREN_CLOSE) {
p->s = saved;
for (;;) {
Node *argval = parse_expr(p, 0);
if (argval == NULL)
return NULL;
*arg_tail = argval;
arg_tail = &argval->next;
t = next_token(p);
if (t.type == TOKEN_PAREN_CLOSE)
break;
if (t.type != TOKEN_COMMA) {
parser_report(p, "Expected ',' after argument in procedure call");
return NULL;
}
}
}
*arg_tail = NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_PROCEDURE_CALL;
parent->left = ret;
parent->right = arg_head;
ret = parent;
} else {
p->s = saved;
break;
}
}
return ret;
}
static Node *parse_expr_inner(Parser *p, Node *left, int min_prec, int flags)
{
for (;;) {
Scanner saved = p->s;
Token t1 = next_token_or_newline(p);
if (precedence(t1, flags) < min_prec) {
p->s = saved;
break;
}
Node *right = parse_atom(p);
if (right == NULL)
return NULL;
for (;;) {
saved = p->s;
Token t2 = next_token_or_newline(p);
int p1 = precedence(t1, flags);
int p2 = precedence(t2, flags);
p->s = saved;
if (p2 < 0)
break;
if (p2 <= p1 && (p1 != p2 || !right_associative(t2)))
break;
right = parse_expr_inner(p, right, p1 + (p2 > p1), flags);
if (right == NULL)
return NULL;
}
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->left = left;
parent->right = right;
switch (t1.type) {
case TOKEN_OPER_ASS: parent->type = NODE_OPER_ASS; break;
case TOKEN_OPER_EQL: parent->type = NODE_OPER_EQL; break;
case TOKEN_OPER_NQL: parent->type = NODE_OPER_NQL; break;
case TOKEN_OPER_LSS: parent->type = NODE_OPER_LSS; break;
case TOKEN_OPER_GRT: parent->type = NODE_OPER_GRT; break;
case TOKEN_OPER_ADD: parent->type = NODE_OPER_ADD; break;
case TOKEN_OPER_SUB: parent->type = NODE_OPER_SUB; break;
case TOKEN_OPER_MUL: parent->type = NODE_OPER_MUL; break;
case TOKEN_OPER_DIV: parent->type = NODE_OPER_DIV; break;
case TOKEN_OPER_MOD: parent->type = NODE_OPER_MOD; break;
case TOKEN_OPER_SHOVEL: parent->type = NODE_OPER_SHOVEL; break;
default:
parser_report(p, "Operator not implemented");
return NULL;
}
left = parent;
}
return left;
}
static Node *parse_expr(Parser *p, int flags)
{
Node *left = parse_atom(p);
if (left == NULL)
return NULL;
return parse_expr_inner(p, left, 0, flags);
}
static Node *parse_expr_stmt(Parser *p, int opflags)
{
Node *e = parse_expr(p, opflags);
if (e == NULL)
return NULL;
return e;
}
static Node *parse_ifelse_stmt(Parser *p, int opflags)
{
Token t = next_token(p);
if (t.type != TOKEN_KWORD_IF) {
parser_report(p, "Missing 'if' keyword before if statement");
return NULL;
}
Node *cond = parse_expr(p, 0);
if (cond == NULL)
return NULL;
t = next_token(p);
if (t.type != TOKEN_COLON) {
parser_report(p, "Missing ':' after if condition");
return NULL;
}
Node *if_stmt = parse_stmt(p, opflags);
if (if_stmt == NULL)
return NULL;
Scanner saved = p->s;
t = next_token(p);
Node *else_stmt = NULL;
if (t.type == TOKEN_KWORD_ELSE) {
else_stmt = parse_stmt(p, opflags);
if (else_stmt == NULL)
return NULL;
} else {
p->s = saved;
}
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_IFELSE;
parent->if_cond = cond;
parent->if_branch1 = if_stmt;
parent->if_branch2 = else_stmt;
return parent;
}
static Node *parse_for_stmt(Parser *p, int opflags)
{
Token t = next_token(p);
if (t.type != TOKEN_KWORD_FOR) {
parser_report(p, "Missing 'for' keyword at the start of a for statement");
return NULL;
}
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Missing iteraion variable name in for statement");
return NULL;
}
String var1 = t.sval;
t = next_token(p);
String var2 = S("");
if (t.type == TOKEN_COMMA) {
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Missing iteration variable name after ',' in for statement");
return NULL;
}
var2 = t.sval;
t = next_token(p);
}
if (t.type != TOKEN_KWORD_IN) {
parser_report(p, "Missing 'in' keyword after iteration variable name in for statement");
return NULL;
}
Node *set = parse_expr(p, 0);
if (set == NULL)
return NULL;
t = next_token(p);
if (t.type != TOKEN_COLON) {
parser_report(p, "Missing ':' after for statement set expression");
return NULL;
}
Node *body = parse_stmt(p, opflags);
if (body == NULL)
return NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_FOR;
parent->left = body;
parent->for_var1 = var1;
parent->for_var2 = var2;
parent->for_set = set;
return parent;
}
static Node *parse_while_stmt(Parser *p, int opflags)
{
Token t = next_token(p);
if (t.type != TOKEN_KWORD_WHILE) {
parser_report(p, "Missing keyword 'while' at the start of a while statement");
return NULL;
}
Node *cond = parse_expr(p, 0);
if (cond == NULL)
return NULL;
t = next_token(p);
if (t.type != TOKEN_COLON) {
parser_report(p, "Missing token ':' after while statement condition");
return NULL;
}
Node *stmt = parse_stmt(p, opflags);
if (stmt == NULL)
return NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_WHILE;
parent->left = stmt;
parent->while_cond = cond;
parent->while_body = stmt;
return parent;
}
static Node *parse_compound_stmt(Parser *p, bool global)
{
if (!global) {
Token t = next_token(p);
if (t.type != TOKEN_CURLY_OPEN) {
parser_report(p, "Missing '{' at the start of a compound statement");
return NULL;
}
}
Node *head;
Node **tail = &head;
for (;;) {
Scanner saved = p->s;
Token t = next_token(p);
if (!global) {
if (t.type == TOKEN_CURLY_CLOSE)
break;
} else {
if (t.type == TOKEN_END)
break;
}
p->s = saved;
Node *node = parse_stmt(p, 0);
if (node == NULL)
return NULL;
*tail = node;
tail = &node->next;
}
*tail = NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = global ? NODE_GLOBAL : NODE_COMPOUND;
parent->left = head;
return parent;
}
static Node *parse_proc_decl(Parser *p, int opflags)
{
Token t = next_token(p);
if (t.type != TOKEN_KWORD_PROCEDURE) {
parser_report(p, "Missing keyword 'procedure' at the start of a procedure declaration");
return NULL;
}
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Missing procedure name after 'procedure' keyword");
return NULL;
}
String name = t.sval;
t = next_token(p);
if (t.type != TOKEN_PAREN_OPEN) {
parser_report(p, "Missing '(' after procedure name in declaration");
return NULL;
}
Node *arg_head;
Node **arg_tail = &arg_head;
Scanner saved = p->s;
t = next_token(p);
if (t.type != TOKEN_PAREN_CLOSE) {
p->s = saved;
for (;;) {
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Missing argument name in procedure declaration");
return NULL;
}
String argname = t.sval;
Node *node = alloc_node(p);
if (node == NULL)
return NULL;
node->type = NODE_PROCEDURE_ARG;
node->sval = argname;
*arg_tail = node;
arg_tail = &node->next;
Scanner saved = p->s;
t = next_token(p);
if (t.type == TOKEN_COMMA) {
saved = p->s;
t = next_token(p);
}
if (t.type == TOKEN_PAREN_CLOSE)
break;
p->s = saved;
}
}
*arg_tail = NULL;
Node *body = parse_stmt(p, opflags);
if (body == NULL)
return NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_PROCEDURE_DECL;
parent->proc_name = name;
parent->proc_args = arg_head;
parent->proc_body = body;
return parent;
}
static Node *parse_var_decl(Parser *p, int opflags)
{
Token t = next_token(p);
if (t.type != TOKEN_KWORD_LET) {
parser_report(p, "Missing keyword 'let' at the start of a variable declaration");
return NULL;
}
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Missing variable name after 'let' keyword");
return NULL;
}
String name = t.sval;
Scanner saved = p->s;
t = next_token(p);
Node *value;
if (t.type == TOKEN_OPER_ASS) {
value = parse_expr(p, opflags);
if (value == NULL)
return NULL;
} else {
p->s = saved;
value = NULL;
}
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_VAR_DECL;
parent->var_name = name;
parent->var_value = value;
return parent;
}
static Node *parse_include_stmt(Parser *p)
{
Token t = next_token(p);
if (t.type != TOKEN_KWORD_INCLUDE) {
parser_report(p, "Missing keyword 'include' at the start of an include statement");
return NULL;
}
t = next_token(p);
if (t.type != TOKEN_VALUE_STR) {
parser_report(p, "Missing file path string after 'include' keyword");
return NULL;
}
String path = t.sval;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_INCLUDE;
parent->include_path = path;
parent->include_root = NULL;
*p->include_tail = parent;
p->include_tail = &parent->include_next;
return parent;
}
static Node *parse_stmt(Parser *p, int opflags)
{
Scanner saved = p->s;
Token t = next_token(p);
p->s = saved;
switch (t.type) {
case TOKEN_KWORD_INCLUDE:
return parse_include_stmt(p);
case TOKEN_KWORD_PROCEDURE:
return parse_proc_decl(p, opflags);
case TOKEN_KWORD_LET:
return parse_var_decl(p, opflags);
case TOKEN_KWORD_IF:
return parse_ifelse_stmt(p, opflags);
case TOKEN_KWORD_WHILE:
return parse_while_stmt(p, opflags);
case TOKEN_KWORD_FOR:
return parse_for_stmt(p, opflags);
case TOKEN_CURLY_OPEN:
return parse_compound_stmt(p, false);
default:
break;
}
return parse_expr_stmt(p, opflags);
}
static void write_node(Writer *w, Node *node)
{
switch (node->type) {
case NODE_VALUE_NONE : write_text(w, S("none")); break;
case NODE_VALUE_TRUE : write_text(w, S("true")); break;
case NODE_VALUE_FALSE: write_text(w, S("false")); break;
case NODE_NESTED:
write_text(w, S("(nested "));
write_node(w, node->left);
write_text(w, S(")"));
break;
case NODE_COMPOUND:
{
write_text(w, S("(compound "));
Node *cur = node->left;
while (cur) {
write_node(w, cur);
cur = cur->next;
if (cur)
write_text(w, S(" "));
}
write_text(w, S(")"));
}
break;
case NODE_GLOBAL:
{
write_text(w, S("(global "));
Node *cur = node->left;
while (cur) {
write_node(w, cur);
cur = cur->next;
if (cur)
write_text(w, S(" "));
}
write_text(w, S(")"));
}
break;
case NODE_OPER_LEN:
write_text(w, S("(len "));
write_node(w, node->left);
write_text(w, S(")"));
break;
case NODE_OPER_POS:
write_text(w, S("(+"));
write_node(w, node->left);
write_text(w, S(")"));
break;
case NODE_OPER_NEG:
write_text(w, S("("));
write_text(w, S("-"));
write_node(w, node->left);
write_text(w, S(")"));
break;
case NODE_OPER_ASS:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("="));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_EQL:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("=="));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_NQL:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("!="));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_LSS:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("<"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_GRT:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S(">"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_ADD:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("+"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_SUB:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("-"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_MUL:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("*"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_DIV:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("/"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_OPER_MOD:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("%%"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_VALUE_INT:
write_text_s64(w, node->ival);
break;
case NODE_VALUE_FLOAT:
write_text_f64(w, node->fval);
break;
case NODE_VALUE_STR:
write_text(w, S("\""));
write_text(w, node->sval);
write_text(w, S("\""));
break;
case NODE_VALUE_VAR:
write_text(w, node->sval);
break;
case NODE_VALUE_SYSVAR:
write_text(w, S("$"));
write_text(w, node->sval);
break;
case NODE_IFELSE:
write_text(w, S("(if "));
write_node(w, node->if_cond);
write_text(w, S(" "));
write_node(w, node->if_branch1);
if (node->if_branch2) {
write_text(w, S(" else "));
write_node(w, node->if_branch2);
}
write_text(w, S(")"));
break;
case NODE_WHILE:
write_text(w, S("(while "));
write_node(w, node->while_cond);
write_text(w, S(" "));
write_node(w, node->while_body);
write_text(w, S(")"));
break;
case NODE_VALUE_HTML:
{
write_text(w, S("(html "));
write_text(w, node->html_tag);
Node *child = node->html_child;
while (child) {
write_text(w, S(" "));
write_node(w, child);
child = child->next;
}
write_text(w, S(")"));
}
break;
case NODE_FOR:
write_text(w, S("(for "));
write_text(w, node->for_var1);
if (node->for_var2.len > 0) {
write_text(w, S(", "));
write_text(w, node->for_var2);
}
write_text(w, S(" in "));
write_node(w, node->for_set);
write_text(w, S(": "));
write_node(w, node->left);
break;
case NODE_SELECT:
write_node(w, node->left);
write_text(w, S("["));
write_node(w, node->right);
write_text(w, S("]"));
break;
case NODE_VALUE_ARRAY:
{
write_text(w, S("["));
Node *child = node->child;
while (child) {
write_node(w, child);
write_text(w, S(", "));
child = child->next;
}
write_text(w, S("]"));
}
break;
case NODE_VALUE_MAP:
{
write_text(w, S("{"));
Node *child = node->child;
while (child) {
write_node(w, child->key);
write_text(w, S(": "));
write_node(w, child);
write_text(w, S(", "));
child = child->next;
}
write_text(w, S("}"));
}
break;
case NODE_PROCEDURE_DECL:
{
write_text(w, S("(proc "));
write_text(w, node->proc_name);
write_text(w, S("("));
Node *arg = node->proc_args;
while (arg) {
write_node(w, arg);
arg = arg->next;
if (arg)
write_text(w, S(", "));
}
write_text(w, S(")"));
write_node(w, node->proc_body);
}
break;
case NODE_PROCEDURE_ARG:
write_text(w, node->sval);
break;
case NODE_PROCEDURE_CALL:
{
write_node(w, node->left);
write_text(w, S("("));
Node *arg = node->right;
while (arg) {
write_node(w, arg);
arg = arg->next;
if (arg)
write_text(w, S(", "));
}
write_text(w, S(")"));
}
break;
case NODE_VAR_DECL:
write_text(w, S("(let "));
write_text(w, node->var_name);
if (node->var_value) {
write_text(w, S(" = "));
write_node(w, node->var_value);
}
write_text(w, S(")"));
break;
case NODE_INCLUDE:
write_text(w, S("include \""));
write_text(w, node->include_path);
write_text(w, S("\""));
break;
}
}
static ParseResult parse(String src, WL_Arena *arena, char *errbuf, int errmax)
{
Parser p = {
.s={ src.ptr, src.len, 0 },
.arena=arena,
.errbuf=errbuf,
.errmax=errmax,
.errlen=0,
};
p.include_tail = &p.include_head;
Node *node = parse_compound_stmt(&p, true);
if (node == NULL)
return (ParseResult) { .node=NULL, .includes=NULL, .errlen=p.errlen };
*p.include_tail = NULL;
return (ParseResult) { .node=node, .includes=p.include_head, .errlen=-1 };
}
/////////////////////////////////////////////////////////////////////////
// CODEGEN
/////////////////////////////////////////////////////////////////////////
enum {
OPCODE_NOPE,
OPCODE_JUMP,
OPCODE_JIFP,
OPCODE_OUTPUT,
OPCODE_SYSVAR,
OPCODE_SYSCALL,
OPCODE_CALL,
OPCODE_RET,
OPCODE_GROUP,
OPCODE_PACK,
OPCODE_GPOP,
OPCODE_FOR,
OPCODE_EXIT,
OPCODE_VARS,
OPCODE_POP,
OPCODE_SETV,
OPCODE_PUSHV,
OPCODE_PUSHI,
OPCODE_PUSHF,
OPCODE_PUSHS,
OPCODE_PUSHA,
OPCODE_PUSHM,
OPCODE_PUSHN,
OPCODE_PUSHT,
OPCODE_PUSHFL,
OPCODE_LEN,
OPCODE_NEG,
OPCODE_EQL,
OPCODE_NQL,
OPCODE_LSS,
OPCODE_GRT,
OPCODE_ADD,
OPCODE_SUB,
OPCODE_MUL,
OPCODE_DIV,
OPCODE_MOD,
OPCODE_APPEND,
OPCODE_INSERT1,
OPCODE_INSERT2,
OPCODE_SELECT,
};
typedef struct UnpatchedCall UnpatchedCall;
struct UnpatchedCall {
UnpatchedCall *next;
String name;
int off;
};
typedef enum {
SYMBOL_VARIABLE,
SYMBOL_PROCEDURE,
} SymbolType;
typedef struct {
SymbolType type;
String name;
bool cnst;
int off;
} Symbol;
typedef enum {
SCOPE_IF,
SCOPE_ELSE,
SCOPE_FOR,
SCOPE_WHILE,
SCOPE_PROC,
SCOPE_COMPOUND,
SCOPE_GLOBAL,
SCOPE_ASSIGNMENT,
} ScopeType;
typedef struct {
ScopeType type;
int idx_syms;
int max_vars;
UnpatchedCall *calls;
} Scope;
#define MAX_SYMBOLS 1024
#define MAX_SCOPES 128
#define MAX_UNPATCHED_CALLS 32
typedef struct {
Writer code;
Writer data;
int num_scopes;
Scope scopes[MAX_SCOPES];
int num_syms;
Symbol syms[MAX_SYMBOLS];
UnpatchedCall *free_list_calls;
UnpatchedCall calls[MAX_UNPATCHED_CALLS];
bool err;
char *errmsg;
int errcap;
int data_off;
} Codegen;
static void cg_report(Codegen *cg, char *fmt, ...)
{
if (cg->err) return;
va_list args;
va_start(args, fmt);
int len = vsnprintf(cg->errmsg, cg->errcap, fmt, args);
va_end(args);
if (len > cg->errcap)
len = cg->errcap-1;
cg->errmsg[len] = '\0';
cg->err = true;
}
static int cg_write_u8(Codegen *cg, uint8_t x)
{
if (cg->err) return -1;
int off = cg->code.len;
write_raw_u8(&cg->code, x);
return off;
}
static int cg_write_u32(Codegen *cg, uint32_t x)
{
if (cg->err) return -1;
int off = cg->code.len;
write_raw_u32(&cg->code, x);
return off;
}
static int cg_write_s64(Codegen *cg, int64_t x)
{
if (cg->err) return -1;
int off = cg->code.len;
write_raw_s64(&cg->code, x);
return off;
}
static int cg_write_f64(Codegen *cg, double x)
{
if (cg->err) return -1;
int off = cg->code.len;
write_raw_f64(&cg->code, x);
return off;
}
static void cg_write_str(Codegen *cg, String x)
{
if (cg->err) return;
int off = cg->data.len;
write_text(&cg->data, x);
write_raw_u32(&cg->code, off);
write_raw_u32(&cg->code, x.len);
}
static void cg_patch_u8(Codegen *cg, int off, uint8_t x)
{
if (cg->err) return;
patch_mem(&cg->code, &x, off, SIZEOF(x));
}
static void cg_patch_u32(Codegen *cg, int off, uint32_t x)
{
if (cg->err) return;
patch_mem(&cg->code, &x, off, SIZEOF(x));
}
static uint32_t cg_current_offset(Codegen *cg)
{
return cg->code.len;
}
int count_nodes(Node *head)
{
int n = 0;
Node *node = head;
while (node) {
n++;
node = node->next;
}
return n;
}
static Scope *parent_scope(Codegen *cg)
{
ASSERT(cg->num_scopes > 0);
int parent = cg->num_scopes-1;
while (cg->scopes[parent].type != SCOPE_PROC && cg->scopes[parent].type != SCOPE_GLOBAL)
parent--;
return &cg->scopes[parent];
}
static bool inside_assignment(Codegen *cg)
{
ASSERT(cg->num_scopes > 0);
int parent = cg->num_scopes-1;
while (cg->scopes[parent].type != SCOPE_PROC
&& cg->scopes[parent].type != SCOPE_GLOBAL
&& cg->scopes[parent].type != SCOPE_ASSIGNMENT)
parent--;
return cg->scopes[parent].type == SCOPE_ASSIGNMENT;
}
static int count_function_vars(Codegen *cg)
{
int n = 0;
Scope *scope = parent_scope(cg);
for (int i = scope->idx_syms; i < cg->num_syms; i++)
if (cg->syms[i].type == SYMBOL_VARIABLE)
n++;
return n;
}
static Symbol *cg_find_symbol(Codegen *cg, String name, bool local)
{
if (cg->err) return NULL;
if (name.len == 0) return NULL;
ASSERT(cg->num_scopes > 0);
Scope *scope = local ? &cg->scopes[cg->num_scopes-1] : parent_scope(cg);
for (int i = cg->num_syms-1; i >= scope->idx_syms; i--)
if (streq(cg->syms[i].name, name))
return &cg->syms[i];
return NULL;
}
static int cg_declare_variable(Codegen *cg, String name, bool cnst)
{
if (cg->err) return -1;
Symbol *sym = cg_find_symbol(cg, name, true);
if (sym) {
cg_report(cg, "Variable declared twice");
return -1;
}
if (cg->num_syms == MAX_SYMBOLS) {
cg_report(cg, "Symbol count limit reached");
return -1;
}
int off = count_function_vars(cg);
Scope *parent = parent_scope(cg);
parent->max_vars = MAX(parent->max_vars, off+1);
cg->syms[cg->num_syms++] = (Symbol) {
.type = SYMBOL_VARIABLE,
.name = name,
.cnst = cnst,
.off = off,
};
return off;
}
static void cg_declare_procedure(Codegen *cg, String name, int off)
{
if (cg->err) return;
Symbol *sym = cg_find_symbol(cg, name, true);
if (sym) {
cg_report(cg, "Procedure declared twice");
return;
}
if (cg->num_syms == MAX_SYMBOLS) {
cg_report(cg, "Symbol count limit reached");
return;
}
cg->syms[cg->num_syms++] = (Symbol) {
.type = SYMBOL_PROCEDURE,
.name = name,
.cnst = true,
.off = off,
};
}
static void cg_push_scope(Codegen *cg, ScopeType type)
{
if (cg->err) return;
if (cg->num_scopes == MAX_SCOPES) {
cg_report(cg, "Scope limit reached");
return;
}
Scope *scope = &cg->scopes[cg->num_scopes++];
scope->type = type;
scope->idx_syms = cg->num_syms;
scope->max_vars = 0;
scope->calls = NULL;
}
static void cg_pop_scope(Codegen *cg)
{
if (cg->err) return;
ASSERT(cg->num_scopes > 0);
Scope *scope = &cg->scopes[cg->num_scopes-1];
ASSERT(scope->type == SCOPE_PROC || scope->type == SCOPE_GLOBAL || scope->max_vars == 0);
Scope *parent_scope = NULL;
if (cg->num_scopes > 1)
parent_scope = &cg->scopes[cg->num_scopes-2];
while (scope->calls) {
UnpatchedCall *call = scope->calls;
scope->calls = call->next;
ASSERT(call - cg->calls >= 0 && call - cg->calls < MAX_UNPATCHED_CALLS);
Symbol *sym = cg_find_symbol(cg, call->name, true);
if (sym == NULL) {
if (parent_scope == NULL) {
cg_report(cg, "Undefined function '%.*s'",
scope->calls->name.len,
scope->calls->name.ptr);
return;
}
call->next = parent_scope->calls;
parent_scope->calls = call;
continue;
}
if (sym->type != SYMBOL_PROCEDURE) {
cg_report(cg, "Symbol '%.*s' is not a procedure", call->name.len, call->name.ptr);
return;
}
cg_patch_u32(cg, call->off, sym->off);
call->next = cg->free_list_calls;
cg->free_list_calls = call;
// TODO: remove
ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS));
}
cg->num_syms = scope->idx_syms;
cg->num_scopes--;
}
static void cg_append_unpatched_call(Codegen *cg, String name, int p)
{
if (cg->err) return;
if (cg->free_list_calls == NULL) {
cg_report(cg, "Out of memory");
return;
}
UnpatchedCall *call = cg->free_list_calls;
cg->free_list_calls = call->next;
ASSERT(call - cg->calls >= 0 && call - cg->calls < MAX_UNPATCHED_CALLS);
call->name = name;
call->off = p;
call->next = NULL;
ASSERT(cg->num_scopes > 0);
Scope *scope = &cg->scopes[cg->num_scopes-1];
call->next = scope->calls;
scope->calls = call;
}
static bool cg_global_scope(Codegen *cg)
{
Scope *scope = parent_scope(cg);
return scope->type == SCOPE_GLOBAL;
}
static void cg_flush_pushs(Codegen *cg)
{
if (cg->data_off != -1) {
if (cg->data_off < cg->data.len) {
cg_write_u8(cg, OPCODE_PUSHS);
cg_write_u32(cg, cg->data_off);
cg_write_u32(cg, cg->data.len - cg->data_off);
}
cg->data_off = -1;
}
}
static int cg_write_opcode(Codegen *cg, uint8_t opcode)
{
ASSERT(opcode != OPCODE_PUSHS);
cg_flush_pushs(cg);
return cg_write_u8(cg, opcode);
}
static void cg_write_pushs(Codegen *cg, String str, bool dont_group)
{
if (dont_group) {
cg_flush_pushs(cg);
cg_write_u8(cg, OPCODE_PUSHS);
cg_write_str(cg, str);
} else {
if (cg->data_off == -1)
cg->data_off = cg->data.len;
write_raw_mem(&cg->data, str.ptr, str.len);
}
}
static void walk_node(Codegen *cg, Node *node);
static void walk_expr_node(Codegen *cg, Node *node, bool one)
{
// TODO: remove
ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS));
switch (node->type) {
case NODE_NESTED:
walk_expr_node(cg, node->left, one);
break;
case NODE_OPER_LEN:
walk_expr_node(cg, node->left, true);
cg_write_opcode(cg, OPCODE_LEN);
break;
case NODE_OPER_POS:
walk_expr_node(cg, node->left, one);
break;
case NODE_OPER_NEG:
walk_expr_node(cg, node->left, true);
cg_write_opcode(cg, OPCODE_NEG);
break;
case NODE_OPER_ASS:
{
Node *dst = node->left;
Node *src = node->right;
if (dst->type == NODE_VALUE_VAR) {
String name = dst->sval;
Symbol *sym = cg_find_symbol(cg, name, false);
if (sym == NULL) {
cg_report(cg, "Write to undeclared variable");
return;
}
if (sym->type == SYMBOL_PROCEDURE) {
cg_report(cg, "Symbol is not a variable");
return;
}
if (sym->cnst) {
cg_report(cg, "Variable is constant");
return;
}
cg_push_scope(cg, SCOPE_ASSIGNMENT);
walk_expr_node(cg, src, true);
cg_pop_scope(cg);
cg_write_opcode(cg, OPCODE_SETV);
cg_write_u8(cg, sym->off);
if (!one)
cg_write_opcode(cg, OPCODE_POP);
} else if (dst->type == NODE_SELECT) {
cg_push_scope(cg, SCOPE_ASSIGNMENT);
walk_expr_node(cg, src, true);
cg_pop_scope(cg);
walk_expr_node(cg, dst->left, true);
walk_expr_node(cg, dst->right, true);
cg_write_opcode(cg, OPCODE_INSERT2);
if (!one)
cg_write_opcode(cg, OPCODE_POP);
} else {
cg_report(cg, "Assignment left side can't be assigned to");
return;
}
}
break;
case NODE_OPER_SHOVEL:
{
walk_expr_node(cg, node->left, true);
cg_push_scope(cg, SCOPE_ASSIGNMENT);
walk_expr_node(cg, node->right, true);
cg_pop_scope(cg);
cg_write_opcode(cg, OPCODE_APPEND);
if (!one)
cg_write_opcode(cg, OPCODE_POP);
}
break;
case NODE_OPER_EQL:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_EQL);
break;
case NODE_OPER_NQL:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_NQL);
break;
case NODE_OPER_LSS:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_LSS);
break;
case NODE_OPER_GRT:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_GRT);
break;
case NODE_OPER_ADD:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_ADD);
break;
case NODE_OPER_SUB:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_SUB);
break;
case NODE_OPER_MUL:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_MUL);
break;
case NODE_OPER_DIV:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_DIV);
break;
case NODE_OPER_MOD:
walk_expr_node(cg, node->left, true);
walk_expr_node(cg, node->right, true);
cg_write_opcode(cg, OPCODE_MOD);
break;
case NODE_VALUE_INT:
cg_write_opcode(cg, OPCODE_PUSHI);
cg_write_s64(cg, node->ival);
break;
case NODE_VALUE_FLOAT:
cg_write_opcode(cg, OPCODE_PUSHF);
cg_write_f64(cg, node->fval);
break;
case NODE_VALUE_STR:
cg_write_pushs(cg, node->sval, one);
break;
case NODE_VALUE_NONE:
cg_write_opcode(cg, OPCODE_PUSHN);
break;
case NODE_VALUE_TRUE:
cg_write_opcode(cg, OPCODE_PUSHT);
break;
case NODE_VALUE_FALSE:
cg_write_opcode(cg, OPCODE_PUSHFL);
break;
case NODE_VALUE_VAR:
{
String name = node->sval;
Symbol *sym = cg_find_symbol(cg, name, false);
if (sym == NULL) {
cg_report(cg, "Access to undeclared variable '%.*s'", name.len, name.ptr);
return;
}
if (sym->type == SYMBOL_PROCEDURE) {
cg_report(cg, "Symbol is not a variable");
return;
}
cg_write_opcode(cg, OPCODE_PUSHV);
cg_write_u8(cg, sym->off);
}
break;
case NODE_VALUE_SYSVAR:
cg_write_opcode(cg, OPCODE_SYSVAR);
cg_write_str(cg, node->sval);
break;
case NODE_VALUE_HTML:
{
if (one)
cg_write_opcode(cg, OPCODE_GROUP);
cg_write_pushs(cg, S("<"), false);
cg_write_pushs(cg, node->html_tag, false);
Node *child = node->html_attr;
while (child) {
walk_node(cg, child);
child = child->next;
}
if (!node->html_body) {
cg_write_pushs(cg, S("/>"), false);
} else {
cg_write_pushs(cg, S(">"), false);
Node *child = node->html_child;
while (child) {
walk_node(cg, child);
child = child->next;
}
cg_write_pushs(cg, S("</"), false);
cg_write_pushs(cg, node->html_tag, false);
cg_write_pushs(cg, S(">"), false);
}
if (one)
cg_write_opcode(cg, OPCODE_PACK);
}
break;
case NODE_VALUE_ARRAY:
{
cg_write_opcode(cg, OPCODE_PUSHA);
cg_write_u32(cg, count_nodes(node->child));
Node *child = node->child;
while (child) {
walk_expr_node(cg, child, true);
cg_write_opcode(cg, OPCODE_APPEND);
child = child->next;
}
}
break;
case NODE_VALUE_MAP:
{
cg_write_opcode(cg, OPCODE_PUSHM);
cg_write_u32(cg, count_nodes(node->child));
Node *child = node->child;
while (child) {
walk_expr_node(cg, child, true);
walk_expr_node(cg, child->key, true);
cg_write_opcode(cg, OPCODE_INSERT1);
child = child->next;
}
}
break;
case NODE_SELECT:
{
Node *set = node->left;
Node *key = node->right;
walk_expr_node(cg, set, true);
walk_expr_node(cg, key, true);
cg_write_opcode(cg, OPCODE_SELECT);
}
break;
case NODE_PROCEDURE_CALL:
{
if (one)
cg_write_opcode(cg, OPCODE_GROUP);
int count = 0;
Node *arg = node->right;
while (arg) {
walk_expr_node(cg, arg, true);
count++;
arg = arg->next;
}
Node *proc = node->left;
if (proc->type == NODE_VALUE_VAR) {
cg_write_opcode(cg, OPCODE_CALL);
cg_write_u8(cg, count);
int p = cg_write_u32(cg, 0);
cg_append_unpatched_call(cg, proc->sval, p);
} else {
ASSERT(proc->type == NODE_VALUE_SYSVAR);
cg_write_opcode(cg, OPCODE_SYSCALL);
cg_write_u8(cg, count);
cg_write_str(cg, proc->sval);
}
if (one)
cg_write_opcode(cg, OPCODE_PACK);
}
break;
default:
UNREACHABLE;
}
}
static void walk_node(Codegen *cg, Node *node)
{
// TODO: remove
ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS));
switch (node->type) {
case NODE_GLOBAL:
for (Node *child = node->left;
child; child = child->next) {
walk_node(cg, child);
}
break;
case NODE_COMPOUND:
cg_push_scope(cg, SCOPE_COMPOUND);
for (Node *child = node->left;
child; child = child->next)
walk_node(cg, child);
cg_pop_scope(cg);
break;
case NODE_PROCEDURE_DECL:
{
cg_push_scope(cg, SCOPE_PROC);
cg_write_opcode(cg, OPCODE_JUMP);
int off0 = cg_write_u32(cg, 0);
#define MAX_ARGS 128
int num_args = 0;
Node *args[MAX_ARGS];
Node *arg = node->proc_args;
while (arg) {
if (num_args == MAX_ARGS) {
cg_report(cg, "Procedure argument limit reached");
return;
}
args[num_args++] = arg;
arg = arg->next;
}
for (int i = num_args-1; i >= 0; i--)
cg_declare_variable(cg, args[i]->sval, false);
int off1 = cg_write_opcode(cg, OPCODE_VARS);
int off2 = cg_write_u8(cg, 0);
walk_node(cg, node->proc_body);
cg_write_opcode(cg, OPCODE_RET);
cg_patch_u8 (cg, off2, cg->scopes[cg->num_scopes-1].max_vars);
cg_patch_u32(cg, off0, cg_current_offset(cg));
cg_pop_scope(cg);
cg_declare_procedure(cg, node->proc_name, off1);
}
break;
case NODE_VAR_DECL:
{
int off = cg_declare_variable(cg, node->var_name, false);
if (node->var_value) {
cg_push_scope(cg, SCOPE_ASSIGNMENT);
walk_expr_node(cg, node->var_value, true);
cg_pop_scope(cg);
} else
cg_write_opcode(cg, OPCODE_PUSHN);
cg_write_opcode(cg, OPCODE_SETV);
cg_write_u8(cg, off);
cg_write_opcode(cg, OPCODE_POP);
}
break;
case NODE_IFELSE:
{
// If there is no else branch:
//
// <cond>
// JIFP end
// <left>
// end:
// ...
//
// If there is:
//
// <cond>
// JIFP else
// <left>
// JUMP end
// else:
// <right>
// end:
// ...
if (node->if_branch2) {
walk_expr_node(cg, node->if_cond, true);
cg_write_opcode(cg, OPCODE_JIFP);
int p1 = cg_write_u32(cg, 0);
cg_push_scope(cg, SCOPE_IF);
walk_node(cg, node->if_branch1);
cg_pop_scope(cg);
cg_write_opcode(cg, OPCODE_JUMP);
int p2 = cg_write_u32(cg, 0);
cg_flush_pushs(cg);
cg_patch_u32(cg, p1, cg_current_offset(cg));
cg_push_scope(cg, SCOPE_ELSE);
walk_node(cg, node->if_branch2);
cg_pop_scope(cg);
cg_flush_pushs(cg);
cg_patch_u32(cg, p2, cg_current_offset(cg));
} else {
walk_expr_node(cg, node->if_cond, true);
cg_write_opcode(cg, OPCODE_JIFP);
int p1 = cg_write_u32(cg, 0);
cg_push_scope(cg, SCOPE_IF);
walk_node(cg, node->if_branch1);
cg_pop_scope(cg);
cg_flush_pushs(cg);
cg_patch_u32(cg, p1, cg_current_offset(cg));
}
}
break;
case NODE_FOR:
{
cg_push_scope(cg, SCOPE_FOR);
int var_1 = cg_declare_variable(cg, node->for_var1, false);
int var_2 = cg_declare_variable(cg, node->for_var2, true);
int var_3 = cg_declare_variable(cg, (String) { NULL, 0 }, true);
walk_expr_node(cg, node->for_set, true);
cg_write_opcode(cg, OPCODE_SETV);
cg_write_u8(cg, var_3);
cg_write_opcode(cg, OPCODE_POP);
cg_write_opcode(cg, OPCODE_PUSHI);
cg_write_s64(cg, -1);
cg_write_opcode(cg, OPCODE_SETV);
cg_write_u8(cg, var_2);
cg_write_opcode(cg, OPCODE_POP);
int start = cg_write_opcode(cg, OPCODE_FOR);
cg_write_u8(cg, var_3);
cg_write_u8(cg, var_1);
cg_write_u8(cg, var_2);
int p = cg_write_u32(cg, 0);
walk_node(cg, node->left);
cg_write_opcode(cg, OPCODE_JUMP);
cg_write_u32(cg, start);
cg_patch_u32(cg, p, cg_current_offset(cg));
cg_pop_scope(cg);
}
break;
case NODE_WHILE:
{
// start:
// <cond>
// JIFP end
// <body>
// JUMP start
// end:
// ...
int start = cg_current_offset(cg);
walk_expr_node(cg, node->while_cond, true);
cg_write_opcode(cg, OPCODE_JIFP);
int p = cg_write_u32(cg, 0);
cg_push_scope(cg, SCOPE_WHILE);
walk_node(cg, node->left);
cg_pop_scope(cg);
cg_write_opcode(cg, OPCODE_JUMP);
cg_write_u32(cg, start);
cg_patch_u32(cg, p, cg_current_offset(cg));
}
break;
case NODE_INCLUDE:
walk_node(cg, node->include_root);
break;
default:
walk_expr_node(cg, node, false);
if (cg_global_scope(cg) && !inside_assignment(cg))
cg_write_opcode(cg, OPCODE_OUTPUT);
break;
}
}
#define WL_MAGIC 0xFEEDBEEF
static int codegen(Node *node, char *dst, int cap, char *errmsg, int errcap)
{
char *hdr;
if (cap < SIZEOF(uint32_t) * 3)
hdr = NULL;
else {
hdr = dst;
dst += SIZEOF(uint32_t) * 3;
cap -= SIZEOF(uint32_t) * 3;
}
Codegen cg = {
.code = { dst, cap/2, 0 },
.data = { dst + cap/2, cap/2, 0 },
.num_scopes = 0,
.err = false,
.errmsg = errmsg,
.errcap = errcap,
.data_off = -1,
};
cg.free_list_calls = cg.calls;
for (int i = 0; i < MAX_UNPATCHED_CALLS-1; i++)
cg.calls[i].next = &cg.calls[i+1];
cg.calls[MAX_UNPATCHED_CALLS-1].next = NULL;
cg_push_scope(&cg, SCOPE_GLOBAL);
cg_write_opcode(&cg, OPCODE_VARS);
int off = cg_write_u8(&cg, 0);
walk_node(&cg, node);
cg_write_opcode(&cg, OPCODE_EXIT);
cg_patch_u8(&cg, off, cg.scopes[0].max_vars);
cg_pop_scope(&cg);
if (cg.err)
return -1;
if (hdr) {
uint32_t magic = WL_MAGIC;
uint32_t code_len = cg.code.len;
uint32_t data_len = cg.data.len;
memcpy(hdr + 0, &magic , sizeof(uint32_t));
memcpy(hdr + 4, &code_len, sizeof(uint32_t));
memcpy(hdr + 8, &data_len, sizeof(uint32_t));
if (cg.code.len + cg.data.len <= cap)
memmove(dst + cg.code.len, dst + cap/2, cg.data.len);
}
return cg.code.len + cg.data.len + SIZEOF(uint32_t) * 3;
}
static int write_instr(Writer *w, char *src, int len, String data)
{
if (len == 0)
return -1;
switch (src[0]) {
uint8_t b0;
uint8_t b1;
uint8_t b2;
uint32_t w0;
uint32_t w1;
int64_t i;
double d;
case OPCODE_NOPE:
write_text(w, S("NOPE\n"));
return 1;
case OPCODE_JUMP:
if (len < 5) return -1;
memcpy(&w0, src + 1, sizeof(uint32_t));
write_text(w, S("JUMP "));
write_text_s64(w, w0);
write_text(w, S("\n"));
return 5;
case OPCODE_JIFP:
if (len < 5) return -1;
memcpy(&w0, src + 1, sizeof(uint32_t));
write_text(w, S("JIFP "));
write_text_s64(w, w0);
write_text(w, S("\n"));
return 5;
case OPCODE_OUTPUT:
write_text(w, S("OUTPUT\n"));
return 1;
case OPCODE_SYSVAR:
if (len < 9) return -1;
memcpy(&w0, src + 1, sizeof(uint32_t));
memcpy(&w1, src + 5, sizeof(uint32_t));
write_text(w, S("SYSVAR \""));
write_text(w, (String) { data.ptr + w0, w1 });
write_text(w, S("\"\n"));
return 9;
case OPCODE_SYSCALL:
if (len < 10) return -1;
memcpy(&b0, src + 1, sizeof(uint8_t));
memcpy(&w0, src + 2, sizeof(uint32_t));
memcpy(&w1, src + 6, sizeof(uint32_t));
write_text(w, S("SYSCALL "));
write_text_s64(w, b0);
write_text(w, S(" \""));
write_text(w, (String) { data.ptr + w0, w1 });
write_text(w, S("\"\n"));
return 10;
case OPCODE_CALL:
if (len < 6) return -1;
memcpy(&b0, src + 1, sizeof(uint8_t));
memcpy(&w0, src + 2, sizeof(uint32_t));
write_text(w, S("CALL "));
write_text_s64(w, b0);
write_text(w, S(" "));
write_text_s64(w, w0);
write_text(w, S("\n"));
return 6;
case OPCODE_RET:
write_text(w, S("RET\n"));
return 1;
case OPCODE_GROUP:
write_text(w, S("GROUP\n"));
return 1;
case OPCODE_PACK:
write_text(w, S("PACK\n"));
return 1;
case OPCODE_GPOP:
write_text(w, S("GPOP\n"));
return 1;
case OPCODE_FOR:
if (len < 8) return -1;
memcpy(&b0, src + 1, sizeof(b0));
memcpy(&b1, src + 2, sizeof(b1));
memcpy(&b2, src + 3, sizeof(b2));
memcpy(&w0, src + 4, sizeof(w0));
write_text(w, S("FOR "));
write_text_s64(w, b0);
write_text(w, S(" "));
write_text_s64(w, b1);
write_text(w, S(" "));
write_text_s64(w, b2);
write_text(w, S(" "));
write_text_s64(w, w0);
write_text(w, S("\n"));
return 8;
case OPCODE_EXIT:
write_text(w, S("EXIT\n"));
return 1;
case OPCODE_VARS:
if (len < 2) return -1;
memcpy(&b0, src + 1, sizeof(b0));
write_text(w, S("VARS "));
write_text_s64(w, b0);
write_text(w, S("\n"));
return 2;
case OPCODE_POP:
write_text(w, S("POP\n"));
return 1;
case OPCODE_SETV:
if (len < 2) return -1;
memcpy(&b0, src + 1, sizeof(uint8_t));
write_text(w, S("SETV "));
write_text_s64(w, b0);
write_text(w, S("\n"));
return 2;
case OPCODE_PUSHV:
if (len < 2) return -1;
memcpy(&b0, src + 1, sizeof(uint8_t));
write_text(w, S("PUSHV "));
write_text_s64(w, b0);
write_text(w, S("\n"));
return 2;
case OPCODE_PUSHI:
if (len < 9) return -1;
memcpy(&i, src + 1, sizeof(int64_t));
write_text(w, S("PUSHI "));
write_text_s64(w, i);
write_text(w, S("\n"));
return 9;
case OPCODE_PUSHF:
if (len < 9) return -1;
memcpy(&d, src + 1, sizeof(double));
write_text(w, S("PUSHF "));
write_text_f64(w, d);
write_text(w, S("\n"));
return 9;
case OPCODE_PUSHS:
if (len < 9) return -1;
memcpy(&w0, src + 1, sizeof(uint32_t));
memcpy(&w1, src + 5, sizeof(uint32_t));
write_text(w, S("PUSHS \""));
write_text(w, (String) { data.ptr + w0, w1 });
write_text(w, S("\"\n"));
return 9;
case OPCODE_PUSHA:
if (len < 5) return -1;
memcpy(&w0, src + 1, sizeof(w0));
write_text(w, S("PUSHA "));
write_text_s64(w, w0);
write_text(w, S("\n"));
return 5;
case OPCODE_PUSHM:
if (len < 5) return -1;
memcpy(&w0, src + 1, sizeof(w0));
write_text(w, S("PUSHM "));
write_text_s64(w, w0);
write_text(w, S("\n"));
return 5;
case OPCODE_PUSHN:
write_text(w, S("PUSHN\n"));
return 1;
case OPCODE_PUSHT:
write_text(w, S("PUSHT\n"));
return 1;
case OPCODE_PUSHFL:
write_text(w, S("PUSHFL\n"));
return 1;
case OPCODE_LEN:
write_text(w, S("LEN\n"));
return 1;
case OPCODE_NEG:
write_text(w, S("NEG\n"));
return 1;
case OPCODE_EQL:
write_text(w, S("EQL\n"));
return 1;
case OPCODE_NQL:
write_text(w, S("NQL\n"));
return 1;
case OPCODE_LSS:
write_text(w, S("LSS\n"));
return 1;
case OPCODE_GRT:
write_text(w, S("GRT\n"));
return 1;
case OPCODE_ADD:
write_text(w, S("ADD\n"));
return 1;
case OPCODE_SUB:
write_text(w, S("SUB\n"));
return 1;
case OPCODE_MUL:
write_text(w, S("MUL\n"));
return 1;
case OPCODE_DIV:
write_text(w, S("DIV\n"));
return 1;
case OPCODE_MOD:
write_text(w, S("MOD\n"));
return 1;
case OPCODE_APPEND:
write_text(w, S("APPEND\n"));
return 1;
case OPCODE_INSERT1:
write_text(w, S("INSERT1\n"));
return 1;
case OPCODE_INSERT2:
write_text(w, S("INSERT2\n"));
return 1;
case OPCODE_SELECT:
write_text(w, S("SELECT\n"));
return 1;
default:
write_text(w, S("byte "));
write_text_s64(w, src[0]);
return 1;
}
return -1;
}
static int write_program(WL_Program program, char *dst, int cap)
{
if (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 content)
{
if (compiler->err)
return (WL_AddResult) { .type=WL_ADD_ERROR };
ParseResult pres = parse((String) { content.ptr, content.len }, compiler->arena, compiler->msg, SIZEOF(compiler->msg));
if (pres.node == NULL) {
compiler->err = true;
return (WL_AddResult) { .type=WL_ADD_ERROR };
}
CompiledFile compiled_file = {
.file = compiler->waiting_file,
.root = pres.node,
.includes = pres.includes,
};
compiler->files[compiler->num_files++] = compiled_file;
compiler->waiting_file = (String) { NULL, 0 };
for (int i = 0; i < compiler->num_files; i++) {
Node *include = compiler->files[i].includes;
while (include) {
ASSERT(include->type == NODE_INCLUDE);
if (include->include_root == NULL) {
for (int j = 0; j < compiler->num_files; j++) {
if (streq(include->include_path, compiler->files[j].file)) {
include->include_root = compiler->files[j].root;
break;
}
}
}
if (include->include_root == NULL) {
if (compiler->num_files == FILE_LIMIT) {
ASSERT(0); // TODO
}
// TODO: Make the path relative to the compiled file
compiler->waiting_file = include->include_path;
return (WL_AddResult) { .type=WL_ADD_AGAIN, .path={ include->include_path.ptr, include->include_path.len } };
}
include = include->include_next;
}
}
return (WL_AddResult) { .type=WL_ADD_LINK };
}
int wl_compiler_link(WL_Compiler *compiler, WL_Program *program)
{
if (compiler->err) return -1;
if (compiler->num_files == 0 || compiler->waiting_file.len > 0) {
int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Missing files in compilation unit");
if (len > SIZEOF(compiler->msg))
len = SIZEOF(compiler->msg)-1;
compiler->msg[len] = '\0';
compiler->err = true;
return -1;
}
char *dst = compiler->arena->ptr + compiler->arena->cur;
int cap = compiler->arena->len - compiler->arena->cur;
int len = codegen(compiler->files[0].root, dst, cap, compiler->msg, SIZEOF(compiler->msg));
if (len < 0) {
compiler->err = true;
return -1;
}
if (len > cap) {
int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Out of memory");
if (len > SIZEOF(compiler->msg))
len = SIZEOF(compiler->msg)-1;
compiler->msg[len] = '\0';
compiler->err = true;
return -1;
}
*program = (WL_Program) { dst, len };
compiler->arena->cur += len;
return 0;
}
WL_String wl_compiler_error(WL_Compiler *compiler)
{
return compiler->err
? (WL_String) { compiler->msg, strlen(compiler->msg) }
: (WL_String) { NULL, 0 };
}
int wl_dump_ast(WL_Compiler *compiler, char *dst, int cap)
{
Writer w = { dst, cap, 0 };
for (int i = 0; i < compiler->num_files; i++) {
write_text(&w, S("(file \""));
write_text(&w, compiler->files[i].file);
write_text(&w, S("\" "));
write_node(&w, compiler->files[i].root);
write_text(&w, S(")"));
}
return w.len;
}
/////////////////////////////////////////////////////////////////////////
// OBJECT MODEL
/////////////////////////////////////////////////////////////////////////
typedef enum {
TYPE_NONE,
TYPE_BOOL,
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING,
TYPE_ARRAY,
TYPE_MAP,
TYPE_ERROR,
} Type;
#define TAG_ERROR 0
#define TAG_POSITIVE_INT 1
#define TAG_NEGATIVE_INT 2
#define TAG_BOOL 3
#define TAG_NONE 4
#define TAG_PTR 5
#define VALUE_NONE ((0 << 3) | TAG_NONE)
#define VALUE_TRUE ((0 << 3) | TAG_BOOL)
#define VALUE_FALSE ((1 << 3) | TAG_BOOL)
#define VALUE_ERROR ((0 << 3) | TAG_ERROR)
typedef uint64_t Value;
typedef struct Extension Extension;
struct Extension {
Extension *next;
int count;
int capacity;
Value vals[];
};
typedef struct {
Type type;
int count;
int capacity;
Extension *ext;
Value vals[];
} AggregateValue;
typedef struct {
Type type;
double raw;
} FloatValue;
typedef struct {
Type type;
int64_t raw;
} IntValue;
typedef struct {
Type type;
int len;
char data[];
} StringValue;
static int value_convert_to_str(Value v, char *dst, int cap);
static Type value_type(Value v)
{
switch (v & 7) {
case TAG_ERROR: return TYPE_ERROR;
case TAG_POSITIVE_INT: return TYPE_INT;
case TAG_NEGATIVE_INT: return TYPE_INT;
case TAG_BOOL : return TYPE_BOOL;
case TAG_NONE : return TYPE_NONE;
case TAG_PTR : return *(Type*) (v & ~(Value) 7); break;
}
return TAG_ERROR;
}
static int64_t value_to_s64(Value v)
{
ASSERT(value_type(v) == TYPE_INT);
if ((v & 7) == TAG_POSITIVE_INT)
return (int64_t) (v >> 3);
if ((v & 7) == TAG_NEGATIVE_INT)
return (int64_t) ((v >> 3) | ((Value) 7 << 61));
IntValue *p = (IntValue*) (v & ~(Value) 7);
return p->raw;
}
static double value_to_f64(Value v)
{
ASSERT(value_type(v) == TYPE_FLOAT);
FloatValue *p = (FloatValue*) (v & ~(Value) 7);
return p->raw;
}
static String value_to_str(Value v)
{
ASSERT(value_type(v) == TYPE_STRING);
StringValue *p = (StringValue*) (v & ~(Value) 7);
return (String) { p->data, p->len };
}
/*
2 bits -> 2^2 = 4
00000 0 .
00001 1 .
00010 2 .
00011 3 .
00100 4 .
00101 5 .
00110 6 .
00111 7 .
01000 8
01001 9
01010 10
01011 11
01100 12
01101 13
01110 14
01111 15
10000 -16
10001 -15
10010 -14
10011 -13
10100 -12
10101 -11
10110 -10
10111 -9
11000 -8 .
11001 -7 .
11010 -6 .
11011 -5 .
11100 -4 .
11101 -3 .
11110 -2 .
11111 -1 .
*/
static Value value_from_s64(int64_t x, WL_Arena *arena, Error *err)
{
Value v = (Value) x;
Value upper3bits = v >> 61;
if (upper3bits == 0)
return (v << 3) | TAG_POSITIVE_INT;
if (upper3bits == 7)
return (v << 3) | TAG_NEGATIVE_INT;
IntValue *p = alloc(arena, SIZEOF(IntValue), _Alignof(IntValue));
if (p == NULL) {
REPORT(err, "Out of memory");
return VALUE_ERROR;
}
p->type = TYPE_INT;
p->raw = x;
ASSERT(((Value) p & 7) == 0);
return ((Value) p) | TAG_PTR;
}
static Value value_from_f64(double x, WL_Arena *arena, Error *err)
{
FloatValue *v = alloc(arena, SIZEOF(FloatValue), _Alignof(FloatValue));
if (v == NULL) {
REPORT(err, "Out of memory");
return VALUE_ERROR;
}
v->type = TYPE_FLOAT;
v->raw = x;
ASSERT(((uintptr_t) v & 7) == 0);
return ((Value) v) | TAG_PTR;
}
static Value value_from_str(String x, WL_Arena *arena, Error *err)
{
StringValue *v = alloc(arena, SIZEOF(StringValue) + x.len, 8);
if (v == NULL) {
REPORT(err, "Out of memory");
return VALUE_ERROR;
}
v->type = TYPE_STRING;
v->len = x.len;
memcpy(v->data, x.ptr, x.len);
ASSERT(((uintptr_t) v & 7) == 0);
return ((Value) v) | TAG_PTR;
}
static Value aggregate_empty(bool map, uint32_t cap, WL_Arena *arena, Error *err)
{
AggregateValue *v = alloc(arena, SIZEOF(AggregateValue) + 2 * cap * SIZEOF(Value), MAX(_Alignof(AggregateValue), 8));
if (v == NULL) {
REPORT(err, "Out of memory");
return VALUE_ERROR;
}
v->type = map ? TYPE_MAP : TYPE_ARRAY;
v->count = 0;
v->capacity = cap;
v->ext = NULL;
ASSERT(((uintptr_t) v & 7) == 0);
return ((Value) v) | TAG_PTR;
}
static int64_t aggregate_length(AggregateValue *agg)
{
int64_t n = agg->count;
Extension *ext = agg->ext;
while (ext) {
n += ext->count;
ext = ext->next;
}
return n;
}
static Value *aggregate_select_by_raw_index(AggregateValue *agg, int64_t idx)
{
ASSERT(agg->type == TYPE_ARRAY || agg->type == TYPE_MAP);
if (idx < 0 || idx >= aggregate_length(agg))
return NULL;
if (idx < agg->count)
return &agg->vals[idx];
idx -= agg->count;
Extension *ext = agg->ext;
while (ext) {
if (idx < ext->count)
return &ext->vals[idx];
idx -= ext->count;
ext = ext->next;
}
UNREACHABLE;
return NULL;
}
static bool value_eql(Value a, Value b);
static Value *aggregate_select(AggregateValue *agg, Value key)
{
if (agg->type == TYPE_MAP) {
for (int i = 0; i < agg->count; i += 2)
if (value_eql(agg->vals[i], key))
return &agg->vals[i+1];
Extension *ext = agg->ext;
while (ext) {
for (int i = 0; i < ext->count; i += 2)
if (value_eql(ext->vals[i], key)) {
return &ext->vals[i+1];
}
ext = ext->next;
}
return NULL;
} else {
ASSERT(agg->type == TYPE_ARRAY);
if (value_type(key) != TYPE_INT)
return NULL;
int64_t idx = value_to_s64(key);
return aggregate_select_by_raw_index(agg, idx);
}
}
static bool aggregate_append(AggregateValue *agg, Value v1, Value v2, WL_Arena *arena)
{
if (agg->count < agg->capacity) {
agg->vals[agg->count++] = v1;
if (v2 != VALUE_ERROR)
agg->vals[agg->count++] = v2;
return true;
}
Extension *tail = agg->ext;
if (tail)
while (tail->next)
tail = tail->next;
Extension *ext;
if (tail == NULL || tail->count == tail->capacity) {
int cap = 8;
ext = alloc(arena, SIZEOF(Extension) + cap * sizeof(Value), ALIGNOF(Extension));
if (ext == NULL)
return false;
ext->count = 0;
ext->capacity = cap;
ext->next = NULL;
if (tail)
tail->next = ext;
else
agg->ext = ext;
} else
ext = tail;
ext->vals[ext->count++] = v1;
if (v2 != VALUE_ERROR)
ext->vals[ext->count++] = v2;
return true;
}
static Value value_empty_map(uint32_t cap, WL_Arena *arena, Error *err)
{
return aggregate_empty(true, 2 * cap, arena, err);
}
static Value value_empty_array(uint32_t cap, WL_Arena *arena, Error *err)
{
return aggregate_empty(false, cap, arena, err);
}
static int64_t value_length(Value set)
{
ASSERT(value_type(set) == TYPE_MAP || value_type(set) == TYPE_ARRAY);
AggregateValue *agg = (void*) (set & ~(Value) 7);
int64_t len = aggregate_length(agg);
if (agg->type == TYPE_MAP)
len /= 2;
return len;
}
static bool value_insert(Value set, Value key, Value val, WL_Arena *arena, Error *err)
{
Type t = value_type(set);
if (t != TYPE_MAP && t != TYPE_ARRAY) {
REPORT(err, "Invalid insertion on non-map and non-array value");
return false;
}
AggregateValue *agg = (void*) (set & ~(Value) 7);
Value *dst = aggregate_select(agg, key);
if (dst != NULL) {
*dst = val;
return true;
}
if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) {
REPORT(err, "Invalid index used in array access");
return false;
}
if (!aggregate_append(agg, key, val, arena)) {
REPORT(err, "Out of memory");
return false;
}
return true;
}
static Value value_select(Value set, Value key, Error *err)
{
Type t = value_type(set);
if (t != TYPE_MAP && t != TYPE_ARRAY) {
REPORT(err, "Invalid selection from non-map and non-array value");
return VALUE_ERROR;
}
AggregateValue *agg = (void*) (set & ~(Value) 7);
Value *dst = aggregate_select(agg, key);
if (dst) return *dst;
if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) {
REPORT(err, "Invalid index used in array access");
return VALUE_ERROR;
}
char key_buf[1<<8];
int key_len = value_convert_to_str(key, key_buf, SIZEOF(key_buf));
if (key_len > SIZEOF(key_buf)-1)
key_len = SIZEOF(key_buf)-1;
key_buf[key_len] = '\0';
char set_buf[1<<8];
int set_len = value_convert_to_str(set, set_buf, SIZEOF(set_buf));
if (set_len > SIZEOF(set_buf)-1)
set_len = SIZEOF(set_buf)-1;
set_buf[set_len] = '\0';
REPORT(err, "Invalid key '%s' used in access to map '%s'", key_buf, set_buf);
return VALUE_ERROR;
}
static Value value_select_by_index(Value set, int64_t idx, Error *err)
{
Type t = value_type(set);
if (t != TYPE_MAP && t != TYPE_ARRAY) {
REPORT(err, "Invalid selection from non-map and non-array value");
return VALUE_ERROR;
}
AggregateValue *agg = (void*) (set & ~(Value) 7);
if (agg->type == TYPE_MAP)
idx *= 2;
Value *src = aggregate_select_by_raw_index(agg, idx);
if (src == NULL) {
REPORT(err, "Invalid selection from non-map and non-array value");
return VALUE_ERROR;
}
return *src;
}
static bool value_append(Value set, Value val, WL_Arena *arena, Error *err)
{
Type t = value_type(set);
if (t != TYPE_ARRAY) {
REPORT(err, "Invalid append on non-array value");
return false;
}
AggregateValue *agg = (void*) (set & ~(Value) 7);
if (!aggregate_append(agg, val, VALUE_ERROR, arena)) {
REPORT(err, "Out of memory");
return false;
}
return true;
}
static bool value_eql(Value a, Value b)
{
Type t1 = value_type(a);
Type t2 = value_type(b);
if (t1 != t2)
return false;
switch (t1) {
case TYPE_NONE:
return true;
case TYPE_BOOL:
return a == b;
case TYPE_INT:
return value_to_s64(a) == value_to_s64(b);
case TYPE_FLOAT:
return value_to_f64(a) == value_to_f64(b);
case TYPE_MAP:
return false; // TODO
case TYPE_ARRAY:
return false; // TODO
case TYPE_STRING:
return streq(value_to_str(a), value_to_str(b));
case TYPE_ERROR:
return true;
}
return false;
}
static bool value_nql(Value a, Value b)
{
return !value_eql(a, b);
}
#define TYPE_PAIR(X, Y) (((uint16_t) (X) << 16) | (uint16_t) (Y))
bool value_greater(Value a, Value b, Error *err)
{
Type t1 = value_type(a);
Type t2 = value_type(b);
switch (TYPE_PAIR(t1, t2)) {
case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) > value_to_s64(b);
case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) > value_to_f64(b);
case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) > value_to_s64(b);
case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) > value_to_f64(b);
default:break;
}
REPORT(err, "Invalid '>' operation on non-numeric type");
return false;
}
bool value_lower(Value a, Value b, Error *err)
{
Type t1 = value_type(a);
Type t2 = value_type(b);
switch (TYPE_PAIR(t1, t2)) {
case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) < value_to_s64(b);
case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) < value_to_f64(b);
case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) < value_to_s64(b);
case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) < value_to_f64(b);
default:break;
}
REPORT(err, "Invalid '<' operation on non-numeric type");
return false;
}
static Value value_neg(Value v, WL_Arena *arena, Error *err)
{
Type t = value_type(v);
if (t == TYPE_INT)
return value_from_s64(-value_to_s64(v), arena, err); // TODO: overflow
if (t == TYPE_FLOAT)
return value_from_f64(-value_to_f64(v), arena, err);
REPORT(err, "Invalid '-' operation on non-numeric type");
return VALUE_ERROR;
}
static Value value_add(Value v1, Value v2, WL_Arena *arena, Error *err)
{
Type t1 = value_type(v1);
Type t2 = value_type(v2);
Value r;
switch (TYPE_PAIR(t1, t2)) {
case TYPE_PAIR(TYPE_INT, TYPE_INT):
{
int64_t u = value_to_s64(v1);
int64_t v = value_to_s64(v2);
// TODO: check overflow and underflow
r = value_from_s64(u + v, arena, err);
}
break;
case TYPE_PAIR(TYPE_INT, TYPE_FLOAT):
{
double u = (double) value_to_s64(v1);
double v = value_to_f64(v2);
r = value_from_f64(u + v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_INT):
{
double u = value_to_f64(v1);
double v = (double) value_to_s64(v2);
r = value_from_f64(u + v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT):
{
double u = value_to_f64(v1);
double v = value_to_f64(v2);
// TODO: check overflow and underflow
r = value_from_f64(u + v, arena, err);
}
break;
default:
REPORT(err, "Invalid operation '+' on non-numeric value");
return VALUE_ERROR;
}
return r;
}
static Value value_sub(Value v1, Value v2, WL_Arena *arena, Error *err)
{
Type t1 = value_type(v1);
Type t2 = value_type(v2);
Value r;
switch (TYPE_PAIR(t1, t2)) {
case TYPE_PAIR(TYPE_INT, TYPE_INT):
{
int64_t u = value_to_s64(v1);
int64_t v = value_to_s64(v2);
// TODO: check overflow and underflow
r = value_from_s64(u - v, arena, err);
}
break;
case TYPE_PAIR(TYPE_INT, TYPE_FLOAT):
{
double u = (double) value_to_s64(v1);
double v = value_to_f64(v2);
r = value_from_f64(u - v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_INT):
{
double u = value_to_f64(v1);
double v = (double) value_to_s64(v2);
r = value_from_f64(u - v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT):
{
double u = value_to_f64(v1);
double v = value_to_f64(v2);
// TODO: check overflow and underflow
r = value_from_f64(u - v, arena, err);
}
break;
default:
REPORT(err, "Invalid operation '-' on non-numeric value");
return VALUE_ERROR;
}
return r;
}
static Value value_mul(Value v1, Value v2, WL_Arena *arena, Error *err)
{
Type t1 = value_type(v1);
Type t2 = value_type(v2);
Value r;
switch (TYPE_PAIR(t1, t2)) {
case TYPE_PAIR(TYPE_INT, TYPE_INT):
{
int64_t u = value_to_s64(v1);
int64_t v = value_to_s64(v2);
// TODO: check overflow and underflow
r = value_from_s64(u * v, arena, err);
}
break;
case TYPE_PAIR(TYPE_INT, TYPE_FLOAT):
{
double u = (double) value_to_s64(v1);
double v = value_to_f64(v2);
r = value_from_f64(u * v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_INT):
{
double u = value_to_f64(v1);
double v = (double) value_to_s64(v2);
r = value_from_f64(u * v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT):
{
double u = value_to_f64(v1);
double v = value_to_f64(v2);
// TODO: check overflow and underflow
r = value_from_f64(u * v, arena, err);
}
break;
default:
REPORT(err, "Invalid operation '*' on non-numeric value");
return VALUE_ERROR;
}
return r;
}
static Value value_div(Value v1, Value v2, WL_Arena *arena, Error *err)
{
Type t1 = value_type(v1);
Type t2 = value_type(v2);
Value r;
switch (TYPE_PAIR(t1, t2)) {
case TYPE_PAIR(TYPE_INT, TYPE_INT):
{
// TODO: check division by 0
int64_t u = value_to_s64(v1);
int64_t v = value_to_s64(v2);
r = value_from_s64(u / v, arena, err);
}
break;
case TYPE_PAIR(TYPE_INT, TYPE_FLOAT):
{
// TODO: check division by 0
double u = (double) value_to_s64(v1);
double v = value_to_f64(v2);
r = value_from_f64(u / v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_INT):
{
// TODO: check division by 0
double u = value_to_f64(v1);
double v = (double) value_to_s64(v2);
r = value_from_f64(u / v, arena, err);
}
break;
case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT):
{
double u = value_to_f64(v1);
double v = value_to_f64(v2);
r = value_from_f64(u / v, arena, err);
}
break;
default:
REPORT(err, "Invalid operation '/' on non-numeric value");
return VALUE_ERROR;
}
return r;
}
static Value value_mod(Value v1, Value v2, WL_Arena *arena, Error *err)
{
Type t1 = value_type(v1);
Type t2 = value_type(v2);
if (t1 != TYPE_INT || t2 != TYPE_INT) {
REPORT(err, "Invalid operation '%%' on non-integer value");
return VALUE_ERROR;
}
int64_t u = value_to_s64(v1);
int64_t v = value_to_s64(v2);
Value r = value_from_s64(u % v, arena, err);
return r;
}
static void value_convert_to_str_inner(Writer *w, Value v)
{
Type t = value_type(v);
switch (t) {
case TYPE_NONE:
break;
case TYPE_BOOL:
write_text(w, v == VALUE_TRUE ? S("true") : S("false"));
break;
case TYPE_INT:
write_text_s64(w, value_to_s64(v));
break;
case TYPE_FLOAT:
write_text_f64(w, value_to_f64(v));
break;
case TYPE_STRING:
write_text(w, value_to_str(v));
break;
case TYPE_ARRAY:
{
AggregateValue *agg = (void*) (v & ~(Value) 7);
for (int i = 0; i < agg->count; i++)
value_convert_to_str_inner(w, agg->vals[i]);
Extension *ext = agg->ext;
while (ext) {
for (int i = 0; i < ext->count; i++)
value_convert_to_str_inner(w, ext->vals[i]);
ext = ext->next;
}
}
break;
case TYPE_MAP:
{
write_text(w, S("{"));
AggregateValue *agg = (void*) (v & ~(Value) 7);
for (int i = 0; i < agg->count; i += 2) {
value_convert_to_str_inner(w, agg->vals[i+0]);
write_text(w, S(": "));
value_convert_to_str_inner(w, agg->vals[i+1]);
if (i+2 < agg->count || agg->ext)
write_text(w, S(", "));
}
Extension *ext = agg->ext;
while (ext) {
for (int i = 0; i < ext->count; i += 2) {
value_convert_to_str_inner(w, ext->vals[i+0]);
write_text(w, S(": "));
value_convert_to_str_inner(w, ext->vals[i+1]);
if (i+2 < ext->count || ext->next)
write_text(w, S(", "));
}
ext = ext->next;
}
write_text(w, S("}"));
}
break;
case TYPE_ERROR:
break;
}
}
static int value_convert_to_str(Value v, char *dst, int cap)
{
Writer w = { dst, cap, 0};
value_convert_to_str_inner(&w, v);
return w.len;
}
#undef TYPE_PAIR
/////////////////////////////////////////////////////////////////////////
// RUNTIME
/////////////////////////////////////////////////////////////////////////
#define MAX_STACK 1024
#define MAX_FRAMES 1024
#define MAX_GROUPS 8
typedef struct {
int retaddr;
int varbase;
} Frame;
typedef enum {
RUNTIME_BEGIN,
RUNTIME_LOOP,
RUNTIME_DONE,
RUNTIME_ERROR,
RUNTIME_OUTPUT,
RUNTIME_SYSVAR,
RUNTIME_SYSCALL,
} RuntimeState;
struct WL_Runtime {
RuntimeState state;
String code;
String data;
int off;
int vars;
int stack;
Value values[MAX_STACK];
int num_frames;
Frame frames[MAX_FRAMES];
int num_groups;
int groups[MAX_GROUPS];
WL_Arena *arena;
char msg[128];
Error err;
int stack_before_user;
String str_for_user;
int num_output;
int cur_output;
char buf[128];
};
WL_Runtime *wl_runtime_init(WL_Arena *arena, WL_Program program)
{
if (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 <= rt->data.len);
return (String) { rt->data.ptr + off, len };
}
static Value *rt_variable(WL_Runtime *rt, uint8_t x)
{
ASSERT(rt->num_frames > 0);
Frame *frame = &rt->frames[rt->num_frames-1];
ASSERT(frame->varbase - x >= 0
&& frame->varbase - x < MAX_STACK);
return &rt->values[frame->varbase - x];
}
static int values_usage(WL_Runtime *rt)
{
int num_vars = (MAX_STACK - rt->vars - 1);
return rt->stack + num_vars;
}
static bool rt_check_stack(WL_Runtime *rt, int min)
{
if (MAX_STACK - values_usage(rt) < min) {
REPORT(&rt->err, "Out of stack");
rt->state = RUNTIME_ERROR;
return false;
}
return true;
}
static bool rt_push_frame(WL_Runtime *rt, uint8_t args)
{
if (rt->num_frames == MAX_FRAMES) {
REPORT(&rt->err, "Call stack limit reached");
rt->state = RUNTIME_ERROR;
return false;
}
if (MAX_STACK - values_usage(rt) < args) {
REPORT(&rt->err, "Stack limit reached");
rt->state = RUNTIME_ERROR;
return false;
}
Frame *frame = &rt->frames[rt->num_frames++];
frame->retaddr = rt->off;
frame->varbase = rt->vars;
for (int i = 0; i < args; i++)
rt->values[rt->vars--] = rt->values[--rt->stack];
return true;
}
static void rt_pop_frame(WL_Runtime *rt)
{
ASSERT(rt->num_frames > 0);
Frame *frame = &rt->frames[rt->num_frames-1];
rt->off = frame->retaddr;
rt->vars = frame->varbase;
rt->num_frames--;
}
static void rt_set_frame_vars(WL_Runtime *rt, uint8_t num)
{
ASSERT(rt->num_frames > 0);
Frame *frame = &rt->frames[rt->num_frames-1];
int num_vars = frame->varbase - rt->vars;
if (num_vars < num)
for (int i = 0; i < num - num_vars; i++)
rt->values[rt->vars - i] = VALUE_NONE;
rt->vars = frame->varbase - num;
}
static void rt_push_group(WL_Runtime *rt)
{
if (rt->num_groups == MAX_GROUPS) {
REPORT(&rt->err, "Out of memory");
rt->state = RUNTIME_ERROR;
return;
}
rt->groups[rt->num_groups++] = rt->stack;
}
static void rt_pack_group(WL_Runtime *rt)
{
if (!rt_check_stack(rt, 1))
return;
ASSERT(rt->num_groups > 0);
int start = rt->groups[--rt->num_groups];
int end = rt->stack;
if (end - start > 1) {
Value set = value_empty_array(end - start, rt->arena, &rt->err);
if (set == VALUE_ERROR)
return;
for (int i = start; i < end; i++)
if (!value_append(set, rt->values[i], rt->arena, &rt->err))
return;
rt->stack = start;
rt->values[rt->stack++] = set;
}
}
static void rt_pop_group(WL_Runtime *rt)
{
ASSERT(rt->num_groups > 0);
rt->stack = rt->groups[--rt->num_groups];
}
static void value_print(Value v)
{
char buf[1<<8];
int len = value_convert_to_str(v, buf, SIZEOF(buf));
if (len < SIZEOF(buf))
fwrite(buf, 1, len, stdout);
else {
len = SIZEOF(buf)-1;
fwrite(buf, 1, len, stdout);
fprintf(stdout, " [...]");
}
putc('\n', stdout);
fflush(stdout);
}
static void step(WL_Runtime *rt)
{
switch (rt_read_u8(rt)) {
Type t;
Value v1;
Value v2;
Value v3;
uint32_t o;
uint8_t b1;
uint8_t b2;
uint8_t b3;
int64_t i;
double f;
String s;
case OPCODE_NOPE:
break;
case OPCODE_JUMP:
rt->off = rt_read_u32(rt);
break;
case OPCODE_JIFP:
ASSERT(rt->stack > 0);
o = rt_read_u32(rt);
v1 = rt->values[--rt->stack];
if (v1 == VALUE_FALSE)
rt->off = o;
else if (value_type(v1) != TYPE_BOOL) {
REPORT(&rt->err, "Invalid non-boolean condition");
rt->state = RUNTIME_ERROR;
break;
}
break;
case OPCODE_VARS:
b1 = rt_read_u8(rt);
rt_set_frame_vars(rt, b1);
break;
case OPCODE_OUTPUT:
if (rt->stack > 0) {
rt->cur_output = 0;
rt->num_output = rt->stack;
rt->state = RUNTIME_OUTPUT;
}
break;
case OPCODE_SYSVAR:
s = rt_read_str(rt);
rt_push_frame(rt, 0);
rt->stack_before_user = rt->stack;
rt->str_for_user = s;
rt->state = RUNTIME_SYSVAR;
break;
case OPCODE_SYSCALL:
b1 = rt_read_u8(rt);
s = rt_read_str(rt);
rt_push_frame(rt, b1);
rt->stack_before_user = rt->stack;
rt->str_for_user = s;
rt->state = RUNTIME_SYSCALL;
break;
case OPCODE_CALL:
b1 = rt_read_u8(rt);
o = rt_read_u32(rt);
rt_push_frame(rt, b1);
rt->off = o;
break;
case OPCODE_RET:
rt_pop_frame(rt);
break;
case OPCODE_GROUP:
rt_push_group(rt);
break;
case OPCODE_PACK:
rt_pack_group(rt);
break;
case OPCODE_GPOP:
rt_pop_group(rt);
break;
case OPCODE_FOR:
b1 = rt_read_u8(rt);
b2 = rt_read_u8(rt);
b3 = rt_read_u8(rt);
o = rt_read_u32(rt);
v1 = *rt_variable(rt, b3);
ASSERT(value_type(v1) == TYPE_INT);
i = value_to_s64(v1);
v2 = *rt_variable(rt, b1);
if (value_length(v2)-1 == i) {
rt->off = o;
break;
}
i++;
v1 = value_select_by_index(v2, i, &rt->err);
if (v1 == VALUE_ERROR) break;
*rt_variable(rt, b2) = v1;
v1 = value_from_s64(i, rt->arena, &rt->err); // TODO: this could be in-place
*rt_variable(rt, b3) = v1;
break;
case OPCODE_EXIT:
rt->state = RUNTIME_DONE;
break;
case OPCODE_POP:
ASSERT(rt->stack > 0);
rt->stack--;
break;
case OPCODE_SETV:
ASSERT(rt->stack > 0);
b1 = rt_read_u8(rt);
*rt_variable(rt, b1) = rt->values[rt->stack-1];
break;
case OPCODE_PUSHV:
if (!rt_check_stack(rt, 1)) break;
b1 = rt_read_u8(rt);
rt->values[rt->stack++] = *rt_variable(rt, b1);
break;
case OPCODE_PUSHI:
if (!rt_check_stack(rt, 1)) break;
i = rt_read_s64(rt);
v1 = value_from_s64(i, rt->arena, &rt->err);
rt->values[rt->stack++] = v1;
break;
case OPCODE_PUSHF:
if (!rt_check_stack(rt, 1)) break;
f = rt_read_f64(rt);
v1 = value_from_f64(f, rt->arena, &rt->err);
rt->values[rt->stack++] = v1;
break;
case OPCODE_PUSHS:
if (!rt_check_stack(rt, 1)) break;
s = rt_read_str(rt);
v1 = value_from_str(s, rt->arena, &rt->err);
rt->values[rt->stack++] = v1;
break;
case OPCODE_PUSHA:
if (!rt_check_stack(rt, 1)) break;
o = rt_read_u32(rt);
v1 = value_empty_array(o, rt->arena, &rt->err);
rt->values[rt->stack++] = v1;
break;
case OPCODE_PUSHM:
if (!rt_check_stack(rt, 1)) break;
o = rt_read_u32(rt);
v1 = value_empty_map(o, rt->arena, &rt->err);
rt->values[rt->stack++] = v1;
break;
case OPCODE_PUSHN:
if (!rt_check_stack(rt, 1)) break;
rt->values[rt->stack++] = VALUE_NONE;
break;
case OPCODE_PUSHT:
if (!rt_check_stack(rt, 1)) break;
rt->values[rt->stack++] = VALUE_TRUE;
break;
case OPCODE_PUSHFL:
if (!rt_check_stack(rt, 1)) break;
rt->values[rt->stack++] = VALUE_FALSE;
break;
case OPCODE_LEN:
ASSERT(rt->stack > 0);
v1 = rt->values[rt->stack-1];
t = value_type(v1);
if (t != TYPE_ARRAY && t != TYPE_MAP) {
REPORT(&rt->err, "Invalid operation 'len' on non-aggregate value");
rt->state = RUNTIME_ERROR;
break;
}
v2 = value_from_s64(value_length(v1), rt->arena, &rt->err);
rt->values[rt->stack-1] = v2;
break;
case OPCODE_NEG:
ASSERT(rt->stack > 0);
v1 = rt->values[rt->stack-1];
v2 = value_neg(v1, rt->arena, &rt->err);
rt->values[rt->stack-1] = v2;
break;
case OPCODE_EQL:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_eql(v2, v1) ? VALUE_TRUE : VALUE_FALSE;
rt->values[rt->stack++] = v3;
break;
case OPCODE_NQL:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_nql(v2, v1) ? VALUE_TRUE : VALUE_FALSE;
rt->values[rt->stack++] = v3;
break;
case OPCODE_LSS:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_lower(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE;
rt->values[rt->stack++] = v3;
break;
case OPCODE_GRT:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_greater(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE;
rt->values[rt->stack++] = v3;
break;
case OPCODE_ADD:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_add(v2, v1, rt->arena, &rt->err);
rt->values[rt->stack++] = v3;
break;
case OPCODE_SUB:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_sub(v2, v1, rt->arena, &rt->err);
rt->values[rt->stack++] = v3;
break;
case OPCODE_MUL:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_mul(v2, v1, rt->arena, &rt->err);
rt->values[rt->stack++] = v3;
break;
case OPCODE_DIV:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_div(v2, v1, rt->arena, &rt->err);
rt->values[rt->stack++] = v3;
break;
case OPCODE_MOD:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_mod(v2, v1, rt->arena, &rt->err);
rt->values[rt->stack++] = v3;
break;
case OPCODE_APPEND:
ASSERT(rt->stack > 1);
v2 = rt->values[--rt->stack];
v1 = rt->values[rt->stack-1];
value_append(v1, v2, rt->arena, &rt->err);
break;
case OPCODE_INSERT1:
ASSERT(rt->stack > 2);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = rt->values[rt->stack-1];
value_insert(v3, v1, v2, rt->arena, &rt->err);
break;
case OPCODE_INSERT2:
ASSERT(rt->stack > 2);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = rt->values[rt->stack-1];
value_insert(v2, v1, v3, rt->arena, &rt->err);
break;
case OPCODE_SELECT:
ASSERT(rt->stack > 1);
v1 = rt->values[--rt->stack];
v2 = rt->values[--rt->stack];
v3 = value_select(v2, v1, &rt->err);
rt->values[rt->stack++] = v3;
break;
default:
UNREACHABLE;
}
}
WL_EvalResult wl_runtime_eval(WL_Runtime *rt)
{
if (rt->state != RUNTIME_OUTPUT || rt->cur_output == rt->num_output) {
switch (rt->state) {
case RUNTIME_BEGIN:
break;
case RUNTIME_DONE:
return (WL_EvalResult) { .type=WL_EVAL_DONE };
case RUNTIME_ERROR:
return (WL_EvalResult) { .type=WL_EVAL_ERROR };
case RUNTIME_OUTPUT:
rt->stack -= rt->num_output;
break;
case RUNTIME_SYSVAR:
{
ASSERT(rt->stack >= rt->stack_before_user);
int pushed_by_user = rt->stack - rt->stack_before_user;
if (pushed_by_user > 1) {
REPORT(&rt->err, "Invalid API usage");
rt->state = RUNTIME_ERROR;
return (WL_EvalResult) { .type=WL_EVAL_ERROR };
}
if (rt->stack == rt->stack_before_user) {
// User didn't push anything on the stack
if (!rt_check_stack(rt, 1))
return (WL_EvalResult) { .type=WL_EVAL_ERROR };
rt->values[rt->stack++] = VALUE_NONE;
}
rt_pop_frame(rt);
}
break;
case RUNTIME_SYSCALL:
ASSERT(rt->stack >= rt->stack_before_user);
rt_pop_frame(rt);
break;
default:
UNREACHABLE;
}
rt->state = RUNTIME_LOOP;
do {
step(rt);
if (rt->err.yes)
rt->state = RUNTIME_ERROR;
} while (rt->state == RUNTIME_LOOP);
}
switch (rt->state) {
case RUNTIME_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");
}