/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "hymn.h"
void *hymn_malloc(size_t size) {
void *mem = malloc(size);
if (mem) {
return mem;
}
fprintf(stderr, "malloc failed.\n");
exit(1);
}
void *hymn_calloc(size_t count, size_t size) {
void *mem = calloc(count, size);
if (mem) {
return mem;
}
fprintf(stderr, "calloc failed.\n");
exit(1);
}
void *hymn_realloc(void *mem, size_t size) {
mem = realloc(mem, size);
if (mem) {
return mem;
}
fprintf(stderr, "realloc failed.\n");
exit(1);
}
void *hymn_malloc_int(int count, size_t size) {
if (count < 0) {
fprintf(stderr, "malloc negative count.\n");
exit(1);
}
void *mem = malloc((size_t)count * size);
if (mem) {
return mem;
}
fprintf(stderr, "malloc failed.\n");
exit(1);
}
void *hymn_calloc_int(int count, size_t size) {
if (count < 0) {
fprintf(stderr, "calloc negative count.\n");
exit(1);
}
void *mem = calloc((size_t)count, size);
if (mem) {
return mem;
}
fprintf(stderr, "calloc failed.\n");
exit(1);
}
void *hymn_realloc_int(void *mem, int count, size_t size) {
if (count < 0) {
fprintf(stderr, "realloc negative count.\n");
exit(1);
}
mem = realloc(mem, (size_t)count * size);
if (mem) {
return mem;
}
fprintf(stderr, "realloc failed.\n");
exit(1);
}
static void hymn_mem_copy(void *dest, void *src, int count, size_t size) {
if (count < 0) {
fprintf(stderr, "memcpy negative count.\n");
exit(1);
}
memcpy(dest, src, (size_t)count * size);
}
FILE *hymn_open_file(const char *path, const char *mode) {
#ifdef _MSC_VER
FILE *open = NULL;
errno_t code = fopen_s(&open, path, mode);
return code != 0 ? NULL : open;
#else
return fopen(path, mode);
#endif
}
static HymnStringHead *string_head_init(size_t length, size_t capacity) {
size_t memory = sizeof(HymnStringHead) + capacity + 1;
HymnStringHead *head = (HymnStringHead *)hymn_malloc(memory);
memset(head, 0, memory);
head->length = length;
head->capacity = capacity;
return head;
}
HymnString *hymn_new_string_with_capacity(size_t capacity) {
HymnStringHead *head = string_head_init(0, capacity);
return (HymnString *)(head + 1);
}
HymnString *hymn_new_string_with_length(const char *chars, size_t length) {
HymnStringHead *head = string_head_init(length, length);
char *string = (char *)(head + 1);
memcpy(string, chars, length);
return (HymnString *)string;
}
HymnString *hymn_new_empty_string(size_t length) {
HymnStringHead *head = string_head_init(length, length);
char *string = (char *)(head + 1);
return (HymnString *)string;
}
HymnString *hymn_new_string(const char *chars) {
size_t length = strlen(chars);
return hymn_new_string_with_length(chars, length);
}
HymnString *hymn_substring(const char *init, size_t start, size_t end) {
size_t length = end - start;
HymnStringHead *head = string_head_init(length, length);
char *string = (char *)(head + 1);
memcpy(string, &init[start], length);
string[length] = '\0';
return (HymnString *)string;
}
void hymn_string_trim(HymnString *string) {
size_t len = hymn_string_len(string);
size_t start = 0;
while (start < len) {
char c = string[start];
if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
break;
}
start++;
}
if (start == len) {
hymn_string_zero(string);
} else {
size_t end = len - 1;
while (end > start) {
char c = string[end];
if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
break;
}
end--;
}
end++;
size_t offset = start;
size_t size = end - start;
for (size_t i = 0; i < size; i++) {
string[i] = string[offset++];
}
HymnStringHead *head = hymn_string_head(string);
head->length = size;
string[size] = '\0';
}
}
HymnString *hymn_string_copy(HymnString *string) {
HymnStringHead *head = hymn_string_head(string);
return hymn_new_string_with_length(string, head->length);
}
void hymn_string_delete(HymnString *string) {
if (string == NULL) {
return;
}
free((char *)string - sizeof(HymnStringHead));
}
void hymn_string_zero(HymnString *string) {
HymnStringHead *head = hymn_string_head(string);
head->length = 0;
string[0] = '\0';
}
static HymnStringHead *string_resize(HymnStringHead *head, size_t capacity) {
size_t memory = sizeof(HymnStringHead) + capacity + 1;
HymnStringHead *new = hymn_realloc(head, memory);
new->capacity = capacity;
return new;
}
HymnString *hymn_string_append(HymnString *string, const char *b) {
HymnStringHead *head = hymn_string_head(string);
size_t len_a = head->length;
size_t len_b = strlen(b);
size_t len = len_a + len_b;
if (len > head->capacity) {
head = string_resize(head, len * 2);
}
head->length = len;
char *s = (char *)(head + 1);
memcpy(s + len_a, b, len_b + 1);
s[len] = '\0';
return (HymnString *)s;
}
HymnString *hymn_string_append_char(HymnString *string, const char b) {
HymnStringHead *head = hymn_string_head(string);
size_t len = head->length + 1;
if (len > head->capacity) {
head = string_resize(head, len * 2);
}
head->length = len;
char *s = (char *)(head + 1);
s[len - 1] = b;
s[len] = '\0';
return (HymnString *)s;
}
HymnString *hymn_string_append_substring(HymnString *string, const char *b, size_t start, size_t end) {
HymnStringHead *head = hymn_string_head(string);
size_t len_a = head->length;
size_t len_b = end - start;
size_t len = len_a + len_b;
if (len > head->capacity) {
head = string_resize(head, len * 2);
}
head->length = len;
char *s = (char *)(head + 1);
memcpy(s + len_a, &b[start], len_b);
s[len] = '\0';
return (HymnString *)s;
}
static bool string_starts_with(const char *t, const char *s) {
size_t tlen = strlen(t);
size_t slen = strlen(s);
return tlen < slen ? false : memcmp(t, s, slen) == 0;
}
bool hymn_string_starts_with(HymnString *s, const char *using) {
size_t slen = hymn_string_len(s);
size_t ulen = strlen(using);
return slen < ulen ? false : memcmp(s, using, ulen) == 0;
}
static bool string_find(HymnString *string, HymnString *sub, size_t *out) {
HymnStringHead *head = hymn_string_head(string);
HymnStringHead *head_sub = hymn_string_head(sub);
size_t len = head->length;
size_t len_sub = head_sub->length;
if (len_sub > len) {
return false;
} else if (len == 0) {
*out = 0;
return true;
}
size_t end = len - len_sub + 1;
for (size_t i = 0; i < end; i++) {
bool match = true;
for (size_t k = 0; k < len_sub; k++) {
if (sub[k] != string[i + k]) {
match = false;
break;
}
}
if (match) {
*out = i;
return true;
}
}
return false;
}
HymnString *hymn_string_replace(HymnString *string, const char *find, const char *replace) {
HymnStringHead *head = hymn_string_head(string);
size_t len = head->length;
size_t len_sub = strlen(find);
if (len == 0) return hymn_new_string("");
if (len_sub == 0 || len_sub > len) return hymn_string_copy(string);
HymnString *out = hymn_new_string_with_capacity(len);
size_t end = len - len_sub + 1;
size_t pos = 0;
for (size_t i = 0; i < end; i++) {
bool match = true;
for (size_t k = 0; k < len_sub; k++) {
if (find[k] != string[i + k]) {
match = false;
break;
}
}
if (match) {
out = hymn_string_append_substring(out, string, pos, i);
out = hymn_string_append(out, replace);
pos = i + len_sub;
i = pos - 1;
}
}
if (pos < len) out = hymn_string_append_substring(out, string, pos, len);
return out;
}
static HymnString *char_to_string(char ch) {
HymnString *s = hymn_new_empty_string(1);
s[0] = ch;
return s;
}
HymnString *hymn_int_to_string(HymnInt number) {
size_t len = (size_t)snprintf(NULL, 0, "%lld", number);
char *str = hymn_malloc(len + 1);
snprintf(str, len + 1, "%lld", number);
HymnString *s = hymn_new_string_with_length(str, len);
free(str);
return s;
}
HymnString *hymn_float_to_string(HymnFloat number) {
size_t len = (size_t)snprintf(NULL, 0, "%g", number);
char *str = hymn_malloc(len + 1);
snprintf(str, len + 1, "%g", number);
HymnString *s = hymn_new_string_with_length(str, len);
free(str);
return s;
}
static char *string_to_chars(HymnString *this) {
size_t len = hymn_string_len(this);
char *s = hymn_malloc((len + 1) * sizeof(char));
memcpy(s, this, len);
s[len] = '\0';
return s;
}
HymnString *hymn_string_format(const char *format, ...) {
va_list args;
va_start(args, format);
size_t len = (size_t)vsnprintf(NULL, 0, format, args);
va_end(args);
char *chars = hymn_malloc((len + 1) * sizeof(char));
va_start(args, format);
len = (size_t)vsnprintf(chars, len + 1, format, args);
va_end(args);
HymnString *str = hymn_new_string_with_length(chars, len);
free(chars);
return str;
}
static HymnString *string_append_format(HymnString *this, const char *format, ...) {
va_list args;
va_start(args, format);
size_t len = (size_t)vsnprintf(NULL, 0, format, args);
va_end(args);
char *chars = hymn_malloc((len + 1) * sizeof(char));
va_start(args, format);
len = (size_t)vsnprintf(chars, len + 1, format, args);
va_end(args);
this = hymn_string_append(this, chars);
free(chars);
return this;
}
HymnString *hymn_working_directory(void) {
char path[PATH_MAX];
if (getcwd(path, sizeof(path)) != NULL) {
return hymn_new_string(path);
}
return NULL;
}
HymnString *hymn_path_convert(HymnString *path) {
size_t size = hymn_string_len(path);
HymnString *convert = hymn_string_copy(path);
for (size_t i = 0; i < size; i++) {
if (convert[i] == PATH_SEP_OTHER) {
convert[i] = PATH_SEP;
}
}
return convert;
}
HymnString *hymn_path_normalize(HymnString *path) {
size_t i = 0;
size_t size = hymn_string_len(path);
if (size > 1 && path[0] == '.') {
if (path[1] == '.') {
if (size > 2 && path[2] == PATH_SEP) {
i = 3;
}
} else if (path[1] == PATH_SEP) {
i = 2;
}
}
size_t n = 0;
char normal[PATH_MAX];
while (i < size) {
if (path[i] == PATH_SEP) {
if (i + 2 < size) {
if (path[i + 1] == '.' && path[i + 2] == PATH_SEP) {
i += 2;
continue;
} else if (path[i + 2] == '.' && i + 3 < size && path[i + 3] == PATH_SEP) {
if (n > 0) {
n--;
while (n > 0) {
if (normal[n] == PATH_SEP) {
break;
}
n--;
}
}
i += 3;
continue;
}
}
}
normal[n] = path[i];
n++;
i++;
}
normal[n] = '\0';
return hymn_new_string(normal);
}
HymnString *hymn_path_parent(HymnString *path) {
size_t size = hymn_string_len(path);
if (size < 2) {
return hymn_string_copy(path);
}
size_t index = size - 2;
while (true) {
if (index == 0 || path[index] == PATH_SEP) {
return hymn_substring(path, 0, index);
}
index--;
}
}
HymnString *hymn_path_absolute(HymnString *path) {
HymnString *working = hymn_working_directory();
if (hymn_string_starts_with(path, working)) {
hymn_string_delete(working);
return hymn_path_normalize(path);
}
working = hymn_string_append_char(working, PATH_SEP);
working = hymn_string_append(working, path);
HymnString *normal = hymn_path_normalize(working);
hymn_string_delete(working);
return normal;
}
size_t hymn_file_size(const char *path) {
FILE *open = hymn_open_file(path, "r");
if (open == NULL) {
return 0;
}
size_t size = 0;
int ch;
while ((ch = fgetc(open)) != EOF) {
size++;
}
fclose(open);
return size;
}
HymnString *hymn_read_file(const char *path) {
size_t size = hymn_file_size(path);
FILE *open = hymn_open_file(path, "r");
if (open == NULL) {
return NULL;
}
HymnString *string = hymn_new_string_with_capacity(size);
HymnStringHead *head = hymn_string_head(string);
for (size_t i = 0; i < size; i++) {
string[i] = (char)fgetc(open);
}
fclose(open);
head->length = size;
return string;
}
bool hymn_file_exists(const char *path) {
struct stat b;
return stat(path, &b) == 0;
}
#ifdef _MSC_VER
#define ANSI_COLOR_RED ""
#define ANSI_COLOR_RESET ""
#else
#define ANSI_COLOR_RED "\x1b[31m"
#define ANSI_COLOR_RESET "\x1b[0m"
#endif
typedef struct JumpList JumpList;
typedef struct LoopList LoopList;
typedef struct Token Token;
typedef struct Local Local;
typedef struct Rule Rule;
typedef struct Scope Scope;
typedef struct Compiler Compiler;
typedef struct CompileResult CompileResult;
typedef struct Instruction Instruction;
typedef struct Optimizer Optimizer;
static const float LOAD_FACTOR = 0.80f;
static const unsigned int INITIAL_BINS = 1 << 3;
static const unsigned int MAXIMUM_BINS = 1 << 30;
enum TokenType {
TOKEN_ADD,
TOKEN_AND,
TOKEN_ASSIGN,
TOKEN_ASSIGN_ADD,
TOKEN_ASSIGN_BIT_AND,
TOKEN_ASSIGN_BIT_LEFT_SHIFT,
TOKEN_ASSIGN_BIT_OR,
TOKEN_ASSIGN_BIT_RIGHT_SHIFT,
TOKEN_ASSIGN_BIT_XOR,
TOKEN_ASSIGN_DIVIDE,
TOKEN_ASSIGN_MODULO,
TOKEN_ASSIGN_MULTIPLY,
TOKEN_ASSIGN_SUBTRACT,
TOKEN_BIT_AND,
TOKEN_BIT_LEFT_SHIFT,
TOKEN_BIT_NOT,
TOKEN_BIT_OR,
TOKEN_BIT_RIGHT_SHIFT,
TOKEN_BIT_XOR,
TOKEN_BREAK,
TOKEN_CLEAR,
TOKEN_COLON,
TOKEN_COMMA,
TOKEN_CONTINUE,
TOKEN_COPY,
TOKEN_OPCODES,
TOKEN_STACK,
TOKEN_REFERENCE,
TOKEN_DELETE,
TOKEN_DIVIDE,
TOKEN_DOT,
TOKEN_ECHO,
TOKEN_ELIF,
TOKEN_ELSE,
TOKEN_EOF,
TOKEN_EQUAL,
TOKEN_ERROR,
TOKEN_EXCEPT,
TOKEN_EXISTS,
TOKEN_FALSE,
TOKEN_FLOAT,
TOKEN_FOR,
TOKEN_FORMAT,
TOKEN_FUNCTION,
TOKEN_GREATER,
TOKEN_GREATER_EQUAL,
TOKEN_IDENT,
TOKEN_IF,
TOKEN_IN,
TOKEN_INDEX,
TOKEN_INSERT,
TOKEN_SRC,
TOKEN_INTEGER,
TOKEN_KEYS,
TOKEN_LEFT_CURLY,
TOKEN_LEFT_PAREN,
TOKEN_LEFT_SQUARE,
TOKEN_LEN,
TOKEN_LESS,
TOKEN_LESS_EQUAL,
TOKEN_SET,
TOKEN_MODULO,
TOKEN_MULTIPLY,
TOKEN_NONE,
TOKEN_NOT,
TOKEN_NOT_EQUAL,
TOKEN_OR,
TOKEN_POINTER,
TOKEN_POP,
TOKEN_PRINT,
TOKEN_PUSH,
TOKEN_RETURN,
TOKEN_RIGHT_CURLY,
TOKEN_RIGHT_PAREN,
TOKEN_RIGHT_SQUARE,
TOKEN_STRING,
TOKEN_SUBTRACT,
TOKEN_THROW,
TOKEN_TO_FLOAT,
TOKEN_TO_INTEGER,
TOKEN_TO_STRING,
TOKEN_TRUE,
TOKEN_TRY,
TOKEN_TYPE_FUNC,
TOKEN_UNDEFINED,
TOKEN_USE,
TOKEN_VALUE,
TOKEN_WHILE,
};
enum Precedence {
PRECEDENCE_NONE,
PRECEDENCE_ASSIGN,
PRECEDENCE_BITS,
PRECEDENCE_OR,
PRECEDENCE_AND,
PRECEDENCE_EQUALITY,
PRECEDENCE_COMPARE,
PRECEDENCE_TERM,
PRECEDENCE_FACTOR,
PRECEDENCE_UNARY,
PRECEDENCE_CALL,
};
enum StringStatus {
STRING_STATUS_NONE,
STRING_STATUS_BEGIN,
STRING_STATUS_ADD,
STRING_STATUS_CLOSE,
STRING_STATUS_CONTINUE,
};
enum OpCode {
OP_ADD,
OP_ADD_LOCALS,
OP_INCREMENT,
OP_INSERT,
OP_ARRAY_POP,
OP_ARRAY_PUSH,
OP_ARRAY_PUSH_LOCALS,
OP_BIT_AND,
OP_BIT_LEFT_SHIFT,
OP_BIT_NOT,
OP_BIT_OR,
OP_BIT_RIGHT_SHIFT,
OP_BIT_XOR,
OP_CALL,
OP_TAIL_CALL,
OP_SELF,
OP_CLEAR,
OP_CONSTANT,
OP_COPY,
OP_CODES,
OP_STACK,
OP_REFERENCE,
OP_FORMAT,
OP_DEFINE_GLOBAL,
OP_DELETE,
OP_DIVIDE,
OP_DUPLICATE,
OP_ECHO,
OP_EQUAL,
OP_EXISTS,
OP_FALSE,
OP_GET_DYNAMIC,
OP_GET_GLOBAL,
OP_GET_GLOBAL_PROPERTY,
OP_GET_LOCAL,
OP_GET_LOCALS,
OP_GET_PROPERTY,
OP_GREATER,
OP_GREATER_EQUAL,
OP_INDEX,
OP_SOURCE,
OP_JUMP,
OP_JUMP_IF_EQUAL,
OP_JUMP_IF_NOT_EQUAL,
OP_JUMP_IF_LESS,
OP_JUMP_IF_GREATER,
OP_JUMP_IF_GREATER_LOCALS,
OP_JUMP_IF_LESS_EQUAL,
OP_JUMP_IF_GREATER_EQUAL,
OP_JUMP_IF_FALSE,
OP_JUMP_IF_TRUE,
OP_KEYS,
OP_LEN,
OP_LESS,
OP_LESS_EQUAL,
OP_LOOP,
OP_MODULO,
OP_MODULO_LOCALS,
OP_MULTIPLY,
OP_NEGATE,
OP_NEW_ARRAY,
OP_NEW_TABLE,
OP_NONE,
OP_NOT,
OP_NOT_EQUAL,
OP_POP,
OP_POP_TWO,
OP_POP_N,
OP_PRINT,
OP_RETURN,
OP_SET_DYNAMIC,
OP_SET_GLOBAL,
OP_SET_LOCAL,
OP_SET_PROPERTY,
OP_INCREMENT_LOCAL,
OP_INCREMENT_LOCAL_AND_SET,
OP_INCREMENT_LOOP,
OP_SLICE,
OP_SUBTRACT,
OP_THROW,
OP_FLOAT,
OP_INT,
OP_STRING,
OP_TRUE,
OP_TYPE,
OP_USE,
OP_FOR,
OP_FOR_LOOP,
OP_VOID,
};
enum FunctionType {
TYPE_FUNCTION,
TYPE_SCRIPT,
TYPE_DIRECT,
TYPE_REPL,
};
static void compile_with_precedence(Compiler *C, enum Precedence precedence);
static void compile_call(Compiler *C, bool assign);
static void compile_group(Compiler *C, bool assign);
static void compile_none(Compiler *C, bool assign);
static void compile_true(Compiler *C, bool assign);
static void compile_false(Compiler *C, bool assign);
static void compile_integer(Compiler *C, bool assign);
static void compile_float(Compiler *C, bool assign);
static void compile_string(Compiler *C, bool assign);
static void compile_array(Compiler *C, bool assign);
static void compile_table(Compiler *C, bool assign);
static void compile_variable(Compiler *C, bool assign);
static void compile_unary(Compiler *C, bool assign);
static void compile_binary(Compiler *C, bool assign);
static void compile_dot(Compiler *C, bool assign);
static void compile_pointer(Compiler *C, bool assign);
static void compile_square(Compiler *C, bool assign);
static void compile_and(Compiler *C, bool assign);
static void compile_or(Compiler *C, bool assign);
static void array_pop_expression(Compiler *C, bool assign);
static void delete_expression(Compiler *C, bool assign);
static void len_expression(Compiler *C, bool assign);
static void cast_integer_expression(Compiler *C, bool assign);
static void cast_float_expression(Compiler *C, bool assign);
static void cast_string_expression(Compiler *C, bool assign);
static void clear_expression(Compiler *C, bool assign);
static void copy_expression(Compiler *C, bool assign);
static void index_expression(Compiler *C, bool assign);
static void keys_expression(Compiler *C, bool assign);
static void type_expression(Compiler *C, bool assign);
static void exists_expression(Compiler *C, bool assign);
static void source_expression(Compiler *C, bool assign);
static void opcode_expression(Compiler *C, bool assign);
static void stack_expression(Compiler *C, bool assign);
static void reference_expression(Compiler *C, bool assign);
static void format_expression(Compiler *C, bool assign);
static void function_expression(Compiler *C, bool assign);
static void declaration(Compiler *C);
static void statement(Compiler *C);
static void expression_statement(Compiler *C);
static void expression(Compiler *C);
static char *interpret(Hymn *H);
struct JumpList {
int jump;
int depth;
HymnByteCode *code;
JumpList *next;
};
struct LoopList {
int start;
int depth;
HymnByteCode *code;
LoopList *next;
bool is_for;
char padding[7];
};
struct Token {
HymnInt integer;
HymnFloat floating;
int row;
int column;
size_t start;
unsigned int length;
enum TokenType type;
};
struct Local {
Token name;
int depth;
char padding[4];
};
struct Rule {
void (*prefix)(Compiler *, bool);
void (*infix)(Compiler *, bool);
enum Precedence precedence;
char padding[4];
};
struct Scope {
struct Scope *enclosing;
HymnFunction *func;
size_t begin;
Local locals[HYMN_UINT8_COUNT];
int local_count;
int depth;
enum FunctionType type;
char padding[4];
};
struct Compiler {
Hymn *H;
Scope *scope;
LoopList *loop;
JumpList *jump;
JumpList *jump_or;
JumpList *jump_and;
JumpList *jump_for;
HymnString *error;
const char *script;
const char *source;
size_t pos;
size_t size;
int pop;
int barrier;
int row;
int column;
Token previous;
Token current;
int string_format;
enum StringStatus string_status;
bool interactive;
char padding[7];
};
struct CompileResult {
HymnFunction *func;
char *error;
};
HymnValue hymn_new_undefined(void) {
return (HymnValue){.is = HYMN_VALUE_UNDEFINED, .as = {.i = 0}};
}
HymnValue hymn_new_none(void) {
return (HymnValue){.is = HYMN_VALUE_NONE, .as = {.i = 0}};
}
HymnValue hymn_new_bool(bool v) {
return (HymnValue){.is = HYMN_VALUE_BOOL, .as = {.b = v}};
}
HymnValue hymn_new_int(HymnInt v) {
return (HymnValue){.is = HYMN_VALUE_INTEGER, .as = {.i = v}};
}
HymnValue hymn_new_float(HymnFloat v) {
return (HymnValue){.is = HYMN_VALUE_FLOAT, .as = {.f = v}};
}
HymnValue hymn_new_native(HymnNativeFunction *v) {
return (HymnValue){.is = HYMN_VALUE_FUNC_NATIVE, .as = {.o = (void *)v}};
}
HymnValue hymn_new_pointer(void *v) {
return (HymnValue){.is = HYMN_VALUE_POINTER, .as = {.p = v}};
}
HymnValue hymn_new_string_value(HymnObjectString *v) {
return (HymnValue){.is = HYMN_VALUE_STRING, .as = {.o = (void *)v}};
}
HymnValue hymn_new_array_value(HymnArray *v) {
return (HymnValue){.is = HYMN_VALUE_ARRAY, .as = {.o = (void *)v}};
}
HymnValue hymn_new_table_value(HymnTable *v) {
return (HymnValue){.is = HYMN_VALUE_TABLE, .as = {.o = (void *)v}};
}
HymnValue hymn_new_func_value(HymnFunction *v) {
return (HymnValue){.is = HYMN_VALUE_FUNC, .as = {.o = (void *)v}};
}
bool hymn_as_bool(HymnValue v) {
return (v).as.b;
}
HymnInt hymn_as_int(HymnValue v) {
return (v).as.i;
}
HymnFloat hymn_as_float(HymnValue v) {
return (v).as.f;
}
HymnNativeFunction *hymn_as_native(HymnValue v) {
return (HymnNativeFunction *)(v).as.o;
}
void *hymn_as_pointer(HymnValue v) {
return (v).as.p;
}
void *hymn_as_object(HymnValue v) {
return (void *)(v).as.o;
}
HymnObjectString *hymn_as_hymn_string(HymnValue v) {
return (HymnObjectString *)(v).as.o;
}
HymnString *hymn_as_string(HymnValue v) {
return hymn_as_hymn_string(v)->string;
}
HymnArray *hymn_as_array(HymnValue v) {
return (HymnArray *)(v).as.o;
}
HymnTable *hymn_as_table(HymnValue v) {
return (HymnTable *)(v).as.o;
}
HymnFunction *hymn_as_func(HymnValue v) {
return (HymnFunction *)(v).as.o;
}
bool hymn_is_undefined(HymnValue v) {
return (v).is == HYMN_VALUE_UNDEFINED;
}
bool hymn_is_none(HymnValue v) {
return (v).is == HYMN_VALUE_NONE;
}
bool hymn_is_bool(HymnValue v) {
return (v).is == HYMN_VALUE_BOOL;
}
bool hymn_is_int(HymnValue v) {
return (v).is == HYMN_VALUE_INTEGER;
}
bool hymn_is_float(HymnValue v) {
return (v).is == HYMN_VALUE_FLOAT;
}
bool hymn_is_native(HymnValue v) {
return (v).is == HYMN_VALUE_FUNC_NATIVE;
}
bool hymn_is_pointer(HymnValue v) {
return (v).is == HYMN_VALUE_POINTER;
}
bool hymn_is_string(HymnValue v) {
return (v).is == HYMN_VALUE_STRING;
}
bool hymn_is_array(HymnValue v) {
return (v).is == HYMN_VALUE_ARRAY;
}
bool hymn_is_table(HymnValue v) {
return (v).is == HYMN_VALUE_TABLE;
}
bool hymn_is_func(HymnValue v) {
return (v).is == HYMN_VALUE_FUNC;
}
static Rule rules[] = {
[TOKEN_ADD] = {NULL, compile_binary, PRECEDENCE_TERM, {0}},
[TOKEN_AND] = {NULL, compile_and, PRECEDENCE_AND, {0}},
[TOKEN_ASSIGN] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_ADD] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_BIT_AND] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_BIT_LEFT_SHIFT] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_BIT_OR] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_BIT_RIGHT_SHIFT] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_BIT_XOR] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_DIVIDE] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_MODULO] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_MULTIPLY] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ASSIGN_SUBTRACT] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_BIT_AND] = {NULL, compile_binary, PRECEDENCE_BITS, {0}},
[TOKEN_BIT_LEFT_SHIFT] = {NULL, compile_binary, PRECEDENCE_BITS, {0}},
[TOKEN_BIT_NOT] = {compile_unary, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_BIT_OR] = {NULL, compile_binary, PRECEDENCE_BITS, {0}},
[TOKEN_BIT_RIGHT_SHIFT] = {NULL, compile_binary, PRECEDENCE_BITS, {0}},
[TOKEN_BIT_XOR] = {NULL, compile_binary, PRECEDENCE_BITS, {0}},
[TOKEN_BREAK] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_CLEAR] = {clear_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_COLON] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_COMMA] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_CONTINUE] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_COPY] = {copy_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_OPCODES] = {opcode_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_STACK] = {stack_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_REFERENCE] = {reference_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_DELETE] = {delete_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_DIVIDE] = {NULL, compile_binary, PRECEDENCE_FACTOR, {0}},
[TOKEN_DOT] = {NULL, compile_dot, PRECEDENCE_CALL, {0}},
[TOKEN_ECHO] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ELIF] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_ELSE] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_EOF] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_EQUAL] = {NULL, compile_binary, PRECEDENCE_EQUALITY, {0}},
[TOKEN_ERROR] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_EXCEPT] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_EXISTS] = {exists_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_FALSE] = {compile_false, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_FLOAT] = {compile_float, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_FOR] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_FORMAT] = {format_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_FUNCTION] = {function_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_GREATER] = {NULL, compile_binary, PRECEDENCE_COMPARE, {0}},
[TOKEN_GREATER_EQUAL] = {NULL, compile_binary, PRECEDENCE_COMPARE, {0}},
[TOKEN_IDENT] = {compile_variable, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_SRC] = {source_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_IF] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_IN] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_INDEX] = {index_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_INSERT] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_INTEGER] = {compile_integer, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_KEYS] = {keys_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_LEFT_CURLY] = {compile_table, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_LEFT_PAREN] = {compile_group, compile_call, PRECEDENCE_CALL, {0}},
[TOKEN_LEFT_SQUARE] = {compile_array, compile_square, PRECEDENCE_CALL, {0}},
[TOKEN_LEN] = {len_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_LESS] = {NULL, compile_binary, PRECEDENCE_COMPARE, {0}},
[TOKEN_LESS_EQUAL] = {NULL, compile_binary, PRECEDENCE_COMPARE, {0}},
[TOKEN_SET] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_MODULO] = {NULL, compile_binary, PRECEDENCE_FACTOR, {0}},
[TOKEN_MULTIPLY] = {NULL, compile_binary, PRECEDENCE_FACTOR, {0}},
[TOKEN_NONE] = {compile_none, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_NOT] = {compile_unary, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_NOT_EQUAL] = {NULL, compile_binary, PRECEDENCE_EQUALITY, {0}},
[TOKEN_OR] = {NULL, compile_or, PRECEDENCE_OR, {0}},
[TOKEN_POINTER] = {NULL, compile_pointer, PRECEDENCE_CALL, {0}},
[TOKEN_POP] = {array_pop_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_PRINT] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_PUSH] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_RETURN] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_RIGHT_CURLY] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_RIGHT_PAREN] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_RIGHT_SQUARE] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_STRING] = {compile_string, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_SUBTRACT] = {compile_unary, compile_binary, PRECEDENCE_TERM, {0}},
[TOKEN_THROW] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_TO_FLOAT] = {cast_float_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_TO_INTEGER] = {cast_integer_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_TO_STRING] = {cast_string_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_TRUE] = {compile_true, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_TRY] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_TYPE_FUNC] = {type_expression, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_UNDEFINED] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_USE] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_VALUE] = {NULL, NULL, PRECEDENCE_NONE, {0}},
[TOKEN_WHILE] = {NULL, NULL, PRECEDENCE_NONE, {0}},
};
const char *hymn_value_type(enum HymnValueType type) {
switch (type) {
case HYMN_VALUE_UNDEFINED: return "undefined";
case HYMN_VALUE_NONE: return "none";
case HYMN_VALUE_BOOL: return "boolean";
case HYMN_VALUE_INTEGER: return "integer";
case HYMN_VALUE_FLOAT: return "float";
case HYMN_VALUE_STRING: return "string";
case HYMN_VALUE_ARRAY: return "array";
case HYMN_VALUE_TABLE: return "table";
case HYMN_VALUE_FUNC: return "function";
case HYMN_VALUE_FUNC_NATIVE: return "native";
case HYMN_VALUE_POINTER: return "pointer";
default: return "?";
}
}
static unsigned int string_mix_code(HymnString *key) {
size_t length = hymn_string_len(key);
unsigned int hash = 0;
for (size_t i = 0; i < length; i++) {
hash = 31 * hash + (unsigned int)key[i];
}
return hash ^ (hash >> 16);
}
static unsigned int string_mix_code_const(const char *key) {
size_t length = strlen(key);
unsigned int hash = 0;
for (size_t i = 0; i < length; i++) {
hash = 31 * hash + (unsigned int)key[i];
}
return hash ^ (hash >> 16);
}
static HymnObjectString *new_hymn_string_with_hash(HymnString *string, unsigned int hash) {
HymnObjectString *object = hymn_calloc(1, sizeof(HymnObjectString));
object->hash = hash;
object->string = string;
return object;
}
HymnObjectString *hymn_new_string_object(HymnString *string) {
return new_hymn_string_with_hash(string, string_mix_code(string));
}
static void table_init(HymnTable *this) {
this->size = 0;
this->bins = INITIAL_BINS;
this->items = hymn_calloc(this->bins, sizeof(HymnTableItem *));
}
static unsigned int table_get_bin(HymnTable *this, unsigned int hash) {
return (this->bins - 1U) & hash;
}
static void table_resize(HymnTable *this) {
unsigned int old_bins = this->bins;
if (old_bins >= MAXIMUM_BINS) {
return;
}
unsigned int bins = old_bins << 1U;
HymnTableItem **old_items = this->items;
HymnTableItem **items = hymn_calloc(bins, sizeof(HymnTableItem *));
for (unsigned int i = 0; i < old_bins; i++) {
HymnTableItem *item = old_items[i];
if (item == NULL) {
continue;
}
if (item->next == NULL) {
items[(bins - 1) & item->key->hash] = item;
} else {
HymnTableItem *low_head = NULL;
HymnTableItem *low_tail = NULL;
HymnTableItem *high_head = NULL;
HymnTableItem *high_tail = NULL;
do {
if ((old_bins & item->key->hash) == 0) {
if (low_tail == NULL) {
low_head = item;
} else {
low_tail->next = item;
}
low_tail = item;
} else {
if (high_tail == NULL) {
high_head = item;
} else {
high_tail->next = item;
}
high_tail = item;
}
item = item->next;
} while (item != NULL);
if (low_tail != NULL) {
low_tail->next = NULL;
items[i] = low_head;
}
if (high_tail != NULL) {
high_tail->next = NULL;
items[i + old_bins] = high_head;
}
}
}
free(old_items);
this->bins = bins;
this->items = items;
}
static HymnValue table_put(HymnTable *this, HymnObjectString *key, HymnValue value) {
unsigned int bin = table_get_bin(this, key->hash);
HymnTableItem *item = this->items[bin];
HymnTableItem *previous = NULL;
while (item != NULL) {
if (key == item->key) {
HymnValue old = item->value;
item->value = value;
return old;
}
previous = item;
item = item->next;
}
item = hymn_malloc(sizeof(HymnTableItem));
item->key = key;
item->value = value;
item->next = NULL;
if (previous == NULL) {
this->items[bin] = item;
} else {
previous->next = item;
}
this->size++;
if (this->size >= (int)((float)this->bins * LOAD_FACTOR)) {
table_resize(this);
}
return hymn_new_undefined();
}
static HymnValue table_get(HymnTable *this, HymnObjectString *key) {
unsigned int bin = table_get_bin(this, key->hash);
HymnTableItem *item = this->items[bin];
while (item != NULL) {
if (key == item->key) {
return item->value;
}
item = item->next;
}
return hymn_new_undefined();
}
HymnValue hymn_table_get(HymnTable *this, const char *key) {
unsigned int hash = string_mix_code_const(key);
unsigned int bin = table_get_bin(this, hash);
HymnTableItem *item = this->items[bin];
while (item != NULL) {
if (hymn_string_equal(key, item->key->string)) {
return item->value;
}
item = item->next;
}
return hymn_new_undefined();
}
static HymnTableItem *table_next(HymnTable *this, HymnObjectString *key) {
unsigned int bins = this->bins;
if (key == NULL) {
for (unsigned int i = 0; i < bins; i++) {
HymnTableItem *item = this->items[i];
if (item != NULL) {
return item;
}
}
return NULL;
}
unsigned int bin = table_get_bin(this, key->hash);
{
HymnTableItem *item = this->items[bin];
while (item != NULL) {
HymnTableItem *next = item->next;
if (key == item->key) {
if (next != NULL) {
return next;
}
}
item = next;
}
}
for (unsigned int i = bin + 1; i < bins; i++) {
HymnTableItem *item = this->items[i];
if (item != NULL) {
return item;
}
}
return NULL;
}
static HymnValue table_remove(HymnTable *this, HymnObjectString *key) {
unsigned int bin = table_get_bin(this, key->hash);
HymnTableItem *item = this->items[bin];
HymnTableItem *previous = NULL;
while (item != NULL) {
if (key == item->key) {
if (previous == NULL) {
this->items[bin] = item->next;
} else {
previous->next = item->next;
}
HymnValue value = item->value;
free(item);
this->size--;
return value;
}
previous = item;
item = item->next;
}
return hymn_new_undefined();
}
static void table_clear(Hymn *H, HymnTable *this) {
this->size = 0;
unsigned int bins = this->bins;
for (unsigned int i = 0; i < bins; i++) {
HymnTableItem *item = this->items[i];
while (item != NULL) {
HymnTableItem *next = item->next;
hymn_dereference(H, item->value);
hymn_dereference_string(H, item->key);
free(item);
item = next;
}
this->items[i] = NULL;
}
}
static void table_release(Hymn *H, HymnTable *this) {
table_clear(H, this);
free(this->items);
}
static void table_delete(Hymn *H, HymnTable *this) {
table_release(H, this);
free(this);
}
void hymn_set_property(Hymn *H, HymnTable *table, HymnObjectString *name, HymnValue value) {
hymn_reference(value);
HymnValue previous = table_put(table, name, value);
if (hymn_is_undefined(previous)) {
hymn_reference_string(name);
} else {
hymn_dereference(H, previous);
}
}
void hymn_set_property_const(Hymn *H, HymnTable *table, const char *name, HymnValue value) {
HymnObjectString *key = hymn_new_intern_string(H, name);
hymn_set_property(H, table, key, value);
}
static void set_init(HymnSet *this) {
this->size = 0;
this->bins = INITIAL_BINS;
this->items = hymn_calloc(this->bins, sizeof(HymnSetItem *));
}
static unsigned int set_get_bin(HymnSet *this, unsigned int hash) {
return (this->bins - 1U) & hash;
}
static void set_resize(HymnSet *this) {
unsigned int old_bins = this->bins;
if (old_bins >= MAXIMUM_BINS) {
return;
}
unsigned int bins = old_bins << 1U;
HymnSetItem **old_items = this->items;
HymnSetItem **items = hymn_calloc(bins, sizeof(HymnSetItem *));
for (unsigned int i = 0; i < old_bins; i++) {
HymnSetItem *item = old_items[i];
if (item == NULL) {
continue;
}
if (item->next == NULL) {
items[(bins - 1) & item->string->hash] = item;
} else {
HymnSetItem *low_head = NULL;
HymnSetItem *low_tail = NULL;
HymnSetItem *high_head = NULL;
HymnSetItem *high_tail = NULL;
do {
if ((old_bins & item->string->hash) == 0) {
if (low_tail == NULL) {
low_head = item;
} else {
low_tail->next = item;
}
low_tail = item;
} else {
if (high_tail == NULL) {
high_head = item;
} else {
high_tail->next = item;
}
high_tail = item;
}
item = item->next;
} while (item != NULL);
if (low_tail != NULL) {
low_tail->next = NULL;
items[i] = low_head;
}
if (high_tail != NULL) {
high_tail->next = NULL;
items[i + old_bins] = high_head;
}
}
}
free(old_items);
this->bins = bins;
this->items = items;
}
static HymnObjectString *set_add_or_get(HymnSet *this, HymnString *add) {
unsigned int hash = string_mix_code(add);
unsigned int bin = set_get_bin(this, hash);
HymnSetItem *item = this->items[bin];
HymnSetItem *previous = NULL;
while (item != NULL) {
if (hymn_string_equal(add, item->string->string)) {
return item->string;
}
previous = item;
item = item->next;
}
HymnObjectString *new = new_hymn_string_with_hash(add, hash);
item = hymn_malloc(sizeof(HymnSetItem));
item->string = new;
item->next = NULL;
if (previous == NULL) {
this->items[bin] = item;
} else {
previous->next = item;
}
this->size++;
if (this->size >= (int)((float)this->bins * LOAD_FACTOR)) {
set_resize(this);
}
return new;
}
static HymnObjectString *set_remove(HymnSet *this, HymnString *remove) {
unsigned int hash = string_mix_code(remove);
unsigned int bin = set_get_bin(this, hash);
HymnSetItem *item = this->items[bin];
HymnSetItem *previous = NULL;
while (item != NULL) {
if (hymn_string_equal(remove, item->string->string)) {
if (previous == NULL) {
this->items[bin] = item->next;
} else {
previous->next = item->next;
}
HymnObjectString *string = item->string;
free(item);
this->size--;
return string;
}
previous = item;
item = item->next;
}
return NULL;
}
static HymnByteCode *current(Compiler *C) {
return &C->scope->func->code;
}
static void compile_error(Compiler *C, Token *token, const char *format, ...) {
if (C->error != NULL) {
return;
}
if (C->interactive && token->type == TOKEN_EOF) {
C->error = hymn_new_string("<eof>");
goto clean;
}
va_list ap;
va_start(ap, format);
size_t len = (size_t)vsnprintf(NULL, 0, format, ap);
va_end(ap);
char *chars = hymn_malloc((len + 1) * sizeof(char));
va_start(ap, format);
len = (size_t)vsnprintf(chars, len + 1, format, ap);
va_end(ap);
HymnString *error = hymn_new_string_with_capacity(len + 128);
error = hymn_string_append(error, chars);
free(chars);
if (token->type != TOKEN_EOF && token->length > 0) {
const char *source = C->source;
const size_t size = C->size;
const size_t start = token->start;
size_t begin = start;
while (true) {
if (source[begin] == '\n') {
begin++;
break;
}
if (begin == 0) {
break;
}
begin--;
}
while (true) {
if (source[begin] != ' ' || begin == size) {
break;
}
begin++;
}
size_t end = start;
while (true) {
if (source[end] == '\n' || end == size) {
break;
}
end++;
}
if (begin < end) {
error = string_append_format(error, "\n %.*s\n ", end - begin, &source[begin]);
for (int i = 0; i < (int)(start - begin); i++) {
error = hymn_string_append_char(error, ' ');
}
error = hymn_string_append(error, ANSI_COLOR_RED);
for (unsigned int i = 0; i < token->length; i++) {
error = hymn_string_append_char(error, '^');
}
error = hymn_string_append(error, ANSI_COLOR_RESET);
}
}
error = string_append_format(error, "\n at %s:%d", C->script == NULL ? "script" : C->script, token->row);
C->error = error;
clean:
C->current.type = TOKEN_EOF;
C->previous.type = TOKEN_EOF;
}
static char next_char(Compiler *C) {
size_t pos = C->pos;
if (pos == C->size) {
return '\0';
}
char c = C->source[pos];
C->pos = pos + 1;
if (c == '\n') {
C->row++;
C->column = 0;
} else {
C->column++;
}
return c;
}
static char peek_char(Compiler *C) {
if (C->pos == C->size) {
return '\0';
}
return C->source[C->pos];
}
static char peek_two_char(Compiler *C) {
if (C->pos + 1 >= C->size) {
return '\0';
}
return C->source[C->pos + 1];
}
static void token(Compiler *C, enum TokenType type) {
Token *current = &C->current;
current->type = type;
current->row = C->row;
current->column = C->column;
if (C->pos == 0) {
current->start = 0;
} else {
current->start = C->pos - 1;
}
current->length = 1;
}
static void token_special(Compiler *C, enum TokenType type, size_t offset, size_t length) {
Token *current = &C->current;
current->type = type;
current->row = C->row;
current->column = C->column;
if (C->pos < offset) {
current->start = 0;
} else {
current->start = C->pos - offset;
}
current->length = (unsigned int)length;
}
static void value_token(Compiler *C, enum TokenType type, size_t start, size_t end) {
Token *current = &C->current;
current->type = type;
current->row = C->row;
current->column = C->column;
current->start = start;
current->length = (unsigned int)(end - start);
}
static void int_token(Compiler *C, enum TokenType type, size_t start, size_t end, HymnInt integer) {
Token *current = &C->current;
current->type = type;
current->row = C->row;
current->column = C->column;
current->start = start;
current->length = (unsigned int)(end - start);
current->integer = integer;
}
static void float_token(Compiler *C, enum TokenType type, size_t start, size_t end, HymnFloat floating) {
Token *current = &C->current;
current->type = type;
current->row = C->row;
current->column = C->column;
current->start = start;
current->length = (unsigned int)(end - start);
current->floating = floating;
}
static enum TokenType ident_trie(const char *ident, int offset, const char *rest, enum TokenType type) {
int i = 0;
do {
if (ident[offset + i] != rest[i]) {
return TOKEN_UNDEFINED;
}
i++;
} while (rest[i] != '\0');
return type;
}
static enum TokenType ident_keyword(const char *ident, size_t size) {
switch (ident[0]) {
case 'o':
if (size == 2) return ident_trie(ident, 1, "r", TOKEN_OR);
break;
case 'u':
if (size == 3) return ident_trie(ident, 1, "se", TOKEN_USE);
break;
case 'a':
if (size == 3) return ident_trie(ident, 1, "nd", TOKEN_AND);
break;
case 'n':
if (size == 3) return ident_trie(ident, 1, "ot", TOKEN_NOT);
if (size == 4) return ident_trie(ident, 1, "one", TOKEN_NONE);
break;
case 'w':
if (size == 5) return ident_trie(ident, 1, "hile", TOKEN_WHILE);
break;
case 'b':
if (size == 5) return ident_trie(ident, 1, "reak", TOKEN_BREAK);
break;
case 'd':
if (size == 6) return ident_trie(ident, 1, "elete", TOKEN_DELETE);
break;
case 'r':
if (size == 6) return ident_trie(ident, 1, "eturn", TOKEN_RETURN);
break;
case 's':
if (size == 3) {
if (ident[1] == 'e' && ident[2] == 't') return TOKEN_SET;
if (ident[1] == 't' && ident[2] == 'r') return TOKEN_TO_STRING;
}
break;
case 'k':
if (size == 4) return ident_trie(ident, 1, "eys", TOKEN_KEYS);
break;
case 'c':
if (size == 4) return ident_trie(ident, 1, "opy", TOKEN_COPY);
if (size == 5) return ident_trie(ident, 1, "lear", TOKEN_CLEAR);
if (size == 8) return ident_trie(ident, 1, "ontinue", TOKEN_CONTINUE);
break;
case 'l':
if (size == 3) return ident_trie(ident, 1, "en", TOKEN_LEN);
break;
case 't':
if (size == 3) return ident_trie(ident, 1, "ry", TOKEN_TRY);
if (size == 5) return ident_trie(ident, 1, "hrow", TOKEN_THROW);
if (size == 4) {
if (ident[1] == 'r') return ident_trie(ident, 2, "ue", TOKEN_TRUE);
if (ident[1] == 'y') return ident_trie(ident, 2, "pe", TOKEN_TYPE_FUNC);
}
break;
case 'i':
if (size == 3) return ident_trie(ident, 1, "nt", TOKEN_TO_INTEGER);
if (size == 5) return ident_trie(ident, 1, "ndex", TOKEN_INDEX);
if (size == 6) return ident_trie(ident, 1, "nsert", TOKEN_INSERT);
if (size == 2) {
if (ident[1] == 'f') return TOKEN_IF;
if (ident[1] == 'n') return TOKEN_IN;
}
break;
case 'p':
if (size == 3) return ident_trie(ident, 1, "op", TOKEN_POP);
if (size == 5) return ident_trie(ident, 1, "rint", TOKEN_PRINT);
if (size == 4) return ident_trie(ident, 1, "ush", TOKEN_PUSH);
break;
case 'e':
if (size == 6) {
if (ident[1] == 'x') {
if (ident[2] == 'c') return ident_trie(ident, 3, "ept", TOKEN_EXCEPT);
if (ident[2] == 'i') return ident_trie(ident, 3, "sts", TOKEN_EXISTS);
}
} else if (size == 4) {
if (ident[1] == 'l') {
if (ident[2] == 's') {
if (ident[3] == 'e') {
return TOKEN_ELSE;
}
} else if (ident[2] == 'i' && ident[3] == 'f') {
return TOKEN_ELIF;
}
} else if (ident[1] == 'c') {
return ident_trie(ident, 2, "ho", TOKEN_ECHO);
}
}
break;
case 'f':
if (size == 3) return ident_trie(ident, 1, "or", TOKEN_FOR);
if (size == 4) return ident_trie(ident, 1, "unc", TOKEN_FUNCTION);
if (size == 5) {
if (ident[1] == 'a') return ident_trie(ident, 2, "lse", TOKEN_FALSE);
if (ident[1] == 'l') return ident_trie(ident, 2, "oat", TOKEN_TO_FLOAT);
}
break;
case 'S':
if (size == 5) return ident_trie(ident, 1, "TACK", TOKEN_STACK);
if (size == 6) return ident_trie(ident, 1, "OURCE", TOKEN_SRC);
break;
case 'F':
if (size == 6) return ident_trie(ident, 1, "ORMAT", TOKEN_FORMAT);
break;
case 'O':
if (size == 7) return ident_trie(ident, 1, "PCODES", TOKEN_OPCODES);
break;
case 'R':
if (size == 9) return ident_trie(ident, 1, "EFERENCE", TOKEN_REFERENCE);
break;
default:
break;
}
return TOKEN_UNDEFINED;
}
static void push_ident_token(Compiler *C, size_t start, size_t end) {
const char *ident = &C->source[start];
size_t size = end - start;
enum TokenType keyword = ident_keyword(ident, size);
if (keyword != TOKEN_UNDEFINED && C->previous.type != TOKEN_DOT) {
value_token(C, keyword, start, end);
} else {
value_token(C, TOKEN_IDENT, start, end);
}
}
static bool is_digit(char c) {
return c >= '0' && c <= '9';
}
static bool is_ident(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
static enum StringStatus string_status(Compiler *C) {
size_t i = C->pos;
const char *source = C->source;
const size_t size = C->size;
bool expression = false;
int brackets = 1;
while (true) {
if (i >= size) return false;
switch (source[i]) {
case '}':
if (brackets > 1) {
expression = true;
i++;
brackets--;
continue;
}
return expression ? STRING_STATUS_BEGIN : STRING_STATUS_CONTINUE;
case '"':
return STRING_STATUS_NONE;
case ' ':
case '\t':
case '\r':
case '\n':
i++;
continue;
case '{':
expression = true;
i++;
brackets++;
continue;
default:
expression = true;
i++;
continue;
}
}
}
static void parse_string(Compiler *C, size_t start) {
while (true) {
char c = next_char(C);
if (c == '\\') {
next_char(C);
continue;
} else if (c == '$') {
if (peek_char(C) == '{') {
next_char(C);
enum StringStatus status = string_status(C);
if (status == STRING_STATUS_BEGIN) {
C->string_format = 1;
C->string_status = STRING_STATUS_BEGIN;
size_t end = C->pos - 2;
value_token(C, TOKEN_STRING, start, end);
return;
} else if (status == STRING_STATUS_CONTINUE) {
C->string_status = STRING_STATUS_CONTINUE;
size_t end = C->pos - 2;
value_token(C, TOKEN_STRING, start, end);
while (true) {
c = next_char(C);
if (c == '}' || c == '\0') {
return;
}
}
} else {
continue;
}
}
} else if (c == '"' || c == '\0') {
break;
}
}
size_t end = C->pos - 1;
value_token(C, TOKEN_STRING, start, end);
}
static void advance(Compiler *C) {
C->previous = C->current;
if (C->previous.type == TOKEN_EOF) {
return;
}
switch (C->string_status) {
case STRING_STATUS_BEGIN:
C->string_status = STRING_STATUS_ADD;
token(C, TOKEN_ADD);
return;
case STRING_STATUS_ADD:
C->string_status = STRING_STATUS_NONE;
token(C, TOKEN_LEFT_PAREN);
return;
case STRING_STATUS_CLOSE:
C->string_status = STRING_STATUS_CONTINUE;
token(C, TOKEN_ADD);
return;
case STRING_STATUS_CONTINUE: {
C->string_status = STRING_STATUS_NONE;
size_t start = C->pos;
parse_string(C, start);
return;
}
default:
break;
}
while (true) {
char c = next_char(C);
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
c = peek_char(C);
while (c != '\0' && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
next_char(C);
c = peek_char(C);
}
continue;
case '#': {
next_char(C);
c = peek_char(C);
while (c != '\n' && c != '\0') {
next_char(C);
c = peek_char(C);
}
continue;
}
case '!':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_NOT_EQUAL, 2, 2);
} else {
token(C, TOKEN_ERROR);
compile_error(C, &C->current, "expected '!='");
}
return;
case '=':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_EQUAL, 2, 2);
} else {
token(C, TOKEN_ASSIGN);
}
return;
case '-': {
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_SUBTRACT, 2, 2);
return;
} else if (peek_char(C) == '>') {
next_char(C);
token_special(C, TOKEN_POINTER, 2, 2);
return;
} else {
token(C, TOKEN_SUBTRACT);
return;
}
}
case '+':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_ADD, 2, 2);
} else {
token(C, TOKEN_ADD);
}
return;
case '*':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_MULTIPLY, 2, 2);
} else {
token(C, TOKEN_MULTIPLY);
}
return;
case '/':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_DIVIDE, 2, 2);
} else {
token(C, TOKEN_DIVIDE);
}
return;
case '%':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_MODULO, 2, 2);
} else {
token(C, TOKEN_MODULO);
}
return;
case '&':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_BIT_AND, 2, 2);
} else {
token(C, TOKEN_BIT_AND);
}
return;
case '|':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_BIT_OR, 2, 2);
} else {
token(C, TOKEN_BIT_OR);
}
return;
case '^':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_BIT_XOR, 2, 2);
} else {
token(C, TOKEN_BIT_XOR);
}
return;
case '>':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_GREATER_EQUAL, 2, 2);
} else if (peek_char(C) == '>') {
next_char(C);
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_BIT_RIGHT_SHIFT, 2, 2);
} else {
token_special(C, TOKEN_BIT_RIGHT_SHIFT, 2, 2);
}
} else {
token(C, TOKEN_GREATER);
}
return;
case '<':
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_LESS_EQUAL, 2, 2);
} else if (peek_char(C) == '<') {
next_char(C);
if (peek_char(C) == '=') {
next_char(C);
token_special(C, TOKEN_ASSIGN_BIT_LEFT_SHIFT, 2, 2);
} else {
token_special(C, TOKEN_BIT_LEFT_SHIFT, 2, 2);
}
} else {
token(C, TOKEN_LESS);
}
return;
case '~': token(C, TOKEN_BIT_NOT); return;
case ',': token(C, TOKEN_COMMA); return;
case '.': token(C, TOKEN_DOT); return;
case '(': token(C, TOKEN_LEFT_PAREN); return;
case ')': token(C, TOKEN_RIGHT_PAREN); return;
case '[': token(C, TOKEN_LEFT_SQUARE); return;
case ']': token(C, TOKEN_RIGHT_SQUARE); return;
case '{':
if (C->string_format > 0) {
C->string_format++;
}
token(C, TOKEN_LEFT_CURLY);
return;
case '}':
if (C->string_format == 1) {
C->string_format = 0;
C->string_status = STRING_STATUS_CLOSE;
token(C, TOKEN_RIGHT_PAREN);
return;
} else if (C->string_format > 1) {
C->string_format--;
}
token(C, TOKEN_RIGHT_CURLY);
return;
case ':': token(C, TOKEN_COLON); return;
case '\0': token(C, TOKEN_EOF); return;
case '"': {
size_t start = C->pos;
parse_string(C, start);
return;
}
case '\'': {
size_t start = C->pos;
while (true) {
c = next_char(C);
if (c == '\\') {
next_char(C);
continue;
} else if (c == '\'' || c == '\0') {
break;
}
}
size_t end = C->pos - 1;
value_token(C, TOKEN_STRING, start, end);
return;
}
default: {
if (is_digit(c)) {
size_t start = C->pos - 1;
if (c == '0') {
const char p = peek_char(C);
if (p == 'b') {
next_char(C);
while (true) {
c = peek_char(C);
if (c != '0' && c != '1') {
break;
}
next_char(C);
}
size_t end = C->pos;
long long number = (long long)strtoll(&C->source[start + 2], NULL, 2);
int_token(C, TOKEN_INTEGER, start, end, number);
return;
} else if (p == 'x') {
next_char(C);
while (true) {
c = peek_char(C);
if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f')) {
break;
}
next_char(C);
}
size_t end = C->pos;
long long number = (long long)strtoll(&C->source[start + 2], NULL, 16);
int_token(C, TOKEN_INTEGER, start, end, number);
return;
}
}
while (is_digit(peek_char(C))) {
next_char(C);
}
const char p = peek_char(C);
if (p == '.') {
next_char(C);
while (is_digit(peek_char(C))) {
next_char(C);
}
const char n = peek_char(C);
if (n == 'e' || n == 'E') {
next_char(C);
const char e = peek_char(C);
if (e == '-' || e == '+') next_char(C);
while (is_digit(peek_char(C))) {
next_char(C);
}
}
size_t end = C->pos;
double number = atof(&C->source[start]);
float_token(C, TOKEN_FLOAT, start, end, number);
return;
} else if (p == 'e' || p == 'E') {
next_char(C);
const char n = peek_char(C);
if (n == '-' || n == '+') next_char(C);
while (is_digit(peek_char(C))) {
next_char(C);
}
size_t end = C->pos;
double number = atof(&C->source[start]);
if (trunc(number) == number) {
int_token(C, TOKEN_INTEGER, start, end, (long long)number);
} else {
float_token(C, TOKEN_FLOAT, start, end, number);
}
return;
}
size_t end = C->pos;
long long number = atoll(&C->source[start]);
int_token(C, TOKEN_INTEGER, start, end, number);
return;
} else if (is_ident(c)) {
size_t start = C->pos - 1;
while (true) {
c = peek_char(C);
if (is_ident(c)) {
next_char(C);
continue;
} else if (c == '-') {
if (is_ident(peek_two_char(C))) {
next_char(C);
next_char(C);
continue;
}
}
break;
}
size_t end = C->pos;
push_ident_token(C, start, end);
return;
} else {
token(C, TOKEN_ERROR);
compile_error(C, &C->current, "unknown character: %c", c);
}
}
}
}
}
bool hymn_value_false(HymnValue value) {
switch (value.is) {
case HYMN_VALUE_NONE: return true;
case HYMN_VALUE_BOOL: return !hymn_as_bool(value);
case HYMN_VALUE_INTEGER: return hymn_as_int(value) == 0;
case HYMN_VALUE_FLOAT: return hymn_as_float(value) == 0.0;
case HYMN_VALUE_STRING: return hymn_string_len(hymn_as_string(value)) == 0;
case HYMN_VALUE_ARRAY: return hymn_as_array(value)->length == 0;
case HYMN_VALUE_TABLE: return hymn_as_table(value)->size == 0;
case HYMN_VALUE_FUNC: return hymn_as_func(value) == NULL;
case HYMN_VALUE_FUNC_NATIVE: return hymn_as_native(value) == NULL;
default: return false;
}
}
bool hymn_values_equal(HymnValue a, HymnValue b) {
switch (a.is) {
case HYMN_VALUE_NONE: return hymn_is_none(b);
case HYMN_VALUE_BOOL: return hymn_is_bool(b) && hymn_as_bool(a) == hymn_as_bool(b);
case HYMN_VALUE_INTEGER:
switch (b.is) {
case HYMN_VALUE_INTEGER: return hymn_as_int(a) == hymn_as_int(b);
case HYMN_VALUE_FLOAT: return (HymnFloat)hymn_as_int(a) == hymn_as_float(b);
default: return false;
}
case HYMN_VALUE_FLOAT:
switch (b.is) {
case HYMN_VALUE_INTEGER: return hymn_as_float(a) == (HymnFloat)hymn_as_int(b);
case HYMN_VALUE_FLOAT: return hymn_as_float(a) == hymn_as_float(b);
default: return false;
}
case HYMN_VALUE_STRING:
case HYMN_VALUE_ARRAY:
case HYMN_VALUE_TABLE:
case HYMN_VALUE_FUNC:
case HYMN_VALUE_FUNC_NATIVE:
return b.is == a.is && hymn_as_object(a) == hymn_as_object(b);
case HYMN_VALUE_POINTER:
return hymn_is_pointer(b) && hymn_as_pointer(a) == hymn_as_pointer(b);
default: return false;
}
}
bool hymn_match_values(HymnValue a, HymnValue b) {
if (a.is != b.is) {
return false;
}
switch (a.is) {
case HYMN_VALUE_UNDEFINED:
case HYMN_VALUE_NONE: return true;
case HYMN_VALUE_BOOL: return hymn_as_bool(a) == hymn_as_bool(b);
case HYMN_VALUE_INTEGER: return hymn_as_int(a) == hymn_as_int(b);
case HYMN_VALUE_FLOAT: return hymn_as_float(a) == hymn_as_float(b);
case HYMN_VALUE_STRING:
case HYMN_VALUE_ARRAY:
case HYMN_VALUE_TABLE:
case HYMN_VALUE_FUNC:
case HYMN_VALUE_FUNC_NATIVE:
return hymn_as_object(a) == hymn_as_object(b);
case HYMN_VALUE_POINTER: return hymn_as_pointer(a) == hymn_as_pointer(b);
default:
return false;
}
}
static void value_pool_init(HymnValuePool *this) {
this->count = 0;
this->capacity = 8;
this->values = hymn_malloc(8 * sizeof(HymnValue));
}
static int value_pool_add(HymnValuePool *this, HymnValue value) {
int count = this->count;
for (int c = 0; c < count; c++) {
if (hymn_match_values(this->values[c], value)) {
return c;
}
}
if (count >= this->capacity) {
this->capacity *= 2;
this->values = hymn_realloc_int(this->values, this->capacity, sizeof(HymnValue));
}
this->values[count] = value;
this->count = count + 1;
return count;
}
static void byte_code_init(HymnByteCode *this) {
this->count = 0;
this->capacity = 8;
this->instructions = hymn_malloc(8 * sizeof(uint8_t));
this->lines = hymn_malloc(8 * sizeof(int));
value_pool_init(&this->constants);
}
static HymnNativeFunction *new_native_function(HymnObjectString *name, HymnNativeCall func) {
HymnNativeFunction *native = hymn_calloc(1, sizeof(HymnNativeFunction));
native->name = name;
native->func = func;
hymn_reference_string(name);
return native;
}
static void array_init_with_capacity(HymnArray *this, HymnInt length, HymnInt capacity) {
if (capacity == 0) {
this->items = NULL;
} else {
this->items = hymn_calloc((size_t)capacity, sizeof(HymnValue));
}
this->length = length;
this->capacity = capacity;
}
static HymnArray *new_array_with_capacity(HymnInt length, HymnInt capacity) {
HymnArray *this = hymn_calloc(1, sizeof(HymnArray));
array_init_with_capacity(this, length, capacity);
return this;
}
HymnArray *hymn_new_array(HymnInt length) {
return new_array_with_capacity(length, length);
}
static HymnArray *new_array_slice(HymnArray *from, HymnInt start, HymnInt end) {
HymnInt length = end - start;
size_t size = (size_t)length * sizeof(HymnValue);
HymnArray *this = hymn_calloc(1, sizeof(HymnArray));
this->items = hymn_malloc(size);
memcpy(this->items, &from->items[start], size);
this->length = length;
this->capacity = length;
for (HymnInt i = 0; i < length; i++) {
hymn_reference(this->items[i]);
}
return this;
}
static HymnArray *new_array_copy(HymnArray *from) {
return new_array_slice(from, 0, from->length);
}
static void array_update_capacity(HymnArray *this, HymnInt length) {
if (length > this->capacity) {
if (this->capacity == 0) {
this->capacity = length;
this->items = hymn_calloc((size_t)length, sizeof(HymnValue));
} else {
this->capacity = length * 2;
this->items = hymn_realloc(this->items, (size_t)this->capacity * sizeof(HymnValue));
memset(this->items + (size_t)this->length, 0, (size_t)(this->capacity - this->length));
}
}
}
void hymn_array_push(HymnArray *this, HymnValue value) {
HymnInt length = this->length + 1;
array_update_capacity(this, length);
this->length = length;
this->items[length - 1] = value;
}
void hymn_array_insert(HymnArray *this, HymnInt index, HymnValue value) {
HymnInt length = this->length + 1;
array_update_capacity(this, length);
this->length = length;
HymnValue *items = this->items;
for (HymnInt i = length - 1; i > index; i--) {
items[i] = items[i - 1];
}
items[index] = value;
}
HymnValue hymn_array_get(HymnArray *this, HymnInt index) {
if (index >= this->length) {
return hymn_new_undefined();
}
return this->items[index];
}
HymnInt hymn_array_index_of(HymnArray *this, HymnValue match) {
HymnInt len = this->length;
HymnValue *items = this->items;
for (HymnInt i = 0; i < len; i++) {
if (hymn_match_values(match, items[i])) {
return i;
}
}
return -1;
}
HymnValue hymn_array_pop(HymnArray *this) {
if (this->length == 0) {
return hymn_new_none();
}
return this->items[--this->length];
}
HymnValue hymn_array_remove_index(HymnArray *this, HymnInt index) {
HymnInt len = --this->length;
HymnValue *items = this->items;
HymnValue deleted = items[index];
while (index < len) {
items[index] = items[index + 1];
index++;
}
return deleted;
}
void hymn_array_clear(Hymn *H, HymnArray *this) {
HymnInt len = this->length;
HymnValue *items = this->items;
for (HymnInt i = 0; i < len; i++) {
hymn_dereference(H, items[i]);
}
this->length = 0;
}
void hymn_array_delete(Hymn *H, HymnArray *this) {
hymn_array_clear(H, this);
free(this->items);
free(this);
}
HymnTable *hymn_new_table(void) {
HymnTable *this = hymn_calloc(1, sizeof(HymnTable));
table_init(this);
return this;
}
static HymnTable *new_table_copy(HymnTable *from) {
HymnTable *this = hymn_new_table();
unsigned int bins = from->bins;
for (unsigned int i = 0; i < bins; i++) {
HymnTableItem *item = from->items[i];
while (item != NULL) {
table_put(this, item->key, item->value);
hymn_reference_string(item->key);
hymn_reference(item->value);
item = item->next;
}
}
return this;
}
static HymnArray *table_keys(HymnTable *this) {
int size = this->size;
HymnArray *array = hymn_new_array((HymnInt)size);
if (size == 0) {
return array;
}
HymnValue *keys = array->items;
unsigned int total = 0;
unsigned int bins = this->bins;
for (unsigned int i = 0; i < bins; i++) {
HymnTableItem *item = this->items[i];
while (item != NULL) {
HymnString *string = item->key->string;
unsigned int insert = 0;
while (insert != total) {
if (strcmp(string, hymn_as_string(keys[insert])) < 0) {
for (unsigned int swap = total; swap > insert; swap--) {
keys[swap] = keys[swap - 1];
}
break;
}
insert++;
}
HymnValue value = hymn_new_string_value(item->key);
hymn_reference(value);
keys[insert] = value;
total++;
item = item->next;
}
}
return array;
}
static HymnObjectString *table_key_of(HymnTable *this, HymnValue match) {
unsigned int bin = 0;
HymnTableItem *item = NULL;
unsigned int bins = this->bins;
for (unsigned int i = 0; i < bins; i++) {
HymnTableItem *start = this->items[i];
if (start) {
bin = i;
item = start;
break;
}
}
if (item == NULL) return NULL;
if (hymn_match_values(match, item->value)) return item->key;
while (true) {
item = item->next;
if (item == NULL) {
for (bin = bin + 1; bin < bins; bin++) {
HymnTableItem *start = this->items[bin];
if (start) {
item = start;
break;
}
}
if (item == NULL) return NULL;
}
if (hymn_match_values(match, item->value)) return item->key;
}
}
static void scope_init(Compiler *C, Scope *scope, enum FunctionType type, size_t begin) {
Scope *enclosing = C->scope;
scope->enclosing = enclosing;
C->scope = scope;
scope->local_count = 0;
scope->depth = 0;
scope->type = type;
scope->begin = begin;
scope->func = hymn_calloc(1, sizeof(HymnFunction));
byte_code_init(&scope->func->code);
if (C->script != NULL) {
scope->func->script = hymn_new_string(C->script);
}
scope->func->parent = enclosing == NULL ? NULL : enclosing->func;
if (type == TYPE_FUNCTION) {
scope->func->name = hymn_substring(C->source, C->previous.start, C->previous.start + C->previous.length);
}
Local *local = &scope->locals[scope->local_count++];
local->depth = 0;
local->name.start = 0;
local->name.length = 0;
}
static void byte_code_delete(HymnByteCode *this) {
free(this->instructions);
free(this->lines);
free(this->constants.values);
}
static uint8_t byte_code_new_constant(Compiler *C, HymnValue value) {
HymnByteCode *code = current(C);
int constant = value_pool_add(&code->constants, value);
if (constant > UINT8_MAX) {
compile_error(C, &C->previous, "too many constants");
constant = 0;
}
return (uint8_t)constant;
}
static void write_byte(HymnByteCode *code, uint8_t b, int row) {
int count = code->count;
if (count >= code->capacity) {
code->capacity *= 2;
code->instructions = hymn_realloc_int(code->instructions, code->capacity, sizeof(uint8_t));
code->lines = hymn_realloc_int(code->lines, code->capacity, sizeof(int));
}
code->instructions[count] = b;
code->lines[count] = row;
code->count = count + 1;
}
static void emit(Compiler *C, uint8_t i) {
write_byte(current(C), i, C->previous.row);
}
static void emit_pop(Compiler *C) {
HymnByteCode *code = current(C);
write_byte(code, OP_POP, C->previous.row);
C->pop = code->count;
}
static void emit_short(Compiler *C, uint8_t i, uint8_t b) {
int row = C->previous.row;
HymnByteCode *code = current(C);
write_byte(code, i, row);
write_byte(code, b, row);
}
static void emit_word(Compiler *C, uint8_t i, uint8_t b, uint8_t n) {
int row = C->previous.row;
HymnByteCode *code = current(C);
write_byte(code, i, row);
write_byte(code, b, row);
write_byte(code, n, row);
}
static uint8_t emit_constant(Compiler *C, HymnValue value) {
uint8_t constant = byte_code_new_constant(C, value);
emit_short(C, OP_CONSTANT, constant);
return constant;
}
static Rule *token_rule(enum TokenType type) {
return &rules[type];
}
HymnObjectString *hymn_intern_string(Hymn *H, HymnString *string) {
HymnObjectString *object = set_add_or_get(&H->strings, string);
if (object->string != string) {
hymn_string_delete(string);
}
return object;
}
HymnObjectString *hymn_new_intern_string(Hymn *H, const char *value) {
return hymn_intern_string(H, hymn_new_string(value));
}
static HymnValue compile_intern_string(Hymn *H, HymnString *string) {
HymnObjectString *object = set_add_or_get(&H->strings, string);
if (object->string == string) {
hymn_reference_string(object);
} else {
hymn_string_delete(string);
}
return hymn_new_string_value(object);
}
static bool check_assign(Compiler *C) {
switch (C->current.type) {
case TOKEN_ASSIGN:
case TOKEN_ASSIGN_ADD:
case TOKEN_ASSIGN_BIT_AND:
case TOKEN_ASSIGN_BIT_LEFT_SHIFT:
case TOKEN_ASSIGN_BIT_OR:
case TOKEN_ASSIGN_BIT_RIGHT_SHIFT:
case TOKEN_ASSIGN_BIT_XOR:
case TOKEN_ASSIGN_DIVIDE:
case TOKEN_ASSIGN_MODULO:
case TOKEN_ASSIGN_MULTIPLY:
case TOKEN_ASSIGN_SUBTRACT:
return true;
default:
return false;
}
}
static bool check(Compiler *C, enum TokenType type) {
return C->current.type == type;
}
static bool match(Compiler *C, enum TokenType type) {
if (C->current.type != type) {
return false;
}
advance(C);
return true;
}
static void compile_with_precedence(Compiler *C, enum Precedence precedence) {
advance(C);
const Rule *rule = token_rule(C->previous.type);
void (*const prefix)(Compiler *, bool) = rule->prefix;
if (prefix == NULL) {
compile_error(C, &C->previous, "expression expected near '%.*s'", C->previous.length, &C->source[C->previous.start]);
return;
}
const bool assign = precedence <= PRECEDENCE_ASSIGN;
prefix(C, assign);
while (precedence <= token_rule(C->current.type)->precedence) {
advance(C);
void (*const infix)(Compiler *, bool) = token_rule(C->previous.type)->infix;
if (infix == NULL) {
compile_error(C, &C->previous, "expected infix");
return;
}
infix(C, assign);
}
if (assign && check_assign(C)) {
advance(C);
compile_error(C, &C->current, "invalid assignment");
}
}
static void consume(Compiler *C, enum TokenType type, const char *error) {
if (C->current.type == type) {
advance(C);
return;
}
compile_error(C, &C->current, error);
}
static uint8_t push_hidden_local(Compiler *C) {
Scope *scope = C->scope;
if (scope->local_count == HYMN_UINT8_COUNT) {
compile_error(C, &C->previous, "too many local variables in scope");
return 0;
}
uint8_t index = (uint8_t)scope->local_count++;
Local *local = &scope->locals[index];
local->name = (Token){0};
local->depth = scope->depth;
return index;
}
static uint8_t arguments(Compiler *C) {
uint8_t count = 0;
if (!check(C, TOKEN_RIGHT_PAREN)) {
do {
expression(C);
if (count == UINT8_MAX) {
compile_error(C, &C->previous, "too many function arguments");
break;
}
count++;
} while (match(C, TOKEN_COMMA));
}
consume(C, TOKEN_RIGHT_PAREN, "function has no closing ')'");
return count;
}
static void compile_call(Compiler *C, bool assign) {
(void)assign;
uint8_t count = arguments(C);
emit_short(C, OP_CALL, count);
}
static void compile_group(Compiler *C, bool assign) {
(void)assign;
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "parenthesis group has no closing ')'");
}
static void compile_none(Compiler *C, bool assign) {
(void)assign;
emit(C, OP_NONE);
}
static void compile_true(Compiler *C, bool assign) {
(void)assign;
emit(C, OP_TRUE);
}
static void compile_false(Compiler *C, bool assign) {
(void)assign;
emit(C, OP_FALSE);
}
static void compile_integer(Compiler *C, bool assign) {
(void)assign;
emit_constant(C, hymn_new_int(C->previous.integer));
}
static void compile_float(Compiler *C, bool assign) {
(void)assign;
emit_constant(C, hymn_new_float(C->previous.floating));
}
static char escape_sequence(const char c) {
switch (c) {
case 'b':
return '\b';
case 'f':
return '\f';
case 'n':
return '\n';
case 'r':
return '\r';
case 't':
return '\t';
case 'v':
return '\v';
default:
return c;
}
}
static HymnString *parse_string_literal(const char *string, size_t start, size_t len) {
const size_t end = start + len;
HymnString *literal = hymn_new_string_with_capacity(len);
for (size_t i = start; i < end; i++) {
const char c = string[i];
if (c == '\\' && i + 1 < end) {
const char e = escape_sequence(string[i + 1]);
literal = hymn_string_append_char(literal, e);
i++;
} else {
literal = hymn_string_append_char(literal, c);
}
}
return literal;
}
static HymnValue string_literal(Compiler *C) {
Token *previous = &C->previous;
HymnString *string = parse_string_literal(C->source, previous->start, previous->length);
while (check(C, TOKEN_STRING)) {
Token *current = &C->current;
HymnString *and = parse_string_literal(C->source, current->start, current->length);
string = hymn_string_append(string, and);
hymn_string_delete(and);
advance(C);
}
return compile_intern_string(C->H, string);
}
static void compile_string(Compiler *C, bool assign) {
(void)assign;
emit_constant(C, string_literal(C));
}
static uint8_t ident_constant_string(Compiler *C) {
return byte_code_new_constant(C, string_literal(C));
}
static uint8_t ident_constant(Compiler *C, Token *token) {
HymnString *string = hymn_substring(C->source, token->start, token->start + token->length);
return byte_code_new_constant(C, compile_intern_string(C->H, string));
}
static void begin_scope(Compiler *C) {
C->scope->depth++;
}
static void end_scope(Compiler *C) {
Scope *scope = C->scope;
scope->depth--;
while (scope->local_count > 0 && scope->locals[scope->local_count - 1].depth > scope->depth) {
emit_pop(C);
scope->local_count--;
}
C->barrier = scope->func->code.count;
}
static void compile_array(Compiler *C, bool assign) {
(void)assign;
emit(C, OP_NEW_ARRAY);
if (match(C, TOKEN_RIGHT_SQUARE)) {
return;
}
while (!check(C, TOKEN_RIGHT_SQUARE) && !check(C, TOKEN_EOF)) {
emit(C, OP_DUPLICATE);
expression(C);
emit(C, OP_ARRAY_PUSH);
if (!check(C, TOKEN_RIGHT_SQUARE)) {
consume(C, TOKEN_COMMA, "expected ',' between array elements");
}
}
consume(C, TOKEN_RIGHT_SQUARE, "expected ']' at end of array declaration");
}
static void compile_table(Compiler *C, bool assign) {
(void)assign;
emit(C, OP_NEW_TABLE);
if (match(C, TOKEN_RIGHT_CURLY)) {
return;
}
while (!check(C, TOKEN_RIGHT_CURLY) && !check(C, TOKEN_EOF)) {
emit(C, OP_DUPLICATE);
uint8_t name;
if (match(C, TOKEN_IDENT)) {
name = ident_constant(C, &C->previous);
} else if (match(C, TOKEN_STRING)) {
name = ident_constant_string(C);
} else {
name = UINT8_MAX;
compile_error(C, &C->current, "expected table key");
}
consume(C, TOKEN_COLON, "expected ':' between table key and value");
expression(C);
emit_short(C, OP_SET_PROPERTY, name);
emit_pop(C);
if (!check(C, TOKEN_RIGHT_CURLY)) {
consume(C, TOKEN_COMMA, "expected ',' between table elements");
}
}
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of table declaration");
}
static void function_delete(HymnFunction *this) {
if (this->parent != NULL) {
HymnFunction *parent = this->parent;
HymnValuePool *constants = &parent->code.constants;
int count = constants->count;
HymnValue *values = constants->values;
for (int i = 0; i < count; i++) {
HymnValue value = values[i];
if (hymn_is_func(value)) {
HymnFunction *func = hymn_as_func(value);
if (func == this) {
values[i] = hymn_new_undefined();
break;
}
}
}
}
HymnValuePool *constants = &this->code.constants;
int count = constants->count;
HymnValue *values = constants->values;
for (int i = 0; i < count; i++) {
HymnValue value = values[i];
if (hymn_is_func(value)) {
HymnFunction *func = hymn_as_func(value);
func->parent = NULL;
if (func->count == 0) {
function_delete(func);
}
}
}
byte_code_delete(&this->code);
hymn_string_delete(this->name);
hymn_string_delete(this->script);
hymn_string_delete(this->source);
HymnExceptList *except = this->except;
while (except != NULL) {
HymnExceptList *next = except->next;
free(except);
except = next;
}
free(this);
}
static void native_function_delete(Hymn *H, HymnNativeFunction *this) {
hymn_dereference_string(H, this->name);
free(this);
}
static void push_local(Compiler *C, Token name) {
Scope *scope = C->scope;
if (scope->local_count == HYMN_UINT8_COUNT) {
compile_error(C, &name, "too many local variables in scope");
return;
}
Local *local = &scope->locals[scope->local_count++];
local->name = name;
local->depth = -1;
}
static bool ident_match(Compiler *C, Token *a, Token *b) {
if (a->length != b->length) {
return false;
}
return memcmp(&C->source[a->start], &C->source[b->start], a->length) == 0;
}
static uint8_t variable(Compiler *C, const char *error) {
consume(C, TOKEN_IDENT, error);
Scope *scope = C->scope;
if (scope->depth == 0) {
return ident_constant(C, &C->previous);
}
Token *name = &C->previous;
for (int i = scope->local_count - 1; i >= 0; i--) {
Local *local = &scope->locals[i];
if (local->depth != -1 && local->depth < scope->depth) {
break;
} else if (ident_match(C, name, &local->name)) {
compile_error(C, name, "variable '%.*s' already exists in scope", name->length, &C->source[name->start]);
}
}
push_local(C, *name);
return 0;
}
static void local_initialize(Compiler *C) {
Scope *scope = C->scope;
if (scope->depth == 0) {
return;
}
scope->locals[scope->local_count - 1].depth = scope->depth;
}
static void finalize_variable(Compiler *C, uint8_t global) {
if (C->scope->depth > 0) {
local_initialize(C);
return;
}
emit_short(C, OP_DEFINE_GLOBAL, global);
}
static void type_declaration(Compiler *C) {
if (match(C, TOKEN_COLON)) {
enum TokenType type = C->current.type;
switch (type) {
case TOKEN_NONE:
case TOKEN_TO_FLOAT:
case TOKEN_TO_STRING:
case TOKEN_TO_INTEGER:
advance(C);
return;
default:
compile_error(C, &C->previous, "unavailable type declaration");
return;
}
}
}
static void define_new_variable(Compiler *C) {
uint8_t global = variable(C, "expected a variable name");
type_declaration(C);
consume(C, TOKEN_ASSIGN, "expected '=' after variable");
expression(C);
finalize_variable(C, global);
}
static int resolve_local(Compiler *C, Token *name) {
Scope *scope = C->scope;
for (int i = scope->local_count - 1; i >= 0; i--) {
Local *local = &scope->locals[i];
if (ident_match(C, name, &local->name)) {
if (local->depth == -1) {
compile_error(C, name, "local variable '%.*s' referenced before assignment", name->length, &C->source[name->start]);
}
return i;
}
}
return -1;
}
static void named_variable(Compiler *C, Token token, bool assign) {
uint8_t get;
uint8_t set;
int var = resolve_local(C, &token);
if (var != -1) {
get = OP_GET_LOCAL;
set = OP_SET_LOCAL;
} else {
get = OP_GET_GLOBAL;
set = OP_SET_GLOBAL;
var = ident_constant(C, &token);
}
if (assign && check_assign(C)) {
enum TokenType type = C->current.type;
advance(C);
if (type != TOKEN_ASSIGN) {
emit_short(C, get, (uint8_t)var);
}
expression(C);
switch (type) {
case TOKEN_ASSIGN_ADD: emit(C, OP_ADD); break;
case TOKEN_ASSIGN_BIT_AND: emit(C, OP_BIT_AND); break;
case TOKEN_ASSIGN_BIT_LEFT_SHIFT: emit(C, OP_BIT_LEFT_SHIFT); break;
case TOKEN_ASSIGN_BIT_OR: emit(C, OP_BIT_OR); break;
case TOKEN_ASSIGN_BIT_RIGHT_SHIFT: emit(C, OP_BIT_RIGHT_SHIFT); break;
case TOKEN_ASSIGN_BIT_XOR: emit(C, OP_BIT_XOR); break;
case TOKEN_ASSIGN_DIVIDE: emit(C, OP_DIVIDE); break;
case TOKEN_ASSIGN_MODULO: emit(C, OP_MODULO); break;
case TOKEN_ASSIGN_MULTIPLY: emit(C, OP_MULTIPLY); break;
case TOKEN_ASSIGN_SUBTRACT: emit(C, OP_SUBTRACT); break;
default: break;
}
emit_short(C, set, (uint8_t)var);
} else {
emit_short(C, get, (uint8_t)var);
}
}
static void compile_variable(Compiler *C, bool assign) {
named_variable(C, C->previous, assign);
}
static void compile_unary(Compiler *C, bool assign) {
(void)assign;
enum TokenType type = C->previous.type;
compile_with_precedence(C, PRECEDENCE_UNARY);
switch (type) {
case TOKEN_NOT: emit(C, OP_NOT); break;
case TOKEN_SUBTRACT: emit(C, OP_NEGATE); break;
case TOKEN_BIT_NOT: emit(C, OP_BIT_NOT); break;
default: return;
}
}
static void compile_binary(Compiler *C, bool assign) {
(void)assign;
enum TokenType type = C->previous.type;
Rule *rule = token_rule(type);
compile_with_precedence(C, (enum Precedence)(rule->precedence + 1));
switch (type) {
case TOKEN_ADD: emit(C, OP_ADD); break;
case TOKEN_SUBTRACT: emit(C, OP_SUBTRACT); break;
case TOKEN_MODULO: emit(C, OP_MODULO); break;
case TOKEN_MULTIPLY: emit(C, OP_MULTIPLY); break;
case TOKEN_DIVIDE: emit(C, OP_DIVIDE); break;
case TOKEN_EQUAL: emit(C, OP_EQUAL); break;
case TOKEN_NOT_EQUAL: emit(C, OP_NOT_EQUAL); break;
case TOKEN_LESS: emit(C, OP_LESS); break;
case TOKEN_LESS_EQUAL: emit(C, OP_LESS_EQUAL); break;
case TOKEN_GREATER: emit(C, OP_GREATER); break;
case TOKEN_GREATER_EQUAL: emit(C, OP_GREATER_EQUAL); break;
case TOKEN_BIT_OR: emit(C, OP_BIT_OR); break;
case TOKEN_BIT_AND: emit(C, OP_BIT_AND); break;
case TOKEN_BIT_XOR: emit(C, OP_BIT_XOR); break;
case TOKEN_BIT_LEFT_SHIFT: emit(C, OP_BIT_LEFT_SHIFT); break;
case TOKEN_BIT_RIGHT_SHIFT: emit(C, OP_BIT_RIGHT_SHIFT); break;
default: return;
}
}
static void compile_dot(Compiler *C, bool assign) {
consume(C, TOKEN_IDENT, "expected table key after '.'");
uint8_t name = ident_constant(C, &C->previous);
if (assign && match(C, TOKEN_ASSIGN)) {
expression(C);
emit_short(C, OP_SET_PROPERTY, name);
} else {
emit_short(C, OP_GET_PROPERTY, name);
}
}
static void compile_pointer(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_IDENT, "expected table key after '->'");
uint8_t name = ident_constant(C, &C->previous);
consume(C, TOKEN_LEFT_PAREN, "expected '(' after function name");
emit_short(C, OP_SELF, name);
uint8_t count = arguments(C);
if (count == UINT8_MAX) {
compile_error(C, &C->previous, "too many function arguments");
return;
}
emit_short(C, OP_CALL, (uint8_t)(count + 1));
}
static void compile_square(Compiler *C, bool assign) {
if (match(C, TOKEN_COLON)) {
emit_constant(C, hymn_new_int(0));
if (match(C, TOKEN_RIGHT_SQUARE)) {
emit_constant(C, hymn_new_none());
} else {
expression(C);
consume(C, TOKEN_RIGHT_SQUARE, "expected ']' after square bracket expression");
}
emit(C, OP_SLICE);
} else {
expression(C);
if (match(C, TOKEN_COLON)) {
if (match(C, TOKEN_RIGHT_SQUARE)) {
emit_constant(C, hymn_new_none());
} else {
expression(C);
consume(C, TOKEN_RIGHT_SQUARE, "expected ']' after square bracket expression");
}
emit(C, OP_SLICE);
} else {
consume(C, TOKEN_RIGHT_SQUARE, "expected ']' after square bracket expression");
if (assign && match(C, TOKEN_ASSIGN)) {
expression(C);
emit(C, OP_SET_DYNAMIC);
} else {
emit(C, OP_GET_DYNAMIC);
}
}
}
}
static int emit_jump(Compiler *C, uint8_t instruction) {
emit(C, instruction);
emit_short(C, UINT8_MAX, UINT8_MAX);
return current(C)->count - 2;
}
static void patch_jump(Compiler *C, int jump) {
if (jump == -1) {
return;
}
HymnByteCode *code = current(C);
int count = code->count;
int offset = count - jump - 2;
if (offset > UINT16_MAX) {
compile_error(C, &C->previous, "jump offset too large");
return;
}
code->instructions[jump] = (uint8_t)((offset >> 8) & UINT8_MAX);
code->instructions[jump + 1] = (uint8_t)(offset & UINT8_MAX);
}
static JumpList *add_jump(Compiler *C, JumpList *list, enum OpCode instruction) {
JumpList *jump = hymn_calloc(1, sizeof(JumpList));
jump->jump = emit_jump(C, (uint8_t)instruction);
jump->depth = C->scope->depth;
jump->code = current(C);
jump->next = list;
return jump;
}
static void free_jump_and_list(Compiler *C) {
JumpList *jump = C->jump_and;
HymnByteCode *code = current(C);
int depth = C->scope->depth;
while (jump != NULL) {
if (jump->code != code || jump->depth < depth) {
break;
}
patch_jump(C, jump->jump);
JumpList *next = jump->next;
free(jump);
jump = next;
}
C->jump_and = jump;
}
static void free_jump_or_list(Compiler *C) {
JumpList *jump = C->jump_or;
HymnByteCode *code = current(C);
int depth = C->scope->depth;
while (jump != NULL) {
if (jump->code != code || jump->depth < depth) {
break;
}
patch_jump(C, jump->jump);
JumpList *next = jump->next;
free(jump);
jump = next;
}
C->jump_or = jump;
}
static void free_jumps(Compiler *C, JumpList *jump) {
while (jump != NULL) {
patch_jump(C, jump->jump);
JumpList *next = jump->next;
free(jump);
jump = next;
}
}
static void compile_and(Compiler *C, bool assign) {
(void)assign;
C->jump_and = add_jump(C, C->jump_and, OP_JUMP_IF_FALSE);
compile_with_precedence(C, PRECEDENCE_AND);
}
static void compile_or(Compiler *C, bool assign) {
(void)assign;
C->jump_or = add_jump(C, C->jump_or, OP_JUMP_IF_TRUE);
free_jump_and_list(C);
compile_with_precedence(C, PRECEDENCE_OR);
}
struct PointerSet {
int count;
int capacity;
void **items;
};
static bool pointer_set_has(struct PointerSet *set, void *pointer) {
void **items = set->items;
if (items) {
int count = set->count;
for (int i = 0; i < count; i++) {
if (pointer == items[i]) {
return true;
}
}
}
return false;
}
static void pointer_set_add(struct PointerSet *set, void *pointer) {
if (set->items) {
int count = set->count;
if (count >= set->capacity) {
set->capacity *= 2;
set->items = hymn_realloc_int(set->items, set->capacity, sizeof(void *));
}
set->items[count] = pointer;
set->count = count + 1;
} else {
set->count = 1;
set->capacity = 1;
set->items = hymn_calloc(1, sizeof(void *));
set->items[0] = pointer;
}
}
static HymnString *value_to_string_recusive(HymnValue value, struct PointerSet *set, bool quote) {
switch (value.is) {
case HYMN_VALUE_UNDEFINED: return hymn_new_string("undefined");
case HYMN_VALUE_NONE: return hymn_new_string("none");
case HYMN_VALUE_BOOL: return hymn_as_bool(value) ? hymn_new_string("true") : hymn_new_string("false");
case HYMN_VALUE_INTEGER: return hymn_int_to_string(hymn_as_int(value));
case HYMN_VALUE_FLOAT: return hymn_float_to_string(hymn_as_float(value));
case HYMN_VALUE_STRING: {
if (quote) return hymn_quote_string(hymn_as_string(value));
return hymn_string_copy(hymn_as_string(value));
}
case HYMN_VALUE_ARRAY: {
HymnArray *array = hymn_as_array(value);
if (array == NULL || array->length == 0) {
return hymn_new_string("[]");
}
if (pointer_set_has(set, array)) {
return hymn_new_string("[..]");
} else {
pointer_set_add(set, array);
}
HymnString *string = hymn_new_string("[");
for (HymnInt i = 0; i < array->length; i++) {
if (i != 0) {
string = hymn_string_append(string, ", ");
}
HymnString *add = value_to_string_recusive(array->items[i], set, true);
string = hymn_string_append(string, add);
hymn_string_delete(add);
}
string = hymn_string_append_char(string, ']');
return string;
}
case HYMN_VALUE_TABLE: {
HymnTable *table = hymn_as_table(value);
if (table == NULL || table->size == 0) {
return hymn_new_string("{}");
} else if (pointer_set_has(set, table)) {
return hymn_new_string("{ .. }");
} else {
pointer_set_add(set, table);
}
int size = table->size;
HymnObjectString **keys = hymn_malloc_int(size, sizeof(HymnObjectString *));
unsigned int total = 0;
unsigned int bins = table->bins;
for (unsigned int i = 0; i < bins; i++) {
HymnTableItem *item = table->items[i];
while (item != NULL) {
HymnString *string = item->key->string;
unsigned int insert = 0;
while (insert != total) {
if (strcmp(string, keys[insert]->string) < 0) {
for (unsigned int swap = total; swap > insert; swap--) {
keys[swap] = keys[swap - 1];
}
break;
}
insert++;
}
keys[insert] = item->key;
total++;
item = item->next;
}
}
HymnString *string = hymn_new_string("{ ");
for (int i = 0; i < size; i++) {
if (i != 0) {
string = hymn_string_append(string, ", ");
}
HymnObjectString *key = keys[i];
HymnValue item = table_get(table, key);
HymnString *add = value_to_string_recusive(item, set, true);
HymnString *quoting = hymn_quote_string(key->string);
string = hymn_string_append(string, quoting);
string = hymn_string_append(string, ": ");
string = hymn_string_append(string, add);
hymn_string_delete(quoting);
hymn_string_delete(add);
}
string = hymn_string_append(string, " }");
free(keys);
return string;
}
case HYMN_VALUE_FUNC: {
HymnFunction *func = hymn_as_func(value);
if (func->name) return hymn_string_copy(func->name);
if (func->script) return hymn_string_copy(func->script);
return hymn_new_string("script");
}
case HYMN_VALUE_FUNC_NATIVE: return hymn_string_copy(hymn_as_native(value)->name->string);
case HYMN_VALUE_POINTER: return hymn_string_format("%p", hymn_as_pointer(value));
default:
break;
}
return hymn_new_string("?");
}
HymnString *hymn_value_to_string(HymnValue value) {
struct PointerSet set = {.count = 0, .capacity = 0, .items = NULL};
HymnString *string = value_to_string_recusive(value, &set, false);
free(set.items);
return string;
}
static HymnString *value_concat(HymnValue a, HymnValue b) {
HymnString *string = hymn_value_to_string(a);
HymnString *second = hymn_value_to_string(b);
string = hymn_string_append(string, second);
hymn_string_delete(second);
return string;
}
static HymnString *debug_value_to_string(HymnValue value) {
HymnString *string = hymn_value_to_string(value);
HymnString *format = hymn_string_format("%s: %s", hymn_value_type(value.is), string);
hymn_string_delete(string);
return format;
}
static int next(uint8_t instruction) {
switch (instruction) {
case OP_CALL:
case OP_CONSTANT:
case OP_DEFINE_GLOBAL:
case OP_EXISTS:
case OP_GET_GLOBAL:
case OP_GET_LOCAL:
case OP_GET_PROPERTY:
case OP_INCREMENT:
case OP_POP_N:
case OP_PRINT:
case OP_SELF:
case OP_SET_GLOBAL:
case OP_SET_LOCAL:
case OP_SET_PROPERTY:
case OP_TAIL_CALL:
return 2;
case OP_ADD_LOCALS:
case OP_ARRAY_PUSH_LOCALS:
case OP_GET_GLOBAL_PROPERTY:
case OP_GET_LOCALS:
case OP_INCREMENT_LOCAL:
case OP_INCREMENT_LOCAL_AND_SET:
case OP_JUMP:
case OP_JUMP_IF_EQUAL:
case OP_JUMP_IF_FALSE:
case OP_JUMP_IF_GREATER:
case OP_JUMP_IF_GREATER_EQUAL:
case OP_JUMP_IF_LESS:
case OP_JUMP_IF_LESS_EQUAL:
case OP_JUMP_IF_NOT_EQUAL:
case OP_JUMP_IF_TRUE:
case OP_LOOP:
case OP_MODULO_LOCALS:
return 3;
case OP_FOR:
case OP_FOR_LOOP:
return 4;
case OP_INCREMENT_LOOP:
case OP_JUMP_IF_GREATER_LOCALS:
return 5;
default:
return 1;
}
}
struct Instruction {
Instruction *next;
int index;
uint8_t instruction;
char padding[3];
};
struct Optimizer {
HymnByteCode *code;
Instruction *important;
HymnExceptList *except;
};
#define GET_JUMP(instructions, index, x, y) (((int)instructions[index + x] << 8) | (int)instructions[index + y])
#define UPDATE_JUMP(instructions, index, x, y, jump) \
instructions[index + x] = (uint8_t)((jump >> 8) & UINT8_MAX); \
instructions[index + y] = (uint8_t)(jump & UINT8_MAX);
#define IS_ADJUSTABLE(off, compare, T, operator) \
if (index compare target) { \
int offset = index + off; \
int jump = ((int)instructions[offset - 2] << 8) | (int)instructions[offset - 1]; \
if (offset operator jump == target) { \
return view; \
} \
} \
break;
static Instruction *adjustable(Optimizer *optimizer, int target) {
Instruction *view = optimizer->important;
uint8_t *instructions = optimizer->code->instructions;
while (view != NULL) {
int index = view->index;
uint8_t instruction = view->instruction;
switch (instruction) {
case OP_JUMP:
case OP_JUMP_IF_FALSE:
case OP_JUMP_IF_TRUE:
case OP_JUMP_IF_EQUAL:
case OP_JUMP_IF_NOT_EQUAL:
case OP_JUMP_IF_LESS:
case OP_JUMP_IF_GREATER:
case OP_JUMP_IF_LESS_EQUAL:
case OP_JUMP_IF_GREATER_EQUAL: {
IS_ADJUSTABLE(3, <, target, +)
}
case OP_FOR: {
IS_ADJUSTABLE(4, <, target, +)
}
case OP_JUMP_IF_GREATER_LOCALS: {
IS_ADJUSTABLE(5, <, target, +)
}
case OP_LOOP: {
IS_ADJUSTABLE(3, >=, target, -)
}
case OP_FOR_LOOP: {
IS_ADJUSTABLE(4, >=, target, -)
}
case OP_INCREMENT_LOOP: {
IS_ADJUSTABLE(5, >=, target, -)
}
default:
break;
}
view = view->next;
}
return NULL;
}
static void rewrite(Optimizer *optimizer, int start, int shift) {
uint8_t *instructions = optimizer->code->instructions;
Instruction *view = optimizer->important;
while (view != NULL) {
int i = view->index;
switch (view->instruction) {
case OP_JUMP:
case OP_JUMP_IF_FALSE:
case OP_JUMP_IF_TRUE:
case OP_JUMP_IF_EQUAL:
case OP_JUMP_IF_NOT_EQUAL:
case OP_JUMP_IF_LESS:
case OP_JUMP_IF_GREATER:
case OP_JUMP_IF_LESS_EQUAL:
case OP_JUMP_IF_GREATER_EQUAL: {
if (i < start) {
int jump = GET_JUMP(instructions, i, 1, 2);
int destination = i + 3 + jump;
if (destination > start) {
jump -= shift;
UPDATE_JUMP(instructions, i, 1, 2, jump)
} else {
assert(destination < start || destination > start + shift || shift == next(optimizer->code->instructions[destination]));
}
}
break;
}
case OP_JUMP_IF_GREATER_LOCALS: {
if (i < start) {
int jump = GET_JUMP(instructions, i, 3, 4);
int destination = i + 5 + jump;
if (destination > start) {
jump -= shift;
UPDATE_JUMP(instructions, i, 3, 4, jump)
} else {
assert(destination < start || destination > start + shift || shift == next(optimizer->code->instructions[destination]));
}
}
break;
}
case OP_FOR: {
if (i < start) {
int jump = GET_JUMP(instructions, i, 2, 3);
int destination = i + 4 + jump;
if (destination > start) {
jump -= shift;
UPDATE_JUMP(instructions, i, 2, 3, jump)
}
}
break;
}
case OP_LOOP: {
if (i >= start) {
int jump = GET_JUMP(instructions, i, 1, 2);
int destination = i + 3 - jump;
if (destination < start) {
jump -= shift;
UPDATE_JUMP(instructions, i, 1, 2, jump)
} else {
assert(destination > start + shift || shift == next(optimizer->code->instructions[destination]));
}
}
break;
}
case OP_FOR_LOOP: {
if (i >= start) {
int jump = GET_JUMP(instructions, i, 2, 3);
int destination = i + 3 - jump;
if (destination < start) {
jump -= shift;
UPDATE_JUMP(instructions, i, 2, 3, jump)
} else {
assert(destination > start + shift || shift == next(optimizer->code->instructions[destination]));
}
}
break;
}
case OP_INCREMENT_LOOP: {
if (i >= start) {
int jump = GET_JUMP(instructions, i, 3, 4);
int destination = i + 5 - jump;
if (destination < start) {
jump -= shift;
UPDATE_JUMP(instructions, i, 3, 4, jump)
} else {
assert(destination > start + shift || shift == next(optimizer->code->instructions[destination]));
}
}
break;
}
default: break;
}
if (i >= start) {
view->index = i - shift;
}
view = view->next;
}
int *lines = optimizer->code->lines;
int count = optimizer->code->count - shift;
for (int c = start; c < count; c++) {
int n = c + shift;
instructions[c] = instructions[n];
lines[c] = lines[n];
}
optimizer->code->count = count;
HymnExceptList *except = optimizer->except;
while (except != NULL) {
if (start < except->start) {
except->start -= shift;
except->end -= shift;
} else if (start < except->end) {
except->end -= shift;
}
except = except->next;
}
}
static void update(Optimizer *optimizer, int index, uint8_t instruction) {
Instruction *view = optimizer->important;
while (view != NULL) {
if (index == view->index) {
view->instruction = instruction;
optimizer->code->instructions[index] = instruction;
return;
}
view = view->next;
}
fprintf(stderr, "optimization failed to find instruction to update\n");
exit(1);
}
static void move(Optimizer *optimizer, int index, uint8_t instruction, int to) {
Instruction *view = optimizer->important;
while (view != NULL) {
if (index == view->index) {
view->index = to;
view->instruction = instruction;
optimizer->code->instructions[to] = instruction;
return;
}
view = view->next;
}
fprintf(stderr, "optimization failed to find instruction to move\n");
exit(1);
}
static void deletion(Optimizer *optimizer, int index) {
Instruction *view = optimizer->important;
Instruction *previous = NULL;
while (view != NULL) {
if (index == view->index) {
Instruction *next = view->next;
free(view);
if (previous == NULL) {
optimizer->important = next;
} else {
previous->next = next;
}
return;
}
previous = view;
view = view->next;
}
fprintf(stderr, "optimization failed to find instruction to delete\n");
exit(1);
}
static void interest(Optimizer *optimizer) {
uint8_t *instructions = optimizer->code->instructions;
int count = optimizer->code->count;
Instruction *head = NULL;
Instruction *tail = NULL;
int i = 0;
while (i < count) {
uint8_t instruction = instructions[i];
switch (instruction) {
case OP_JUMP:
case OP_JUMP_IF_FALSE:
case OP_JUMP_IF_TRUE:
case OP_JUMP_IF_EQUAL:
case OP_JUMP_IF_NOT_EQUAL:
case OP_JUMP_IF_LESS:
case OP_JUMP_IF_GREATER:
case OP_JUMP_IF_GREATER_LOCALS:
case OP_JUMP_IF_LESS_EQUAL:
case OP_JUMP_IF_GREATER_EQUAL:
case OP_FOR:
case OP_LOOP:
case OP_FOR_LOOP:
case OP_INCREMENT_LOOP: {
Instruction *important = hymn_calloc(1, sizeof(Instruction));
important->index = i;
important->instruction = instruction;
if (tail == NULL) {
head = important;
} else {
tail->next = important;
}
tail = important;
break;
}
default:
break;
}
i += next(instruction);
}
optimizer->important = head;
}
static HymnString *disassemble_byte_code(HymnByteCode *code);
static void optimize(Compiler *C) {
Scope *scope = C->scope;
HymnFunction *func = scope->func;
Optimizer optimizer = {0};
optimizer.code = &func->code;
optimizer.except = func->except;
if (optimizer.code->count <= 2) {
return;
}
interest(&optimizer);
#define COUNT() optimizer.code->count
#define INSTRUCTION(I) optimizer.code->instructions[I]
#define CONSTANT(I) optimizer.code->constants.values[optimizer.code->instructions[I]]
#define SET(I, O) optimizer.code->instructions[I] = O
#define JUMP_IF(T, F) \
if (second == OP_JUMP_IF_TRUE) { \
rewrite(&optimizer, one, 1); \
update(&optimizer, one, T); \
continue; \
} else if (second == OP_JUMP_IF_FALSE) { \
rewrite(&optimizer, one, 1); \
update(&optimizer, one, F); \
continue; \
}
int zero = -1;
int one = 0;
while (one < COUNT()) {
uint8_t first = INSTRUCTION(one);
int two = one + next(first);
if (two >= COUNT()) {
break;
}
uint8_t second = INSTRUCTION(two);
switch (first) {
case OP_INCREMENT_LOCAL_AND_SET: {
if (second == OP_LOOP) {
// set to increment loop and reuse loop jump
move(&optimizer, two, OP_INCREMENT_LOOP, one);
// delete loop
rewrite(&optimizer, two, 1);
// subtract 1 to account for one less byte to jump after deleting instruction
uint8_t *instructions = optimizer.code->instructions;
int jump = GET_JUMP(instructions, one, 3, 4) - 1;
UPDATE_JUMP(instructions, one, 3, 4, jump)
continue;
}
break;
}
case OP_POP:
case OP_POP_TWO: {
if (second == OP_VOID) {
rewrite(&optimizer, one, 1);
continue;
}
break;
}
case OP_POP_N: {
if (second == OP_VOID) {
rewrite(&optimizer, one, 2);
continue;
}
break;
}
default:
break;
}
if (adjustable(&optimizer, two) != NULL) {
goto next;
}
switch (first) {
case OP_GET_LOCAL: {
if (second == OP_GET_LOCAL) {
SET(one, OP_GET_LOCALS);
rewrite(&optimizer, one + 2, 1);
continue;
}
break;
}
case OP_GET_LOCALS: {
if (second == OP_LESS_EQUAL) {
int three = two + next(second);
uint8_t third = three < COUNT() ? INSTRUCTION(three) : UINT8_MAX;
if (third == OP_JUMP_IF_FALSE) {
move(&optimizer, three, OP_JUMP_IF_GREATER_LOCALS, one);
rewrite(&optimizer, two, 2);
continue;
}
}
break;
}
case OP_POP: {
if (second == OP_POP) {
rewrite(&optimizer, one, 1);
SET(one, OP_POP_TWO);
continue;
}
break;
}
case OP_POP_TWO: {
if (second == OP_POP) {
SET(one, OP_POP_N);
SET(one + 1, 3);
continue;
}
break;
}
case OP_POP_N: {
if (second == OP_POP) {
uint8_t pop = INSTRUCTION(one + 1);
if (pop < UINT8_MAX - 1) {
rewrite(&optimizer, one + 1, 1);
SET(one + 1, (uint8_t)(pop + 1));
continue;
}
}
break;
}
default:
break;
}
if (adjustable(&optimizer, one) != NULL) {
goto next;
}
switch (first) {
case OP_RETURN: {
if (second == OP_VOID) {
rewrite(&optimizer, two, 1);
continue;
}
break;
}
case OP_CALL: {
if (second == OP_RETURN) {
SET(one, OP_TAIL_CALL);
continue;
}
break;
}
case OP_GET_GLOBAL: {
if (second == OP_GET_PROPERTY) {
SET(one, OP_GET_GLOBAL_PROPERTY);
rewrite(&optimizer, one + 2, 1);
continue;
}
break;
}
case OP_GET_LOCAL: {
if (second == OP_CONSTANT) {
int three = two + next(second);
uint8_t third = three < COUNT() ? INSTRUCTION(three) : UINT8_MAX;
if (third == OP_ADD) {
HymnValue value = CONSTANT(two + 1);
if (hymn_is_int(value)) {
HymnInt add = hymn_as_int(value);
if (add >= 0 && add <= UINT8_MAX) {
uint8_t local = INSTRUCTION(one + 1);
rewrite(&optimizer, one, 2);
SET(one, OP_INCREMENT_LOCAL);
SET(one + 1, local);
SET(one + 2, (uint8_t)add);
continue;
}
}
}
}
break;
}
case OP_GET_LOCALS: {
if (second == OP_ADD) {
SET(one, OP_ADD_LOCALS);
rewrite(&optimizer, one + 3, 1);
continue;
} else if (second == OP_MODULO) {
SET(one, OP_MODULO_LOCALS);
rewrite(&optimizer, one + 3, 1);
continue;
} else if (second == OP_ARRAY_PUSH) {
SET(one, OP_ARRAY_PUSH_LOCALS);
rewrite(&optimizer, one + 3, 1);
continue;
}
break;
}
case OP_INCREMENT_LOCAL: {
if (second == OP_SET_LOCAL) {
if (INSTRUCTION(one + 1) == INSTRUCTION(one + 4)) {
int three = two + next(second);
uint8_t third = three < COUNT() ? INSTRUCTION(three) : UINT8_MAX;
if (third == OP_POP) {
SET(one, OP_INCREMENT_LOCAL_AND_SET);
rewrite(&optimizer, one + 3, 3);
continue;
}
}
}
break;
}
case OP_CONSTANT: {
if (zero == -1) zero = one;
if (second == OP_CONSTANT) {
int three = two + next(second);
uint8_t third = three < COUNT() ? INSTRUCTION(three) : UINT8_MAX;
if (third == OP_ADD) {
HymnValue a = CONSTANT(one + 1);
HymnValue b = CONSTANT(two + 1);
HymnValue value;
if (hymn_is_none(a)) {
if (hymn_is_string(b)) {
value = compile_intern_string(C->H, value_concat(a, b));
} else {
break;
}
} else if (hymn_is_bool(a)) {
if (hymn_is_string(b)) {
value = compile_intern_string(C->H, value_concat(a, b));
} else {
break;
}
} else if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
value = hymn_new_int(a.as.i + b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float((HymnFloat)a.as.i + b.as.f);
} else if (hymn_is_string(b)) {
value = compile_intern_string(C->H, value_concat(a, b));
} else {
break;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
value = hymn_new_float(a.as.f + (HymnFloat)b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float(a.as.f + b.as.f);
} else if (hymn_is_string(b)) {
value = compile_intern_string(C->H, value_concat(a, b));
} else {
break;
}
} else if (hymn_is_string(a)) {
value = compile_intern_string(C->H, value_concat(a, b));
} else {
break;
}
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
} else if (third == OP_SUBTRACT) {
HymnValue a = CONSTANT(one + 1);
HymnValue b = CONSTANT(two + 1);
HymnValue value;
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
value = hymn_new_int(a.as.i - b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float((HymnFloat)a.as.i - b.as.f);
} else {
break;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
value = hymn_new_float(a.as.f - (HymnFloat)b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float(a.as.f - b.as.f);
} else {
break;
}
} else {
break;
}
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
} else if (third == OP_MULTIPLY) {
HymnValue a = CONSTANT(one + 1);
HymnValue b = CONSTANT(two + 1);
HymnValue value;
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
value = hymn_new_int(a.as.i * b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float((HymnFloat)a.as.i * b.as.f);
} else {
break;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
value = hymn_new_float(a.as.f * (HymnFloat)b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float(a.as.f * b.as.f);
} else {
break;
}
} else {
break;
}
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
} else if (third == OP_DIVIDE) {
HymnValue a = CONSTANT(one + 1);
HymnValue b = CONSTANT(two + 1);
HymnValue value;
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
value = hymn_new_int(a.as.i / b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float((HymnFloat)a.as.i / b.as.f);
} else {
break;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
value = hymn_new_float(a.as.f / (HymnFloat)b.as.i);
} else if (hymn_is_float(b)) {
value = hymn_new_float(a.as.f / b.as.f);
} else {
break;
}
} else {
break;
}
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
} else if (third == OP_BIT_AND) {
HymnValue a = CONSTANT(one + 1);
if (hymn_is_int(a)) {
HymnValue b = CONSTANT(two + 1);
if (hymn_is_int(b)) {
HymnInt x = hymn_as_int(a);
HymnInt y = hymn_as_int(b);
HymnValue value = hymn_new_int(x & y);
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
}
}
} else if (third == OP_BIT_OR) {
HymnValue a = CONSTANT(one + 1);
if (hymn_is_int(a)) {
HymnValue b = CONSTANT(two + 1);
if (hymn_is_int(b)) {
HymnInt x = hymn_as_int(a);
HymnInt y = hymn_as_int(b);
HymnValue value = hymn_new_int(x | y);
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
}
}
} else if (third == OP_BIT_XOR) {
HymnValue a = CONSTANT(one + 1);
if (hymn_is_int(a)) {
HymnValue b = CONSTANT(two + 1);
if (hymn_is_int(b)) {
HymnInt x = hymn_as_int(a);
HymnInt y = hymn_as_int(b);
HymnValue value = hymn_new_int(x ^ y);
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
}
}
} else if (third == OP_BIT_LEFT_SHIFT) {
HymnValue a = CONSTANT(one + 1);
if (hymn_is_int(a)) {
HymnValue b = CONSTANT(two + 1);
if (hymn_is_int(b)) {
HymnInt x = hymn_as_int(a);
HymnInt y = hymn_as_int(b);
HymnValue value = hymn_new_int(x << y);
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
}
}
} else if (third == OP_BIT_RIGHT_SHIFT) {
HymnValue a = CONSTANT(one + 1);
if (hymn_is_int(a)) {
HymnValue b = CONSTANT(two + 1);
if (hymn_is_int(b)) {
HymnInt x = hymn_as_int(a);
HymnInt y = hymn_as_int(b);
HymnValue value = hymn_new_int(x >> y);
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, two, 3);
one = zero;
continue;
}
}
}
} else {
zero = -1;
if (second == OP_NEGATE) {
HymnValue value = CONSTANT(one + 1);
if (hymn_is_int(value)) {
value.as.i = -value.as.i;
} else if (hymn_is_float(value)) {
value.as.f = -value.as.f;
} else {
break;
}
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, one + 2, 1);
continue;
} else if (second == OP_BIT_NOT) {
HymnValue value = CONSTANT(one + 1);
if (hymn_is_int(value)) {
value.as.i = ~value.as.i;
} else {
break;
}
uint8_t constant = byte_code_new_constant(C, value);
SET(one + 1, constant);
rewrite(&optimizer, one + 2, 1);
continue;
} else if (second == OP_ADD) {
HymnValue value = CONSTANT(one + 1);
if (hymn_is_int(value)) {
HymnInt add = hymn_as_int(value);
if (add >= 0 && add <= UINT8_MAX) {
SET(one, OP_INCREMENT);
SET(one + 1, (uint8_t)add);
rewrite(&optimizer, one + 2, 1);
continue;
}
}
}
}
break;
}
case OP_EQUAL: {
JUMP_IF(OP_JUMP_IF_EQUAL, OP_JUMP_IF_NOT_EQUAL)
break;
}
case OP_NOT_EQUAL: {
JUMP_IF(OP_JUMP_IF_NOT_EQUAL, OP_JUMP_IF_EQUAL)
break;
}
case OP_LESS: {
JUMP_IF(OP_JUMP_IF_LESS, OP_JUMP_IF_GREATER_EQUAL)
break;
}
case OP_GREATER: {
JUMP_IF(OP_JUMP_IF_GREATER, OP_JUMP_IF_LESS_EQUAL)
break;
}
case OP_LESS_EQUAL: {
JUMP_IF(OP_JUMP_IF_LESS_EQUAL, OP_JUMP_IF_GREATER)
break;
}
case OP_GREATER_EQUAL: {
JUMP_IF(OP_JUMP_IF_GREATER_EQUAL, OP_JUMP_IF_LESS)
break;
}
case OP_TRUE: {
if (second == OP_JUMP_IF_TRUE) {
rewrite(&optimizer, one, 1);
update(&optimizer, one, OP_JUMP);
continue;
} else if (second == OP_JUMP_IF_FALSE) {
deletion(&optimizer, two);
rewrite(&optimizer, one, 4);
continue;
}
break;
}
case OP_FALSE: {
if (second == OP_JUMP_IF_TRUE) {
deletion(&optimizer, two);
rewrite(&optimizer, one, 4);
continue;
} else if (second == OP_JUMP_IF_FALSE) {
rewrite(&optimizer, one, 1);
update(&optimizer, one, OP_JUMP);
continue;
}
break;
}
case OP_NOT: {
if (second == OP_JUMP_IF_TRUE) {
rewrite(&optimizer, one, 1);
update(&optimizer, one, OP_JUMP_IF_FALSE);
continue;
} else if (second == OP_JUMP_IF_FALSE) {
rewrite(&optimizer, one, 1);
update(&optimizer, one, OP_JUMP_IF_TRUE);
continue;
}
break;
}
default:
break;
}
next:
one = two;
}
Instruction *important = optimizer.important;
while (important != NULL) {
Instruction *next = important->next;
free(important);
important = next;
}
}
static void echo_if_none(Compiler *C) {
HymnByteCode *code = &C->scope->func->code;
int count = code->count;
if (C->barrier == count) {
return;
}
if (C->pop == count) code->instructions[count - 1] = OP_ECHO;
}
static HymnFunction *end_function(Compiler *C) {
Scope *scope = C->scope;
HymnFunction *func = scope->func;
if (scope->type == TYPE_DIRECT || scope->type == TYPE_REPL) echo_if_none(C);
emit(C, OP_VOID);
#ifndef HYMN_NO_OPTIMIZE
optimize(C);
#endif
if (scope->type == TYPE_FUNCTION) func->source = hymn_substring(C->source, scope->begin, C->previous.start + C->previous.length);
C->scope = scope->enclosing;
return func;
}
static void compile_function(Compiler *C, enum FunctionType type, size_t begin) {
Scope scope = {0};
scope_init(C, &scope, type, begin);
begin_scope(C);
consume(C, TOKEN_LEFT_PAREN, "expected '(' after function name");
HymnFunction *func = C->scope->func;
if (!check(C, TOKEN_RIGHT_PAREN)) {
do {
func->arity++;
if (func->arity > UINT8_MAX) {
compile_error(C, &C->previous, "too many function parameters");
}
uint8_t parameter = variable(C, "expected parameter name");
finalize_variable(C, parameter);
type_declaration(C);
} while (match(C, TOKEN_COMMA));
}
consume(C, TOKEN_RIGHT_PAREN, "expected ')' after function parameters");
type_declaration(C);
consume(C, TOKEN_LEFT_CURLY, "expected '{' after function parameters");
while (!check(C, TOKEN_RIGHT_CURLY) && !check(C, TOKEN_EOF)) {
declaration(C);
}
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of function body");
end_function(C);
emit_constant(C, hymn_new_func_value(func));
}
static void function_expression(Compiler *C, bool assign) {
(void)assign;
compile_function(C, TYPE_FUNCTION, C->previous.start);
}
static void declare_function(Compiler *C) {
size_t begin = C->previous.start;
uint8_t global = variable(C, "expected function name");
local_initialize(C);
compile_function(C, TYPE_FUNCTION, begin);
finalize_variable(C, global);
}
static void declaration(Compiler *C) {
if (match(C, TOKEN_SET)) {
define_new_variable(C);
} else if (match(C, TOKEN_FUNCTION)) {
declare_function(C);
} else {
statement(C);
}
}
static void block(Compiler *C) {
begin_scope(C);
while (!check(C, TOKEN_RIGHT_CURLY) && !check(C, TOKEN_EOF)) {
declaration(C);
}
end_scope(C);
}
static void if_statement(Compiler *C) {
expression(C);
int jump = emit_jump(C, OP_JUMP_IF_FALSE);
free_jump_or_list(C);
consume(C, TOKEN_LEFT_CURLY, "expected '{' after if statement");
begin_scope(C);
while (!check(C, TOKEN_RIGHT_CURLY) && !check(C, TOKEN_EOF)) {
declaration(C);
}
end_scope(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of if statement body");
if (check(C, TOKEN_ELIF) || check(C, TOKEN_ELSE)) {
JumpList jump_end = {0};
jump_end.jump = emit_jump(C, OP_JUMP);
JumpList *tail = &jump_end;
while (match(C, TOKEN_ELIF)) {
patch_jump(C, jump);
free_jump_and_list(C);
expression(C);
jump = emit_jump(C, OP_JUMP_IF_FALSE);
free_jump_or_list(C);
consume(C, TOKEN_LEFT_CURLY, "expected '{' after elif statement");
begin_scope(C);
while (!check(C, TOKEN_RIGHT_CURLY) && !check(C, TOKEN_EOF)) {
declaration(C);
}
end_scope(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of elif statement body");
JumpList *next = hymn_calloc(1, sizeof(JumpList));
next->jump = emit_jump(C, OP_JUMP);
tail->next = next;
tail = next;
}
patch_jump(C, jump);
free_jump_and_list(C);
if (match(C, TOKEN_ELSE)) {
consume(C, TOKEN_LEFT_CURLY, "expected '{' after else statement");
block(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of else statement body");
}
patch_jump(C, jump_end.jump);
free_jumps(C, jump_end.next);
} else {
patch_jump(C, jump);
free_jump_and_list(C);
}
}
static void emit_loop(Compiler *C, int start) {
emit(C, OP_LOOP);
int offset = current(C)->count - start + 2;
if (offset > UINT16_MAX) {
compile_error(C, &C->previous, "loop is too large");
}
emit_short(C, (uint8_t)((offset >> 8) & UINT8_MAX), (uint8_t)(offset & UINT8_MAX));
}
static void patch_jump_list(Compiler *C) {
while (C->jump != NULL) {
int depth;
if (C->loop == NULL) {
depth = 1;
} else {
depth = C->loop->depth + 1;
}
if (C->jump->depth < depth) {
break;
}
patch_jump(C, C->jump->jump);
JumpList *next = C->jump->next;
free(C->jump);
C->jump = next;
}
}
static void patch_jump_for_list(Compiler *C) {
while (C->jump_for != NULL) {
int depth;
if (C->loop == NULL) {
depth = 1;
} else {
depth = C->loop->depth;
}
if (C->jump_for->depth < depth) {
break;
}
patch_jump(C, C->jump_for->jump);
JumpList *next = C->jump_for->next;
free(C->jump_for);
C->jump_for = next;
}
}
static void iterator_statement(Compiler *C, bool pair) {
local_initialize(C);
int index = C->scope->local_count;
if (index <= 0 || index >= UINT8_MAX) {
compile_error(C, &C->current, "too many local variables in iterator");
return;
}
uint8_t value = (uint8_t)(index + 1);
uint8_t object = (uint8_t)(index - 1);
push_hidden_local(C);
if (pair) {
variable(C, "expected variable name in for loop");
local_initialize(C);
consume(C, TOKEN_IN, "expected 'in' after variable name in for loop");
C->scope->locals[index].name = C->scope->locals[object].name;
} else {
push_hidden_local(C);
C->scope->locals[value].name = C->scope->locals[object].name;
}
C->scope->locals[object].name = (Token){0};
// IN
expression(C);
emit_short(C, OP_FOR, object);
emit_short(C, UINT8_MAX, UINT8_MAX);
int start = current(C)->count;
int jump = start - 2;
LoopList loop = {.start = start, .depth = C->scope->depth + 1, .next = C->loop, .is_for = true};
C->loop = &loop;
// BODY
consume(C, TOKEN_LEFT_CURLY, "expected '{' after for loop declaration");
block(C);
// LOOP
patch_jump_for_list(C);
emit_short(C, OP_FOR_LOOP, object);
int offset = current(C)->count - start + 2;
if (offset > UINT16_MAX) {
compile_error(C, &C->previous, "loop is too large");
}
emit_short(C, (uint8_t)((offset >> 8) & UINT8_MAX), (uint8_t)(offset & UINT8_MAX));
// END
C->loop = loop.next;
patch_jump(C, jump);
patch_jump_list(C);
end_scope(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of for loop");
}
static void for_statement(Compiler *C) {
begin_scope(C);
// ASSIGN
uint8_t index = (uint8_t)C->scope->local_count;
variable(C, "expected variable name in for loop");
if (match(C, TOKEN_ASSIGN)) {
expression(C);
local_initialize(C);
consume(C, TOKEN_COMMA, "expected ',' in for loop after variable assignment");
} else if (match(C, TOKEN_COMMA)) {
iterator_statement(C, true);
return;
} else if (match(C, TOKEN_IN)) {
iterator_statement(C, false);
return;
} else {
compile_error(C, &C->previous, "incomplete for loop declaration");
return;
}
// COMPARE
int compare = current(C)->count;
expression(C);
int jump = emit_jump(C, OP_JUMP_IF_FALSE);
// INCREMENT
int increment = current(C)->count;
LoopList loop = {.start = increment, .depth = C->scope->depth + 1, .next = C->loop, .is_for = true};
C->loop = &loop;
if (match(C, TOKEN_COMMA)) {
expression_statement(C);
} else {
emit_word(C, OP_INCREMENT_LOCAL_AND_SET, index, 1);
}
HymnByteCode *code = current(C);
int count = code->count - increment;
uint8_t *instructions = hymn_malloc_int(count, sizeof(uint8_t));
int *lines = hymn_malloc_int(count, sizeof(int));
hymn_mem_copy(instructions, &code->instructions[increment], count, sizeof(uint8_t));
hymn_mem_copy(lines, &code->lines[increment], count, sizeof(int));
code->count = increment;
// BODY
consume(C, TOKEN_LEFT_CURLY, "expected '{' after for loop declaration");
block(C);
// INCREMENT
patch_jump_for_list(C);
while (code->count + count > code->capacity) {
code->capacity *= 2;
code->instructions = hymn_realloc_int(code->instructions, code->capacity, sizeof(uint8_t));
code->lines = hymn_realloc_int(code->lines, code->capacity, sizeof(int));
}
hymn_mem_copy(&code->instructions[code->count], instructions, count, sizeof(uint8_t));
hymn_mem_copy(&code->lines[code->count], lines, count, sizeof(int));
code->count += count;
free(instructions);
free(lines);
emit_loop(C, compare);
// END
C->loop = loop.next;
patch_jump(C, jump);
patch_jump_list(C);
end_scope(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of for loop");
}
static void while_statement(Compiler *C) {
int start = current(C)->count;
LoopList loop = {.start = start, .depth = C->scope->depth + 1, .next = C->loop, .is_for = false};
C->loop = &loop;
expression(C);
int jump = emit_jump(C, OP_JUMP_IF_FALSE);
consume(C, TOKEN_LEFT_CURLY, "expected '{' after while loop declaration");
block(C);
emit_loop(C, start);
C->loop = loop.next;
patch_jump(C, jump);
patch_jump_list(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of while loop");
}
static void return_statement(Compiler *C) {
if (C->scope->type != TYPE_FUNCTION) {
compile_error(C, &C->previous, "return statement outside of function");
}
if (check(C, TOKEN_RIGHT_CURLY)) {
emit(C, OP_VOID);
} else {
expression(C);
emit(C, OP_RETURN);
}
}
static void pop_stack_loop(Compiler *C) {
int depth = C->loop->depth;
Scope *scope = C->scope;
for (int i = scope->local_count; i > 0; i--) {
if (scope->locals[i - 1].depth < depth) {
return;
}
emit_pop(C);
}
}
static void break_statement(Compiler *C) {
if (C->loop == NULL) {
compile_error(C, &C->previous, "break statement outside of loop");
return;
}
pop_stack_loop(C);
JumpList *jump_next = C->jump;
JumpList *jump = hymn_malloc(sizeof(JumpList));
jump->jump = emit_jump(C, OP_JUMP);
jump->depth = C->loop->depth;
jump->next = jump_next;
C->jump = jump;
}
static void continue_statement(Compiler *C) {
if (C->loop == NULL) {
compile_error(C, &C->previous, "continue statement outside of loop");
return;
}
pop_stack_loop(C);
if (C->loop->is_for) {
JumpList *jump_next = C->jump_for;
JumpList *jump = hymn_malloc(sizeof(JumpList));
jump->jump = emit_jump(C, OP_JUMP);
jump->depth = C->loop->depth;
jump->next = jump_next;
C->jump_for = jump;
} else {
emit_loop(C, C->loop->start);
}
}
static void try_statement(Compiler *C) {
HymnByteCode *code = current(C);
HymnExceptList *except = hymn_calloc(1, sizeof(HymnExceptList));
except->locals = C->scope->local_count;
except->start = code->count;
HymnFunction *func = C->scope->func;
except->next = func->except;
func->except = except;
consume(C, TOKEN_LEFT_CURLY, "expected '{' after try declaration");
begin_scope(C);
while (!check(C, TOKEN_RIGHT_CURLY) && !check(C, TOKEN_EOF)) {
declaration(C);
}
end_scope(C);
int jump = emit_jump(C, OP_JUMP);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of try statement");
consume(C, TOKEN_EXCEPT, "expected 'except' at end of try statement");
except->end = code->count;
begin_scope(C);
uint8_t message = variable(C, "expected variable name in exception declaration");
finalize_variable(C, message);
consume(C, TOKEN_LEFT_CURLY, "expected '{' after exception declaration");
while (!check(C, TOKEN_RIGHT_CURLY) && !check(C, TOKEN_EOF)) {
declaration(C);
}
end_scope(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of exception statement");
patch_jump(C, jump);
}
static void echo_statement(Compiler *C) {
expression(C);
emit(C, OP_ECHO);
}
static void print_statement(Compiler *C) {
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'print'");
expression(C);
consume(C, TOKEN_COMMA, "not enough arguments in call to 'print' (expected 2)");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'print'");
emit(C, OP_PRINT);
}
static void push_statement(Compiler *C) {
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'push'");
expression(C);
consume(C, TOKEN_COMMA, "not enough arguments in call to 'push' (expected 2)");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'push'");
emit(C, OP_ARRAY_PUSH);
}
static void insert_statement(Compiler *C) {
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'insert'");
expression(C);
consume(C, TOKEN_COMMA, "not enough arguments in call to 'insert' (expected 3)");
expression(C);
consume(C, TOKEN_COMMA, "not enough arguments in call to 'insert' (expected 3)");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'insert'");
emit(C, OP_INSERT);
}
static void use_statement(Compiler *C) {
expression(C);
emit(C, OP_USE);
}
static void throw_statement(Compiler *C) {
expression(C);
emit(C, OP_THROW);
}
static void statement(Compiler *C) {
if (match(C, TOKEN_ECHO)) {
echo_statement(C);
} else if (match(C, TOKEN_PRINT)) {
print_statement(C);
} else if (match(C, TOKEN_PUSH)) {
push_statement(C);
} else if (match(C, TOKEN_INSERT)) {
insert_statement(C);
} else if (match(C, TOKEN_USE)) {
use_statement(C);
} else if (match(C, TOKEN_IF)) {
if_statement(C);
} else if (match(C, TOKEN_FOR)) {
for_statement(C);
} else if (match(C, TOKEN_WHILE)) {
while_statement(C);
} else if (match(C, TOKEN_RETURN)) {
return_statement(C);
} else if (match(C, TOKEN_BREAK)) {
break_statement(C);
} else if (match(C, TOKEN_CONTINUE)) {
continue_statement(C);
} else if (match(C, TOKEN_TRY)) {
try_statement(C);
} else if (match(C, TOKEN_THROW)) {
throw_statement(C);
} else if (match(C, TOKEN_LEFT_CURLY)) {
block(C);
consume(C, TOKEN_RIGHT_CURLY, "expected '}' at end of block statement");
} else {
expression_statement(C);
}
}
static void array_pop_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'pop'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'pop'");
emit(C, OP_ARRAY_POP);
}
static void delete_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'delete'");
expression(C);
consume(C, TOKEN_COMMA, "not enough arguments in call to 'delete' (expected 2)");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'delete'");
emit(C, OP_DELETE);
}
static void len_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'len'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'len'");
emit(C, OP_LEN);
}
static void cast_integer_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'int'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'int'");
emit(C, OP_INT);
}
static void cast_float_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'float'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'float'");
emit(C, OP_FLOAT);
}
static void cast_string_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'str'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'str'");
emit(C, OP_STRING);
}
static void type_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'type'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'type'");
emit(C, OP_TYPE);
}
static void clear_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'clear'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'clear'");
emit(C, OP_CLEAR);
}
static void copy_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'copy'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'copy'");
emit(C, OP_COPY);
}
static void keys_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'keys'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'keys'");
emit(C, OP_KEYS);
}
static void index_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'index'");
expression(C);
consume(C, TOKEN_COMMA, "not enough arguments in call to 'index' (expected 2)");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'index'");
emit(C, OP_INDEX);
}
static void exists_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'exists'");
expression(C);
consume(C, TOKEN_COMMA, "not enough arguments in call to 'exists' (expected 2)");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'exists'");
emit(C, OP_EXISTS);
}
static void source_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'SOURCE'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'SOURCE'");
emit(C, OP_SOURCE);
}
static void opcode_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'OPCODES'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'OPCODES'");
emit(C, OP_CODES);
}
static void stack_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'STACK'");
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'STACK'");
emit(C, OP_STACK);
}
static void reference_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'REFERENCE'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'REFERENCE'");
emit(C, OP_REFERENCE);
}
static void format_expression(Compiler *C, bool assign) {
(void)assign;
consume(C, TOKEN_LEFT_PAREN, "expected opening '(' in call to 'FORMAT'");
expression(C);
consume(C, TOKEN_RIGHT_PAREN, "expected closing ')' in call to 'FORMAT'");
emit(C, OP_FORMAT);
}
static void expression_statement(Compiler *C) {
expression(C);
emit_pop(C);
}
static void expression(Compiler *C) {
compile_with_precedence(C, PRECEDENCE_ASSIGN);
}
static HymnFrame *parent_frame(Hymn *H, int offset) {
int frame_count = H->frame_count;
if (offset > frame_count) return NULL;
return &H->frames[frame_count - offset];
}
static HymnFrame *current_frame(Hymn *H) {
return &H->frames[H->frame_count - 1];
}
static CompileResult compile(Hymn *H, const char *script, const char *source, enum FunctionType type) {
Scope scope = {0};
Compiler C = {0};
C.row = 1;
C.column = 1;
C.script = script;
C.source = source;
C.interactive = type == TYPE_REPL;
C.size = strlen(source);
C.previous.type = TOKEN_UNDEFINED;
C.current.type = TOKEN_UNDEFINED;
C.string_status = STRING_STATUS_NONE;
C.H = H;
C.pop = -1;
C.barrier = -1;
scope_init(&C, &scope, type, 0);
advance(&C);
while (!match(&C, TOKEN_EOF)) {
declaration(&C);
}
HymnFunction *func = end_function(&C);
if (C.error != NULL) {
char *error = string_to_chars(C.error);
hymn_string_delete(C.error);
function_delete(func);
return (CompileResult){.func = NULL, .error = error};
}
return (CompileResult){.func = func, .error = NULL};
}
HymnString *hymn_quote_string(HymnString *string) {
size_t len = hymn_string_len(string);
size_t extra = 2;
for (size_t s = 0; s < len; s++) {
char c = string[s];
if (c == '\\' || c == '\"') {
extra++;
}
}
HymnString *quoted = hymn_new_string_with_capacity(len + extra);
quoted[0] = '"';
size_t q = 1;
for (size_t s = 0; s < len; s++) {
char c = string[s];
if (c == '\\') {
quoted[q++] = '\\';
quoted[q++] = '\\';
} else if (c == '"') {
quoted[q++] = '\\';
quoted[q++] = '"';
} else {
quoted[q++] = c;
}
}
HymnStringHead *head = hymn_string_head(quoted);
head->length = head->capacity;
quoted[len + extra - 1] = '"';
quoted[len + extra] = '\0';
return quoted;
}
static void reset_stack(Hymn *H) {
H->stack_top = H->stack;
H->frame_count = 0;
}
#ifdef HYMN_NO_MEMORY
void hymn_reference_string(HymnObjectString *string) {
(void)string;
}
#else
void hymn_reference_string(HymnObjectString *string) {
string->count++;
}
#endif
#ifdef HYMN_NO_MEMORY
void hymn_reference(HymnValue value) {
(void)value;
}
#else
void hymn_reference(HymnValue value) {
switch (value.is) {
case HYMN_VALUE_STRING:
((HymnObjectString *)value.as.o)->count++;
return;
case HYMN_VALUE_ARRAY:
((HymnArray *)value.as.o)->count++;
return;
case HYMN_VALUE_TABLE:
((HymnTable *)value.as.o)->count++;
return;
case HYMN_VALUE_FUNC:
((HymnFunction *)value.as.o)->count++;
return;
case HYMN_VALUE_FUNC_NATIVE:
((HymnNativeFunction *)value.as.o)->count++;
return;
default:
return;
}
}
#endif
#ifdef HYMN_NO_MEMORY
void hymn_dereference_string(Hymn *H, HymnObjectString *string) {
(void)H;
(void)string;
}
#else
void hymn_dereference_string(Hymn *H, HymnObjectString *string) {
int count = --string->count;
assert(count >= 0);
if (count == 0) {
set_remove(&H->strings, string->string);
hymn_string_delete(string->string);
free(string);
}
}
#endif
#ifdef HYMN_NO_MEMORY
void hymn_dereference(Hymn *H, HymnValue value) {
(void)H;
(void)value;
}
#else
void hymn_dereference(Hymn *H, HymnValue value) {
switch (value.is) {
case HYMN_VALUE_STRING: {
HymnObjectString *string = (HymnObjectString *)value.as.o;
hymn_dereference_string(H, string);
return;
}
case HYMN_VALUE_ARRAY: {
HymnArray *array = (HymnArray *)value.as.o;
int count = --array->count;
assert(count >= 0);
if (count == 0) {
hymn_array_delete(H, array);
}
return;
}
case HYMN_VALUE_TABLE: {
HymnTable *table = (HymnTable *)value.as.o;
int count = --table->count;
assert(count >= 0);
if (count == 0) {
table_delete(H, table);
}
return;
}
case HYMN_VALUE_FUNC: {
HymnFunction *func = (HymnFunction *)value.as.o;
int count = --func->count;
assert(count >= 0);
if (count == 0) {
function_delete(func);
}
return;
}
case HYMN_VALUE_FUNC_NATIVE: {
HymnNativeFunction *func = (HymnNativeFunction *)value.as.o;
int count = --func->count;
assert(count >= 0);
if (count == 0) {
native_function_delete(H, func);
}
return;
}
default:
return;
}
}
#endif
static void push(Hymn *H, HymnValue value) {
*H->stack_top = value;
H->stack_top++;
}
static HymnValue peek(Hymn *H, int dist) {
assert(&H->stack_top[-dist] >= H->stack);
return H->stack_top[-dist];
}
static HymnValue pop(Hymn *H) {
assert(&H->stack_top[-1] >= H->stack);
H->stack_top--;
return *H->stack_top;
}
static void push_string(Hymn *H, HymnString *string) {
HymnObjectString *intern = hymn_intern_string(H, string);
hymn_reference_string(intern);
push(H, hymn_new_string_value(intern));
}
static HymnFrame *exception(Hymn *H) {
HymnFrame *frame = current_frame(H);
while (true) {
HymnFunction *func = frame->func;
uint8_t *instructions = func->code.instructions;
HymnExceptList *except = NULL;
HymnExceptList *range = func->except;
while (range != NULL) {
if (frame->ip >= &instructions[range->start] && frame->ip <= &instructions[range->end]) {
except = range;
break;
}
range = range->next;
}
HymnValue message = pop(H);
if (except != NULL) {
while (H->stack_top != &frame->stack[except->locals]) {
hymn_dereference(H, pop(H));
}
frame->ip = &instructions[except->end];
push(H, message);
return frame;
}
while (H->stack_top != frame->stack) {
hymn_dereference(H, pop(H));
}
H->frame_count--;
if (H->frame_count == 0 || func->name == NULL) {
assert(H->error == NULL);
H->error = hymn_value_to_string(message);
hymn_dereference(H, message);
return NULL;
}
push(H, message);
frame = current_frame(H);
}
}
static HymnString *stacktrace(Hymn *H) {
HymnString *trace = hymn_new_string("");
for (int i = H->frame_count - 1; i >= 0; i--) {
HymnFrame *frame = &H->frames[i];
HymnFunction *func = frame->func;
int row = func->code.lines[frame->ip - func->code.instructions - 1];
if (func->name == NULL) {
if (func->script == NULL) {
trace = string_append_format(trace, " at script:%d", row);
} else {
trace = string_append_format(trace, " at %s:%d", func->script, row);
}
} else if (func->script == NULL) {
trace = string_append_format(trace, " at %s script:%d", func->name, row);
} else {
trace = string_append_format(trace, " at %s %s:%d", func->name, func->script, row);
}
if (i > 0) trace = hymn_string_append_char(trace, '\n');
}
return trace;
}
static HymnFrame *push_error(Hymn *H, HymnString *error) {
HymnObjectString *message = hymn_intern_string(H, error);
hymn_reference_string(message);
push(H, hymn_new_string_value(message));
return exception(H);
}
static HymnFrame *throw_existing_error(Hymn *H, char *error) {
HymnString *message = hymn_new_string(error);
free(error);
return push_error(H, message);
}
static HymnFrame *throw_error(Hymn *H, const char *format, ...) {
va_list ap;
va_start(ap, format);
size_t len = (size_t)vsnprintf(NULL, 0, format, ap);
va_end(ap);
char *chars = hymn_malloc((len + 1) * sizeof(char));
va_start(ap, format);
len = (size_t)vsnprintf(chars, len + 1, format, ap);
va_end(ap);
HymnString *error = hymn_new_string_with_capacity(len + 128);
error = hymn_string_append(error, chars);
free(chars);
HymnString *trace = stacktrace(H);
error = hymn_string_append(error, "\n");
error = hymn_string_append(error, trace);
hymn_string_delete(trace);
return push_error(H, error);
}
static HymnFrame *throw_exception(Hymn *H, const char *name) {
HymnString *trace = stacktrace(H);
HymnString *error = hymn_new_string_with_capacity(hymn_string_len(H->exception) + hymn_string_len(trace) + 128);
error = hymn_string_append(error, H->exception);
hymn_string_delete(H->exception);
H->exception = NULL;
error = hymn_string_append(error, "\n in ");
error = hymn_string_append(error, name);
HymnFrame *frame = &H->frames[H->frame_count - 1];
HymnFunction *func = frame->func;
int row = func->code.lines[frame->ip - func->code.instructions - 1];
if (func->script == NULL) {
error = string_append_format(error, " script:%d\n", row);
} else {
error = string_append_format(error, " %s:%d\n", func->script, row);
}
error = hymn_string_append(error, trace);
hymn_string_delete(trace);
return push_error(H, error);
}
static HymnFrame *throw_error_string(Hymn *H, HymnString *string) {
HymnFrame *frame = throw_error(H, string);
hymn_string_delete(string);
return frame;
}
HymnValue hymn_new_exception(Hymn *H, const char *error) {
H->exception = hymn_new_string(error);
return hymn_new_none();
}
HymnValue hymn_arity_exception(Hymn *H, int expected, int actual) {
H->exception = hymn_string_format("expected: %d function argument(s) but was: %d", expected, actual);
return hymn_new_none();
}
HymnValue hymn_type_exception(Hymn *H, enum HymnValueType expected, enum HymnValueType actual) {
H->exception = hymn_string_format("expected type: %s but was: %s", hymn_value_type(expected), hymn_value_type(actual));
return hymn_new_none();
}
static HymnFrame *call(Hymn *H, HymnFunction *func, int count) {
if (count != func->arity) {
if (count < func->arity) return throw_error(H, "not enough arguments in call to '%s' (expected %d)", func->name, func->arity);
return throw_error(H, "too many arguments in call to '%s' (expected %d)", func->name, func->arity);
} else if (H->frame_count == HYMN_FRAMES_MAX) {
return throw_error(H, "stack overflow");
}
HymnFrame *frame = &H->frames[H->frame_count++];
frame->func = func;
frame->ip = func->code.instructions;
frame->stack = H->stack_top - count - 1;
return frame;
}
static HymnFrame *call_value(Hymn *H, HymnValue value, int count) {
switch (value.is) {
case HYMN_VALUE_FUNC:
return call(H, hymn_as_func(value), count);
case HYMN_VALUE_FUNC_NATIVE: {
HymnNativeFunction *native = hymn_as_native(value);
HymnNativeCall func = native->func;
HymnValue result = func(H, count, H->stack_top - count);
HymnValue *top = H->stack_top - count - 1;
while (H->stack_top != top) {
hymn_dereference(H, pop(H));
}
if (H->exception != NULL) {
return throw_exception(H, native->name->string);
} else {
hymn_reference(result);
push(H, result);
return current_frame(H);
}
}
default: {
const char *is = hymn_value_type(value.is);
return throw_error(H, "can't call %s (expected function)", is);
}
}
}
static HymnFrame *import(Hymn *H, HymnObjectString *file) {
HymnTable *imports = H->imports;
HymnString *script = NULL;
int p = 1;
while (true) {
HymnFrame *frame = parent_frame(H, p);
if (frame == NULL) {
break;
}
script = frame->func->script;
if (script) {
break;
}
p++;
}
HymnString *look = hymn_path_convert(file->string);
HymnString *parent = script ? hymn_path_parent(script) : NULL;
HymnObjectString *module = NULL;
HymnArray *paths = H->paths;
HymnInt size = paths->length;
for (HymnInt i = 0; i < size; i++) {
HymnValue value = paths->items[i];
if (!hymn_is_string(value)) {
continue;
}
HymnString *question = hymn_as_string(value);
HymnString *replace = hymn_string_replace(question, "<path>", look);
HymnString *path = parent ? hymn_string_replace(replace, "<parent>", parent) : hymn_string_copy(replace);
HymnObjectString *use = hymn_intern_string(H, hymn_path_absolute(path));
hymn_reference_string(use);
hymn_string_delete(path);
hymn_string_delete(replace);
if (!hymn_is_undefined(table_get(imports, use))) {
hymn_dereference_string(H, use);
hymn_string_delete(look);
if (parent) hymn_string_delete(parent);
return current_frame(H);
}
if (hymn_file_exists(use->string)) {
module = use;
break;
}
hymn_dereference_string(H, use);
}
if (module == NULL) {
HymnString *missing = hymn_string_format("import not found: %s", look);
for (HymnInt i = 0; i < size; i++) {
HymnValue value = paths->items[i];
if (!hymn_is_string(value)) {
continue;
}
HymnString *question = hymn_as_string(value);
HymnString *replace = hymn_string_replace(question, "<path>", look);
HymnString *path = parent ? hymn_string_replace(replace, "<parent>", parent) : hymn_string_copy(replace);
HymnString *use = hymn_path_absolute(path);
missing = string_append_format(missing, "\n no file: %s", use);
hymn_string_delete(path);
hymn_string_delete(replace);
hymn_string_delete(use);
}
hymn_string_delete(look);
if (parent) hymn_string_delete(parent);
return throw_error_string(H, missing);
}
hymn_string_delete(look);
if (parent != NULL) {
hymn_string_delete(parent);
}
table_put(imports, module, hymn_new_bool(true));
HymnString *module_string = module->string;
#ifndef HYMN_NO_DYNAMIC_LIBS
size_t len = hymn_string_len(module_string);
size_t lib_len = strlen(HYMN_DLIB_EXTENSION);
if (len > lib_len && memcmp(&module_string[len - lib_len], HYMN_DLIB_EXTENSION, lib_len) == 0) {
HymnString *error = hymn_use_dlib(H, module_string, "hymn_import");
if (error != NULL) {
hymn_string_trim(error);
HymnString *failed = hymn_string_format("error reading file: %s: %s\n", module_string, error);
hymn_string_delete(error);
return throw_error_string(H, failed);
}
return current_frame(H);
}
#endif
HymnString *source = hymn_read_file(module_string);
if (source == NULL) {
HymnString *failed = hymn_string_format("error reading file: %s\n", module_string);
return throw_error_string(H, failed);
}
CompileResult result = compile(H, module_string, source, TYPE_SCRIPT);
hymn_string_delete(source);
char *error = result.error;
if (error != NULL) {
return throw_existing_error(H, error);
}
HymnFunction *func = result.func;
HymnValue function = hymn_new_func_value(func);
func->count = 1;
push(H, function);
call(H, func, 0);
error = interpret(H);
if (error != NULL) return throw_existing_error(H, error);
return current_frame(H);
}
static int debug_constant_instruction(HymnString **debug, const char *name, HymnByteCode *code, int index) {
uint8_t constant = code->instructions[index + 1];
HymnString *value = debug_value_to_string(code->constants.values[constant]);
*debug = string_append_format(*debug, "%s: [%d] [%s]", name, constant, value);
hymn_string_delete(value);
return index + 2;
}
static int debug_two_constant_instruction(HymnString **debug, const char *name, HymnByteCode *code, int index) {
uint8_t constant = code->instructions[index + 1];
HymnString *value = debug_value_to_string(code->constants.values[constant]);
uint8_t constant2 = code->instructions[index + 2];
HymnString *value2 = debug_value_to_string(code->constants.values[constant2]);
*debug = string_append_format(*debug, "%s: [%d] [%s] & [%d] [%s]", name, constant, value, constant2, value2);
hymn_string_delete(value);
hymn_string_delete(value2);
return index + 3;
}
static int debug_byte_instruction(HymnString **debug, const char *name, HymnByteCode *code, int index) {
uint8_t byte = code->instructions[index + 1];
*debug = string_append_format(*debug, "%s: [%d]", name, byte);
return index + 2;
}
static int debug_jump_instruction(HymnString **debug, const char *name, int sign, HymnByteCode *code, int index) {
int jump = ((int)code->instructions[index + 1] << 8) | (int)code->instructions[index + 2];
*debug = string_append_format(*debug, "%s: [%zu] -> [%zu]", name, index, sign < 0 ? index + 3 - jump : index + 3 + jump);
return index + 3;
}
static int debug_register_jump_instruction(HymnString **debug, const char *name, HymnByteCode *code, int index) {
uint8_t slot_a = code->instructions[index + 1];
uint8_t slot_b = code->instructions[index + 2];
int jump = ((int)code->instructions[index + 3] << 8) | (int)code->instructions[index + 4];
*debug = string_append_format(*debug, "%s: [%d] [%d] ? [%zu] -> [%zu]", name, slot_a, slot_b, index, index + 5 + jump);
return index + 5;
}
static int debug_three_byte_instruction(HymnString **debug, const char *name, HymnByteCode *code, int index) {
uint8_t byte = code->instructions[index + 1];
uint8_t next = code->instructions[index + 2];
*debug = string_append_format(*debug, "%s: [%d] [%d]", name, byte, next);
return index + 3;
}
static int debug_for_loop_instruction(HymnString **debug, const char *name, int sign, HymnByteCode *code, int index) {
uint8_t slot = code->instructions[index + 1];
int jump = ((int)code->instructions[index + 2] << 8) | (int)code->instructions[index + 3];
*debug = string_append_format(*debug, "%s: [%d] [%zu] -> [%zu]", name, slot, index, sign < 0 ? index + 4 - jump : index + 4 + jump);
return index + 4;
}
static int debug_increment_loop_instruction(HymnString **debug, const char *name, HymnByteCode *code, int index) {
uint8_t slot = code->instructions[index + 1];
uint8_t increment = code->instructions[index + 2];
int jump = ((int)code->instructions[index + 3] << 8) | (int)code->instructions[index + 4];
*debug = string_append_format(*debug, "%s: [%d] [%d] & [%zu] -> [%zu]", name, slot, increment, index, index + 5 - jump);
return index + 5;
}
static int debug_instruction(HymnString **debug, const char *name, int index) {
*debug = string_append_format(*debug, "%s", name);
return index + 1;
}
static int disassemble_instruction(HymnString **debug, HymnByteCode *code, int index) {
*debug = string_append_format(*debug, "%04zu ", index);
if (index > 0 && code->lines[index] == code->lines[index - 1]) {
*debug = hymn_string_append(*debug, " | ");
} else {
*debug = string_append_format(*debug, "%4d ", code->lines[index]);
}
uint8_t instruction = code->instructions[index];
switch (instruction) {
case OP_ADD: return debug_instruction(debug, "OP_ADD", index);
case OP_ADD_LOCALS: return debug_three_byte_instruction(debug, "OP_ADD_LOCALS", code, index);
case OP_INSERT: return debug_instruction(debug, "OP_INSERT", index);
case OP_ARRAY_POP: return debug_instruction(debug, "OP_ARRAY_POP", index);
case OP_ARRAY_PUSH: return debug_instruction(debug, "OP_ARRAY_PUSH", index);
case OP_ARRAY_PUSH_LOCALS: return debug_three_byte_instruction(debug, "OP_ARRAY_PUSH_LOCALS", code, index);
case OP_BIT_AND: return debug_instruction(debug, "OP_BIT_AND", index);
case OP_BIT_LEFT_SHIFT: return debug_instruction(debug, "OP_BIT_LEFT_SHIFT", index);
case OP_BIT_NOT: return debug_instruction(debug, "OP_BIT_NOT", index);
case OP_BIT_OR: return debug_instruction(debug, "OP_BIT_OR", index);
case OP_BIT_RIGHT_SHIFT: return debug_instruction(debug, "OP_BIT_RIGHT_SHIFT", index);
case OP_BIT_XOR: return debug_instruction(debug, "OP_BIT_XOR", index);
case OP_CALL: return debug_byte_instruction(debug, "OP_CALL", code, index);
case OP_SELF: return debug_constant_instruction(debug, "OP_SELF", code, index);
case OP_CLEAR: return debug_instruction(debug, "OP_CLEAR", index);
case OP_CONSTANT: return debug_constant_instruction(debug, "OP_CONSTANT", code, index);
case OP_NEW_ARRAY: return debug_instruction(debug, "OP_NEW_ARRAY", index);
case OP_NEW_TABLE: return debug_instruction(debug, "OP_NEW_TABLE", index);
case OP_COPY: return debug_instruction(debug, "OP_COPY", index);
case OP_DEFINE_GLOBAL: return debug_constant_instruction(debug, "OP_DEFINE_GLOBAL", code, index);
case OP_CODES: return debug_instruction(debug, "OP_CODES", index);
case OP_STACK: return debug_instruction(debug, "OP_STACK", index);
case OP_REFERENCE: return debug_instruction(debug, "OP_REFERENCE", index);
case OP_FORMAT: return debug_instruction(debug, "OP_FORMAT", index);
case OP_DELETE: return debug_instruction(debug, "OP_DELETE", index);
case OP_DIVIDE: return debug_instruction(debug, "OP_DIVIDE", index);
case OP_DUPLICATE: return debug_instruction(debug, "OP_DUPLICATE", index);
case OP_EQUAL: return debug_instruction(debug, "OP_EQUAL", index);
case OP_ECHO: return debug_instruction(debug, "OP_ECHO", index);
case OP_EXISTS: return debug_instruction(debug, "OP_EXISTS", index);
case OP_FALSE: return debug_instruction(debug, "OP_FALSE", index);
case OP_FOR: return debug_for_loop_instruction(debug, "OP_FOR", 1, code, index);
case OP_FOR_LOOP: return debug_for_loop_instruction(debug, "OP_FOR_LOOP", -1, code, index);
case OP_GET_DYNAMIC: return debug_instruction(debug, "OP_GET_DYNAMIC", index);
case OP_GET_GLOBAL: return debug_constant_instruction(debug, "OP_GET_GLOBAL", code, index);
case OP_GET_GLOBAL_PROPERTY: return debug_two_constant_instruction(debug, "OP_GET_GLOBAL_PROPERTY", code, index);
case OP_GET_LOCAL: return debug_byte_instruction(debug, "OP_GET_LOCAL", code, index);
case OP_GET_PROPERTY: return debug_constant_instruction(debug, "OP_GET_PROPERTY", code, index);
case OP_GET_LOCALS: return debug_three_byte_instruction(debug, "OP_GET_LOCALS", code, index);
case OP_GREATER: return debug_instruction(debug, "OP_GREATER", index);
case OP_GREATER_EQUAL: return debug_instruction(debug, "OP_GREATER_EQUAL", index);
case OP_INCREMENT: return debug_byte_instruction(debug, "OP_INCREMENT", code, index);
case OP_INCREMENT_LOCAL: return debug_three_byte_instruction(debug, "OP_INCREMENT_LOCAL", code, index);
case OP_INCREMENT_LOCAL_AND_SET: return debug_three_byte_instruction(debug, "OP_INCREMENT_LOCAL_AND_SET", code, index);
case OP_INCREMENT_LOOP: return debug_increment_loop_instruction(debug, "OP_INCREMENT_LOOP", code, index);
case OP_INDEX: return debug_instruction(debug, "OP_INDEX", index);
case OP_SOURCE: return debug_instruction(debug, "OP_SOURCE", index);
case OP_JUMP: return debug_jump_instruction(debug, "OP_JUMP", 1, code, index);
case OP_JUMP_IF_EQUAL: return debug_jump_instruction(debug, "OP_JUMP_IF_EQUAL", 1, code, index);
case OP_JUMP_IF_FALSE: return debug_jump_instruction(debug, "OP_JUMP_IF_FALSE", 1, code, index);
case OP_JUMP_IF_GREATER: return debug_jump_instruction(debug, "OP_JUMP_IF_GREATER", 1, code, index);
case OP_JUMP_IF_GREATER_LOCALS: return debug_register_jump_instruction(debug, "OP_JUMP_IF_GREATER_LOCALS", code, index);
case OP_JUMP_IF_GREATER_EQUAL: return debug_jump_instruction(debug, "OP_JUMP_IF_GREATER_EQUAL", 1, code, index);
case OP_JUMP_IF_LESS: return debug_jump_instruction(debug, "OP_JUMP_IF_LESS", 1, code, index);
case OP_JUMP_IF_LESS_EQUAL: return debug_jump_instruction(debug, "OP_JUMP_IF_LESS_EQUAL", 1, code, index);
case OP_JUMP_IF_NOT_EQUAL: return debug_jump_instruction(debug, "OP_JUMP_IF_NOT_EQUAL", 1, code, index);
case OP_JUMP_IF_TRUE: return debug_jump_instruction(debug, "OP_JUMP_IF_TRUE", 1, code, index);
case OP_KEYS: return debug_instruction(debug, "OP_KEYS", index);
case OP_LEN: return debug_instruction(debug, "OP_LEN", index);
case OP_LESS: return debug_instruction(debug, "OP_LESS", index);
case OP_LESS_EQUAL: return debug_instruction(debug, "OP_LESS_EQUAL", index);
case OP_LOOP: return debug_jump_instruction(debug, "OP_LOOP", -1, code, index);
case OP_MODULO: return debug_instruction(debug, "OP_MODULO", index);
case OP_MODULO_LOCALS: return debug_three_byte_instruction(debug, "OP_MODULO_LOCALS", code, index);
case OP_MULTIPLY: return debug_instruction(debug, "OP_MULTIPLY", index);
case OP_NEGATE: return debug_instruction(debug, "OP_NEGATE", index);
case OP_NONE: return debug_instruction(debug, "OP_NONE", index);
case OP_NOT: return debug_instruction(debug, "OP_NOT", index);
case OP_NOT_EQUAL: return debug_instruction(debug, "OP_NOT_EQUAL", index);
case OP_POP: return debug_instruction(debug, "OP_POP", index);
case OP_POP_N: return debug_byte_instruction(debug, "OP_POP_N", code, index);
case OP_POP_TWO: return debug_instruction(debug, "OP_POP_TWO", index);
case OP_PRINT: return debug_instruction(debug, "OP_PRINT", index);
case OP_RETURN: return debug_instruction(debug, "OP_RETURN", index);
case OP_VOID: return debug_instruction(debug, "OP_VOID", index);
case OP_SET_DYNAMIC: return debug_instruction(debug, "OP_SET_DYNAMIC", index);
case OP_SET_GLOBAL: return debug_constant_instruction(debug, "OP_SET_GLOBAL", code, index);
case OP_SET_LOCAL: return debug_byte_instruction(debug, "OP_SET_LOCAL", code, index);
case OP_SET_PROPERTY: return debug_constant_instruction(debug, "OP_SET_PROPERTY", code, index);
case OP_SLICE: return debug_instruction(debug, "OP_SLICE", index);
case OP_SUBTRACT: return debug_instruction(debug, "OP_SUBTRACT", index);
case OP_TAIL_CALL: return debug_byte_instruction(debug, "OP_TAIL_CALL", code, index);
case OP_THROW: return debug_instruction(debug, "OP_THROW", index);
case OP_FLOAT: return debug_instruction(debug, "OP_FLOAT", index);
case OP_INT: return debug_instruction(debug, "OP_INT", index);
case OP_STRING: return debug_instruction(debug, "OP_STRING", index);
case OP_TRUE: return debug_instruction(debug, "OP_TRUE", index);
case OP_TYPE: return debug_instruction(debug, "OP_TYPE", index);
case OP_USE: return debug_instruction(debug, "OP_USE", index);
default: *debug = string_append_format(*debug, "UNKNOWN_OPCODE %d\n", instruction); return index + 1;
}
}
static HymnString *disassemble_byte_code(HymnByteCode *code) {
HymnString *debug = hymn_new_string("");
if (code->count > 0) {
int offset = disassemble_instruction(&debug, code, 0);
while (offset < code->count) {
debug = hymn_string_append_char(debug, '\n');
offset = disassemble_instruction(&debug, code, offset);
}
}
return debug;
}
#define READ_BYTE(F) (*F->ip++)
#define READ_SHORT(F) (F->ip += 2, (((int)F->ip[-2] << 8) | (int)F->ip[-1]))
#define GET_CONSTANT(F, B) (F->func->code.constants.values[B])
#define READ_CONSTANT(F) (GET_CONSTANT(F, READ_BYTE(F)))
#define THROW(...) \
frame = throw_error(H, ##__VA_ARGS__); \
if (frame == NULL) { \
return; \
} \
goto dispatch;
#define COMPARE_OP(compare) \
HymnValue b = pop(H); \
HymnValue a = pop(H); \
if (hymn_is_int(a)) { \
if (hymn_is_int(b)) { \
push(H, hymn_new_bool(hymn_as_int(a) compare hymn_as_int(b))); \
} else if (hymn_is_float(b)) { \
push(H, hymn_new_bool((HymnFloat)hymn_as_int(a) compare hymn_as_float(b))); \
} else { \
const char *is_a = hymn_value_type(a.is); \
const char *is_b = hymn_value_type(b.is); \
hymn_dereference(H, a); \
hymn_dereference(H, b); \
THROW("comparison '" #compare "' can't use %s and %s (expected numbers)", is_a, is_b) \
} \
} else if (hymn_is_float(a)) { \
if (hymn_is_int(b)) { \
push(H, hymn_new_bool(hymn_as_float(a) compare(HymnFloat) hymn_as_int(b))); \
} else if (hymn_is_float(b)) { \
push(H, hymn_new_bool(hymn_as_float(a) compare hymn_as_float(b))); \
} else { \
const char *is_a = hymn_value_type(a.is); \
const char *is_b = hymn_value_type(b.is); \
hymn_dereference(H, a); \
hymn_dereference(H, b); \
THROW("comparison '" #compare "' can't use %s and %s (expected numbers)", is_a, is_b) \
} \
} else { \
const char *is_a = hymn_value_type(a.is); \
const char *is_b = hymn_value_type(b.is); \
hymn_dereference(H, a); \
hymn_dereference(H, b); \
THROW("comparison '" #compare "' can't use %s and %s (expected numbers)", is_a, is_b) \
}
#define JUMP_COMPARE_OP(compare) \
HymnValue b = pop(H); \
HymnValue a = pop(H); \
bool answer; \
if (hymn_is_int(a)) { \
if (hymn_is_int(b)) { \
answer = hymn_as_int(a) compare hymn_as_int(b); \
} else if (hymn_is_float(b)) { \
answer = (HymnFloat)hymn_as_int(a) compare hymn_as_float(b); \
} else { \
hymn_dereference(H, a); \
hymn_dereference(H, b); \
THROW("comparison: operands must be numbers") \
} \
} else if (hymn_is_float(a)) { \
if (hymn_is_int(b)) { \
answer = hymn_as_float(a) compare(HymnFloat) hymn_as_int(b); \
} else if (hymn_is_float(b)) { \
answer = hymn_as_float(a) compare hymn_as_float(b); \
} else { \
hymn_dereference(H, a); \
hymn_dereference(H, b); \
THROW("comparison: operands must be numbers") \
} \
} else { \
hymn_dereference(H, a); \
hymn_dereference(H, b); \
THROW("comparison: operands must be numbers") \
} \
int jump = READ_SHORT(frame); \
if (answer) { \
frame->ip += jump; \
}
static void run(Hymn *H) {
HymnFrame *frame = current_frame(H);
dispatch:
switch (READ_BYTE(frame)) {
case OP_VOID: {
H->frame_count--;
bool done = H->frame_count == 0 || frame->func->name == NULL;
while (H->stack_top != frame->stack) {
hymn_dereference(H, pop(H));
}
if (done) {
return;
}
push(H, hymn_new_none());
frame = current_frame(H);
goto dispatch;
}
case OP_RETURN: {
HymnValue result = pop(H);
H->frame_count--;
bool done = H->frame_count == 0 || frame->func->name == NULL;
while (H->stack_top != frame->stack) {
hymn_dereference(H, pop(H));
}
if (done) {
return;
}
push(H, result);
frame = current_frame(H);
goto dispatch;
}
case OP_POP: {
hymn_dereference(H, pop(H));
goto dispatch;
}
case OP_POP_TWO: {
hymn_dereference(H, pop(H));
hymn_dereference(H, pop(H));
goto dispatch;
}
case OP_POP_N: {
int count = READ_BYTE(frame);
while (count--) {
hymn_dereference(H, pop(H));
}
goto dispatch;
}
case OP_TRUE: {
push(H, hymn_new_bool(true));
goto dispatch;
}
case OP_FALSE: {
push(H, hymn_new_bool(false));
goto dispatch;
}
case OP_NONE: {
push(H, hymn_new_none());
goto dispatch;
}
case OP_CALL: {
int count = READ_BYTE(frame);
HymnValue value = peek(H, count + 1);
frame = call_value(H, value, count);
if (frame == NULL) {
return;
}
goto dispatch;
}
case OP_TAIL_CALL: {
int count = READ_BYTE(frame);
HymnValue value = peek(H, count + 1);
if (!hymn_is_func(value)) {
frame = call_value(H, value, count);
} else {
HymnFunction *func = hymn_as_func(value);
if (count != func->arity) {
if (count < func->arity) frame = throw_error(H, "not enough arguments in call to '%s' (expected %d)", func->name, func->arity);
frame = throw_error(H, "too many arguments in call to '%s' (expected %d)", func->name, func->arity);
if (frame == NULL) {
return;
}
} else {
HymnValue *top = H->stack_top;
HymnValue *new_frame = top - count - 1;
HymnValue *bottom = frame->stack;
HymnValue *shift = new_frame;
while (bottom != new_frame) {
hymn_dereference(H, *bottom);
if (shift != top) {
*bottom = *shift;
shift++;
}
bottom++;
}
while (shift != top) {
*bottom = *shift;
shift++;
bottom++;
}
H->stack_top = frame->stack + count + 1;
frame->func = func;
frame->ip = func->code.instructions;
}
}
if (frame == NULL) {
return;
}
goto dispatch;
}
case OP_SELF: {
HymnValue table = peek(H, 1);
if (!hymn_is_table(table)) {
const char *is = hymn_value_type(table.is);
THROW("can't get property of %s (expected table)", is)
}
HymnObjectString *name = hymn_as_hymn_string(READ_CONSTANT(frame));
HymnValue fun = table_get(hymn_as_table(table), name);
*(H->stack_top - 1) = fun;
hymn_reference(fun);
*H->stack_top = table;
H->stack_top++;
goto dispatch;
}
case OP_JUMP: {
int jump = READ_SHORT(frame);
frame->ip += jump;
goto dispatch;
}
case OP_JUMP_IF_FALSE: {
HymnValue a = pop(H);
int jump = READ_SHORT(frame);
if (hymn_value_false(a)) {
frame->ip += jump;
}
hymn_dereference(H, a);
goto dispatch;
}
case OP_JUMP_IF_TRUE: {
HymnValue a = pop(H);
int jump = READ_SHORT(frame);
if (!hymn_value_false(a)) {
frame->ip += jump;
}
hymn_dereference(H, a);
goto dispatch;
}
case OP_JUMP_IF_EQUAL: {
HymnValue b = pop(H);
HymnValue a = pop(H);
int jump = READ_SHORT(frame);
if (hymn_values_equal(a, b)) {
frame->ip += jump;
}
hymn_dereference(H, a);
hymn_dereference(H, b);
goto dispatch;
}
case OP_JUMP_IF_NOT_EQUAL: {
HymnValue b = pop(H);
HymnValue a = pop(H);
int jump = READ_SHORT(frame);
if (!hymn_values_equal(a, b)) {
frame->ip += jump;
}
hymn_dereference(H, a);
hymn_dereference(H, b);
goto dispatch;
}
case OP_JUMP_IF_LESS: {
JUMP_COMPARE_OP(<)
goto dispatch;
}
case OP_JUMP_IF_LESS_EQUAL: {
JUMP_COMPARE_OP(<=)
goto dispatch;
}
case OP_JUMP_IF_GREATER: {
JUMP_COMPARE_OP(>)
goto dispatch;
}
case OP_JUMP_IF_GREATER_LOCALS: {
HymnValue a = frame->stack[READ_BYTE(frame)];
HymnValue b = frame->stack[READ_BYTE(frame)];
int jump = READ_SHORT(frame);
bool answer;
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
answer = hymn_as_int(a) > hymn_as_int(b);
} else if (hymn_is_float(b)) {
answer = (HymnFloat)hymn_as_int(a) > hymn_as_float(b);
} else {
THROW("comparison: operands must be numbers")
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
answer = hymn_as_float(a) > (HymnFloat)hymn_as_int(b);
} else if (hymn_is_float(b)) {
answer = hymn_as_float(a) > hymn_as_float(b);
} else {
THROW("comparison: operands must be numbers")
}
} else {
THROW("comparison: operands must be numbers")
}
if (answer) {
frame->ip += jump;
}
goto dispatch;
}
case OP_JUMP_IF_GREATER_EQUAL: {
JUMP_COMPARE_OP(>=)
goto dispatch;
}
case OP_LOOP: {
int jump = READ_SHORT(frame);
frame->ip -= jump;
goto dispatch;
}
case OP_INCREMENT_LOOP: {
int slot = READ_BYTE(frame);
int increment = READ_BYTE(frame);
int jump = READ_SHORT(frame);
HymnValue value = frame->stack[slot];
if (hymn_is_int(value)) {
value.as.i += (HymnInt)increment;
} else if (hymn_is_float(value)) {
value.as.f += (HymnFloat)increment;
} else {
const char *is = hymn_value_type(value.is);
THROW("expected a number but was '%s'", is)
}
frame->stack[slot] = value;
frame->ip -= jump;
goto dispatch;
}
case OP_FOR: {
int slot = READ_BYTE(frame);
HymnValue object = frame->stack[slot];
H->stack_top += 2;
if (hymn_is_table(object)) {
HymnTable *table = hymn_as_table(object);
HymnTableItem *next = table_next(table, NULL);
if (next == NULL) {
frame->stack[slot + 1] = hymn_new_none();
frame->stack[slot + 2] = hymn_new_none();
int jump = READ_SHORT(frame);
frame->ip += jump;
} else {
frame->stack[slot + 1] = hymn_new_string_value(next->key);
frame->stack[slot + 2] = next->value;
hymn_reference_string(next->key);
hymn_reference(next->value);
frame->ip += 2;
}
} else if (hymn_is_array(object)) {
HymnArray *array = hymn_as_array(object);
if (array->length == 0) {
frame->stack[slot + 1] = hymn_new_none();
frame->stack[slot + 2] = hymn_new_none();
int jump = READ_SHORT(frame);
frame->ip += jump;
} else {
HymnValue item = array->items[0];
frame->stack[slot + 1] = hymn_new_int(0);
frame->stack[slot + 2] = item;
hymn_reference(item);
frame->ip += 2;
}
} else {
frame->stack[slot + 1] = hymn_new_none();
frame->stack[slot + 2] = hymn_new_none();
const char *is = hymn_value_type(object.is);
THROW("can't iterate over %s (expected array or table)", is)
}
goto dispatch;
}
case OP_FOR_LOOP: {
int slot = READ_BYTE(frame);
HymnValue object = frame->stack[slot];
int index = slot + 1;
int value = slot + 2;
if (hymn_is_table(object)) {
HymnTable *table = hymn_as_table(object);
HymnObjectString *key = hymn_as_hymn_string(frame->stack[index]);
HymnTableItem *next = table_next(table, key);
if (next == NULL) {
frame->ip += 2;
} else {
hymn_dereference(H, frame->stack[index]);
hymn_dereference(H, frame->stack[value]);
frame->stack[index] = hymn_new_string_value(next->key);
frame->stack[value] = next->value;
hymn_reference_string(next->key);
hymn_reference(next->value);
int jump = READ_SHORT(frame);
frame->ip -= jump;
}
} else {
HymnArray *array = hymn_as_array(object);
HymnInt key = hymn_as_int(frame->stack[index]) + 1;
if (key >= array->length) {
frame->ip += 2;
} else {
hymn_dereference(H, frame->stack[value]);
HymnValue item = array->items[key];
frame->stack[index].as.i++;
frame->stack[value] = item;
hymn_reference(item);
int jump = READ_SHORT(frame);
frame->ip -= jump;
}
}
goto dispatch;
}
case OP_EQUAL: {
HymnValue b = pop(H);
HymnValue a = pop(H);
push(H, hymn_new_bool(hymn_values_equal(a, b)));
hymn_dereference(H, a);
hymn_dereference(H, b);
goto dispatch;
}
case OP_NOT_EQUAL: {
HymnValue b = pop(H);
HymnValue a = pop(H);
push(H, hymn_new_bool(!hymn_values_equal(a, b)));
hymn_dereference(H, a);
hymn_dereference(H, b);
goto dispatch;
}
case OP_LESS: {
COMPARE_OP(<)
goto dispatch;
}
case OP_LESS_EQUAL: {
COMPARE_OP(<=)
goto dispatch;
}
case OP_GREATER: {
COMPARE_OP(>)
goto dispatch;
}
case OP_GREATER_EQUAL: {
COMPARE_OP(>=)
goto dispatch;
}
case OP_ADD: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_none(a)) {
if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add;
}
} else if (hymn_is_bool(a)) {
if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add;
}
} else if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
a.as.i += b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
b.as.f += (HymnFloat)a.as.i;
push(H, a);
} else if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
a.as.f += (HymnFloat)b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
a.as.f += b.as.f;
push(H, a);
} else if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add;
}
} else if (hymn_is_string(a)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add;
}
hymn_dereference(H, a);
hymn_dereference(H, b);
goto dispatch;
bad_add:;
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("can't add %s and %s", is_a, is_b)
}
case OP_ADD_LOCALS: {
HymnValue a = frame->stack[READ_BYTE(frame)];
HymnValue b = frame->stack[READ_BYTE(frame)];
if (hymn_is_none(a)) {
if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add_two;
}
} else if (hymn_is_bool(a)) {
if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add_two;
}
} else if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
a.as.i += b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
b.as.f += (HymnFloat)a.as.i;
push(H, a);
} else if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add_two;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
a.as.f += (HymnFloat)b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
a.as.f += b.as.f;
push(H, a);
} else if (hymn_is_string(b)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add_two;
}
} else if (hymn_is_string(a)) {
push_string(H, value_concat(a, b));
} else {
goto bad_add_two;
}
goto dispatch;
bad_add_two:;
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("can't add %s and %s", is_a, is_b)
}
case OP_INCREMENT: {
HymnValue a = pop(H);
HymnInt increment = READ_BYTE(frame);
if (hymn_is_none(a)) {
goto bad_increment;
} else if (hymn_is_bool(a)) {
goto bad_increment;
} else if (hymn_is_int(a)) {
a.as.i += increment;
push(H, a);
} else if (hymn_is_float(a)) {
a.as.f += (HymnFloat)increment;
push(H, a);
} else if (hymn_is_string(a)) {
push_string(H, value_concat(a, hymn_new_int(increment)));
} else {
goto bad_increment;
}
hymn_dereference(H, a);
goto dispatch;
bad_increment:;
const char *is = hymn_value_type(a.is);
hymn_dereference(H, a);
THROW("can't increment %s", is)
}
case OP_SUBTRACT: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
a.as.i -= b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
HymnValue new = hymn_new_float((HymnFloat)a.as.i);
new.as.f -= b.as.f;
push(H, new);
} else {
goto bad_subtract;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
a.as.f -= (HymnFloat)b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
a.as.f -= b.as.f;
push(H, a);
} else {
goto bad_subtract;
}
} else {
goto bad_subtract;
}
goto dispatch;
bad_subtract:;
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("can't subtract %s and %s (expected numbers)", is_a, is_b)
}
case OP_MULTIPLY: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
a.as.i *= b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
HymnValue new = hymn_new_float((HymnFloat)a.as.i);
new.as.f *= b.as.f;
push(H, new);
} else {
goto bad_multiply;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
a.as.f *= (HymnFloat)b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
a.as.f *= b.as.f;
push(H, a);
} else {
goto bad_multiply;
}
} else {
goto bad_multiply;
}
goto dispatch;
bad_multiply:;
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("can't multiply %s and %s (expected numbers)", is_a, is_b)
}
case OP_DIVIDE: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
a.as.i /= b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
HymnValue new = hymn_new_float((HymnFloat)a.as.i);
new.as.f /= b.as.f;
push(H, new);
} else {
goto bad_divide;
}
} else if (hymn_is_float(a)) {
if (hymn_is_int(b)) {
a.as.f /= (HymnFloat)b.as.i;
push(H, a);
} else if (hymn_is_float(b)) {
a.as.f /= b.as.f;
push(H, a);
} else {
goto bad_divide;
}
} else {
goto bad_divide;
}
goto dispatch;
bad_divide:;
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("can't divide %s and %s (expected numbers)", is_a, is_b)
}
case OP_MODULO: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
a.as.i %= b.as.i;
push(H, a);
} else {
goto bad_modulo;
}
} else {
goto bad_modulo;
}
goto dispatch;
bad_modulo:;
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("can't modulo %s and %s (expected integers)", is_a, is_b)
}
case OP_MODULO_LOCALS: {
HymnValue a = frame->stack[READ_BYTE(frame)];
HymnValue b = frame->stack[READ_BYTE(frame)];
if (hymn_is_int(a)) {
if (hymn_is_int(b)) {
a.as.i %= b.as.i;
push(H, a);
} else {
goto bad_modulo_locals;
}
} else {
goto bad_modulo_locals;
}
goto dispatch;
bad_modulo_locals:;
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
THROW("can't modulo %s and %s (expected integers)", is_a, is_b)
}
case OP_BIT_NOT: {
HymnValue value = pop(H);
if (hymn_is_int(value)) {
value.as.i = ~value.as.i;
push(H, value);
} else {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("bitwise '~' can't use %s (expected integer)", is)
}
goto dispatch;
}
case OP_BIT_OR: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a) && hymn_is_int(b)) {
a.as.i |= b.as.i;
push(H, a);
} else {
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("bitwise '|' can't use %s and %s (expected integers)", is_a, is_b)
}
goto dispatch;
}
case OP_BIT_AND: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a) && hymn_is_int(b)) {
a.as.i &= b.as.i;
push(H, a);
} else {
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("bitwise '&' can't use %s and %s (expected integers)", is_a, is_b)
}
goto dispatch;
}
case OP_BIT_XOR: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a) && hymn_is_int(b)) {
a.as.i ^= b.as.i;
push(H, a);
} else {
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("bitwise '^' can't use %s and %s (expected integers)", is_a, is_b)
}
goto dispatch;
}
case OP_BIT_LEFT_SHIFT: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a) && hymn_is_int(b)) {
a.as.i <<= b.as.i;
push(H, a);
} else {
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("bitwise '<<' can't use %s and %s (expected integers)", is_a, is_b)
}
goto dispatch;
}
case OP_BIT_RIGHT_SHIFT: {
HymnValue b = pop(H);
HymnValue a = pop(H);
if (hymn_is_int(a) && hymn_is_int(b)) {
a.as.i >>= b.as.i;
push(H, a);
} else {
const char *is_a = hymn_value_type(a.is);
const char *is_b = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("bitwise '>>' can't use %s and %s (expected integers)", is_a, is_b)
}
goto dispatch;
}
case OP_NEGATE: {
HymnValue value = pop(H);
if (hymn_is_int(value)) {
value.as.i = -value.as.i;
} else if (hymn_is_float(value)) {
value.as.f = -value.as.f;
} else {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("negation '-' can't use %s (expected number)", is)
}
push(H, value);
goto dispatch;
}
case OP_NOT: {
HymnValue value = pop(H);
if (hymn_is_bool(value)) {
value.as.b = !value.as.b;
} else {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("'not' can't use %s (expected boolean)", is)
}
push(H, value);
goto dispatch;
}
case OP_CONSTANT: {
HymnValue constant = READ_CONSTANT(frame);
hymn_reference(constant);
push(H, constant);
goto dispatch;
}
case OP_NEW_ARRAY: {
HymnValue constant = hymn_new_array_value(hymn_new_array(0));
hymn_reference(constant);
push(H, constant);
goto dispatch;
}
case OP_NEW_TABLE: {
HymnValue constant = hymn_new_table_value(hymn_new_table());
hymn_reference(constant);
push(H, constant);
goto dispatch;
}
case OP_DEFINE_GLOBAL: {
HymnObjectString *name = hymn_as_hymn_string(READ_CONSTANT(frame));
HymnValue value = pop(H);
HymnValue previous = table_put(&H->globals, name, value);
if (hymn_is_undefined(previous)) {
hymn_reference_string(name);
} else {
table_put(&H->globals, name, previous);
hymn_dereference(H, value);
THROW("multiple global definitions of '%s'", name->string)
}
goto dispatch;
}
case OP_SET_GLOBAL: {
HymnObjectString *name = hymn_as_hymn_string(READ_CONSTANT(frame));
HymnValue value = peek(H, 1);
HymnValue previous = table_put(&H->globals, name, value);
if (hymn_is_undefined(previous)) {
hymn_reference_string(name);
} else {
hymn_dereference(H, previous);
}
hymn_reference(value);
goto dispatch;
}
case OP_GET_GLOBAL: {
HymnObjectString *name = hymn_as_hymn_string(READ_CONSTANT(frame));
HymnValue get = table_get(&H->globals, name);
if (hymn_is_undefined(get)) {
THROW("undefined global '%s'", name->string)
}
hymn_reference(get);
push(H, get);
goto dispatch;
}
case OP_GET_GLOBAL_PROPERTY: {
HymnObjectString *name = hymn_as_hymn_string(READ_CONSTANT(frame));
HymnObjectString *property = hymn_as_hymn_string(READ_CONSTANT(frame));
HymnValue global = table_get(&H->globals, name);
if (hymn_is_undefined(global)) {
THROW("undefined global '%s'", name->string)
} else if (!hymn_is_table(global)) {
const char *is = hymn_value_type(global.is);
THROW("can't get property of %s (expected table)", is)
}
HymnTable *table = hymn_as_table(global);
HymnValue get = table_get(table, property);
if (hymn_is_undefined(get)) {
get.is = HYMN_VALUE_NONE;
} else {
hymn_reference(get);
}
push(H, get);
goto dispatch;
}
case OP_SET_LOCAL: {
int slot = READ_BYTE(frame);
HymnValue value = peek(H, 1);
hymn_dereference(H, frame->stack[slot]);
frame->stack[slot] = value;
hymn_reference(value);
goto dispatch;
}
case OP_GET_LOCAL: {
int slot = READ_BYTE(frame);
HymnValue value = frame->stack[slot];
hymn_reference(value);
push(H, value);
goto dispatch;
}
case OP_GET_LOCALS: {
int slot_a = READ_BYTE(frame);
int slot_b = READ_BYTE(frame);
HymnValue value_a = frame->stack[slot_a];
HymnValue value_b = frame->stack[slot_b];
hymn_reference(value_a);
hymn_reference(value_b);
push(H, value_a);
push(H, value_b);
goto dispatch;
}
case OP_INCREMENT_LOCAL: {
int slot = READ_BYTE(frame);
int increment = READ_BYTE(frame);
HymnValue value = frame->stack[slot];
if (hymn_is_int(value)) {
value.as.i += (HymnInt)increment;
} else if (hymn_is_float(value)) {
value.as.f += (HymnFloat)increment;
} else {
const char *is = hymn_value_type(value.is);
THROW("can't increment %s (expected number)", is)
}
push(H, value);
goto dispatch;
}
case OP_INCREMENT_LOCAL_AND_SET: {
int slot = READ_BYTE(frame);
int increment = READ_BYTE(frame);
HymnValue value = frame->stack[slot];
if (hymn_is_int(value)) {
value.as.i += (HymnInt)increment;
} else if (hymn_is_float(value)) {
value.as.f += (HymnFloat)increment;
} else {
const char *is = hymn_value_type(value.is);
THROW("can't increment %s (expected number)", is)
}
frame->stack[slot] = value;
goto dispatch;
}
case OP_SET_PROPERTY: {
HymnValue value = pop(H);
HymnValue table_value = pop(H);
if (!hymn_is_table(table_value)) {
const char *is = hymn_value_type(table_value.is);
hymn_dereference(H, value);
hymn_dereference(H, table_value);
THROW("can't set property of %s (expected table)", is)
}
HymnTable *table = hymn_as_table(table_value);
HymnObjectString *name = hymn_as_hymn_string(READ_CONSTANT(frame));
hymn_set_property(H, table, name, value);
push(H, value);
hymn_dereference(H, table_value);
goto dispatch;
}
case OP_GET_PROPERTY: {
HymnValue value = pop(H);
if (!hymn_is_table(value)) {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("can't get property of %s (expected table)", is)
}
HymnTable *table = hymn_as_table(value);
HymnObjectString *name = hymn_as_hymn_string(READ_CONSTANT(frame));
HymnValue get = table_get(table, name);
if (hymn_is_undefined(get)) {
get.is = HYMN_VALUE_NONE;
} else {
hymn_reference(get);
}
hymn_dereference(H, value);
push(H, get);
goto dispatch;
}
case OP_EXISTS: {
HymnValue value = pop(H);
HymnValue object = pop(H);
if (!hymn_is_table(object)) {
const char *is = hymn_value_type(object.is);
hymn_dereference(H, value);
hymn_dereference(H, object);
THROW("call to 'exists' can't use %s for 1st argument (expected table)", is)
}
if (!hymn_is_string(value)) {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
hymn_dereference(H, object);
THROW("call to 'exists' can't use %s for 2nd argument (expected string)", is)
}
HymnTable *table = hymn_as_table(object);
HymnObjectString *name = hymn_as_hymn_string(value);
HymnValue g = table_get(table, name);
if (hymn_is_undefined(g)) {
push(H, hymn_new_bool(false));
} else {
push(H, hymn_new_bool(true));
}
hymn_dereference(H, value);
hymn_dereference(H, object);
goto dispatch;
}
case OP_SET_DYNAMIC: {
HymnValue value = pop(H);
HymnValue property = pop(H);
HymnValue object = pop(H);
if (hymn_is_array(object)) {
if (!hymn_is_int(property)) {
const char *is = hymn_value_type(property.is);
hymn_dereference(H, value);
hymn_dereference(H, property);
hymn_dereference(H, object);
THROW("array assignment index can't be %s (expected integer)", is)
}
HymnArray *array = hymn_as_array(object);
HymnInt size = array->length;
HymnInt index = hymn_as_int(property);
if (index > size) {
hymn_dereference(H, value);
hymn_dereference(H, property);
hymn_dereference(H, object);
THROW("array assignment index out of bounds: %d > %d", index, size)
}
if (index < 0) {
index = size + index;
if (index < 0) {
hymn_dereference(H, value);
hymn_dereference(H, property);
hymn_dereference(H, object);
THROW("negative array assignment index: %d", index)
}
}
if (index == size) {
hymn_array_push(array, value);
} else {
hymn_dereference(H, array->items[index]);
array->items[index] = value;
}
} else if (hymn_is_table(object)) {
if (!hymn_is_string(property)) {
const char *is = hymn_value_type(property.is);
hymn_dereference(H, value);
hymn_dereference(H, property);
hymn_dereference(H, object);
THROW("table assignment key can't be %s (expected string)", is)
}
HymnTable *table = hymn_as_table(object);
HymnObjectString *name = hymn_as_hymn_string(property);
HymnValue previous = table_put(table, name, value);
if (hymn_is_undefined(previous)) {
hymn_reference_string(name);
} else {
hymn_dereference(H, previous);
}
} else {
const char *is = hymn_value_type(object.is);
hymn_dereference(H, value);
hymn_dereference(H, property);
hymn_dereference(H, object);
THROW("can't assign value to %s (expected array or table)", is)
}
push(H, value);
hymn_dereference(H, object);
hymn_reference(value);
goto dispatch;
}
case OP_GET_DYNAMIC: {
HymnValue i = pop(H);
HymnValue v = pop(H);
switch (v.is) {
case HYMN_VALUE_STRING: {
if (!hymn_is_int(i)) {
const char *is = hymn_value_type(i.is);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("string index can't be %s (expected integer)", is)
}
HymnString *string = hymn_as_string(v);
HymnInt size = (HymnInt)hymn_string_len(string);
HymnInt index = hymn_as_int(i);
if (index >= size) {
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("string index out of bounds: %d >= %d", index, size)
}
if (index < 0) {
index = size + index;
if (index < 0) {
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("negative string index: %d", index)
}
}
char c = string[index];
push_string(H, char_to_string(c));
hymn_dereference(H, v);
break;
}
case HYMN_VALUE_ARRAY: {
if (!hymn_is_int(i)) {
const char *is = hymn_value_type(i.is);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("array index can't be %s (expected integer)", is)
}
HymnArray *array = hymn_as_array(v);
HymnInt size = array->length;
HymnInt index = hymn_as_int(i);
if (index >= size) {
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("array index out of bounds: %d >= %d", index, size)
}
if (index < 0) {
index = size + index;
if (index < 0) {
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("negative array index: %d", index)
}
}
HymnValue g = hymn_array_get(array, index);
hymn_reference(g);
push(H, g);
hymn_dereference(H, v);
break;
}
case HYMN_VALUE_TABLE: {
if (!hymn_is_string(i)) {
const char *is = hymn_value_type(i.is);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("table key can't be %s (expected string)", is)
}
HymnTable *table = hymn_as_table(v);
HymnObjectString *name = hymn_as_hymn_string(i);
HymnValue g = table_get(table, name);
if (hymn_is_undefined(g)) {
g.is = HYMN_VALUE_NONE;
} else {
hymn_reference(g);
}
push(H, g);
hymn_dereference(H, i);
hymn_dereference(H, v);
break;
}
default: {
const char *is = hymn_value_type(v.is);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("can't get value from %s (expected array, table, or string)", is)
}
}
goto dispatch;
}
case OP_LEN: {
HymnValue value = pop(H);
switch (value.is) {
case HYMN_VALUE_STRING: {
HymnInt len = (HymnInt)hymn_string_len(hymn_as_string(value));
push(H, hymn_new_int(len));
break;
}
case HYMN_VALUE_ARRAY: {
HymnInt len = hymn_as_array(value)->length;
push(H, hymn_new_int(len));
break;
}
case HYMN_VALUE_TABLE: {
HymnInt len = (HymnInt)hymn_as_table(value)->size;
push(H, hymn_new_int(len));
break;
}
default: {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("call to 'len' can't use %s (expected array, string, or table)", is)
}
}
hymn_dereference(H, value);
goto dispatch;
}
case OP_ARRAY_POP: {
HymnValue a = pop(H);
if (!hymn_is_array(a)) {
const char *is = hymn_value_type(a.is);
hymn_dereference(H, a);
THROW("call to 'pop' can't use %s (expected array)", is)
} else {
HymnValue value = hymn_array_pop(hymn_as_array(a));
push(H, value);
hymn_dereference(H, a);
}
goto dispatch;
}
case OP_ARRAY_PUSH: {
HymnValue value = pop(H);
HymnValue array = pop(H);
if (!hymn_is_array(array)) {
const char *is = hymn_value_type(array.is);
hymn_dereference(H, array);
hymn_dereference(H, value);
THROW("call to 'push' can't use %s for 1st argument (expected array)", is)
} else {
hymn_array_push(hymn_as_array(array), value);
hymn_dereference(H, array);
}
goto dispatch;
}
case OP_ARRAY_PUSH_LOCALS: {
HymnValue array = frame->stack[READ_BYTE(frame)];
if (!hymn_is_array(array)) {
const char *is = hymn_value_type(array.is);
THROW("call to 'push' can't use %s for 1st argument (expected array)", is)
} else {
HymnValue value = frame->stack[READ_BYTE(frame)];
hymn_array_push(hymn_as_array(array), value);
}
goto dispatch;
}
case OP_INSERT: {
HymnValue p = pop(H);
HymnValue i = pop(H);
HymnValue v = pop(H);
if (hymn_is_array(v)) {
if (!hymn_is_int(i)) {
const char *is = hymn_value_type(i.is);
hymn_dereference(H, p);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("call to 'insert' can't use %s for 2nd argument (expected integer)", is)
}
HymnArray *array = hymn_as_array(v);
HymnInt size = array->length;
HymnInt index = hymn_as_int(i);
if (index > size) {
hymn_dereference(H, p);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("index out of bounds in call to 'insert': %d > %d", index, size)
}
if (index < 0) {
index = size + index;
if (index < 0) {
hymn_dereference(H, p);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("negative index in 'insert' call: %d", index)
}
}
if (index == size) {
hymn_array_push(array, p);
} else {
hymn_array_insert(array, index, p);
}
hymn_dereference(H, v);
} else {
const char *is = hymn_value_type(v.is);
hymn_dereference(H, p);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("call to 'insert' can't use %s for 1st argument (expected array)", is)
}
goto dispatch;
}
case OP_DELETE: {
HymnValue i = pop(H);
HymnValue v = pop(H);
if (hymn_is_array(v)) {
if (!hymn_is_int(i)) {
const char *is = hymn_value_type(i.is);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("call to 'delete' can't use %s for 2nd argument (expected integer)", is)
}
HymnArray *array = hymn_as_array(v);
HymnInt size = array->length;
HymnInt index = hymn_as_int(i);
if (index >= size) {
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("index out of bounds in call to 'delete': %d >= %d.", index, size)
}
if (index < 0) {
index = size + index;
if (index < 0) {
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("negative index in 'delete' call: %d", index)
}
}
HymnValue value = hymn_array_remove_index(array, index);
push(H, value);
hymn_dereference(H, v);
} else if (hymn_is_table(v)) {
if (!hymn_is_string(i)) {
const char *is = hymn_value_type(i.is);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("call to 'delete' can't use %s for 2nd argument (expected string)", is)
}
HymnTable *table = hymn_as_table(v);
HymnObjectString *name = hymn_as_hymn_string(i);
HymnValue value = table_remove(table, name);
if (hymn_is_undefined(value)) {
value.is = HYMN_VALUE_NONE;
} else {
hymn_dereference_string(H, name);
}
push(H, value);
hymn_dereference(H, v);
hymn_dereference_string(H, name);
} else {
const char *is = hymn_value_type(v.is);
hymn_dereference(H, i);
hymn_dereference(H, v);
THROW("call to 'delete' can't use %s for 1st argument (expected array or table)", is)
}
goto dispatch;
}
case OP_COPY: {
HymnValue value = pop(H);
switch (value.is) {
case HYMN_VALUE_NONE:
case HYMN_VALUE_BOOL:
case HYMN_VALUE_INTEGER:
case HYMN_VALUE_FLOAT:
case HYMN_VALUE_STRING:
case HYMN_VALUE_FUNC:
case HYMN_VALUE_FUNC_NATIVE:
push(H, value);
break;
case HYMN_VALUE_ARRAY: {
HymnArray *copy = new_array_copy(hymn_as_array(value));
HymnValue new = hymn_new_array_value(copy);
push(H, new);
hymn_reference(new);
hymn_dereference(H, value);
break;
}
case HYMN_VALUE_TABLE: {
HymnTable *copy = new_table_copy(hymn_as_table(value));
HymnValue new = hymn_new_table_value(copy);
push(H, new);
hymn_reference(new);
hymn_dereference(H, value);
break;
}
default:
push(H, hymn_new_none());
}
goto dispatch;
}
case OP_SLICE: {
HymnValue b = pop(H);
HymnValue a = pop(H);
HymnValue v = pop(H);
if (!hymn_is_int(a)) {
const char *is = hymn_value_type(a.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("slice can't use %s (expected integer)", is)
}
HymnInt start = hymn_as_int(a);
if (start < 0) {
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("negative slice start: %d", start)
}
if (hymn_is_string(v)) {
HymnString *original = hymn_as_string(v);
HymnInt size = (HymnInt)hymn_string_len(original);
HymnInt end;
if (hymn_is_int(b)) {
end = hymn_as_int(b);
} else if (hymn_is_none(b)) {
end = size;
} else {
const char *is = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("slice can't use %s (expected integer)", is)
}
if (end > size) {
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("slice out of bounds: %d > %d", end, size)
}
if (end < 0) {
end = size + end;
if (end < 0) {
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("negative slice end: %d", end)
}
}
if (start >= end) {
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("slice out of range: %d >= %d", start, end)
}
HymnString *sub = hymn_substring(original, (size_t)start, (size_t)end);
push_string(H, sub);
} else if (hymn_is_array(v)) {
HymnArray *array = hymn_as_array(v);
HymnInt size = array->length;
HymnInt end;
if (hymn_is_int(b)) {
end = hymn_as_int(b);
} else if (hymn_is_none(b)) {
end = size;
} else {
const char *is = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("slice can't use %s (expected integer)", is)
}
if (end > size) {
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("slice out of bounds: %d > %d", end, size)
}
if (end < 0) {
end = size + end;
if (end < 0) {
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("negative slice end: %d", end)
}
}
if (start >= end) {
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("slice out of range: %d >= %d", start, end)
}
HymnArray *copy = new_array_slice(array, start, end);
HymnValue new = hymn_new_array_value(copy);
hymn_reference(new);
push(H, new);
} else {
const char *is = hymn_value_type(v.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
hymn_dereference(H, v);
THROW("can't slice %s (expected string or array)", is)
}
hymn_dereference(H, v);
goto dispatch;
}
case OP_CLEAR: {
HymnValue value = pop(H);
switch (value.is) {
case HYMN_VALUE_BOOL:
push(H, hymn_new_bool(false));
break;
case HYMN_VALUE_INTEGER:
push(H, hymn_new_int(0));
break;
case HYMN_VALUE_FLOAT:
push(H, hymn_new_float(0.0));
break;
case HYMN_VALUE_STRING:
push_string(H, hymn_new_string(""));
break;
case HYMN_VALUE_ARRAY: {
HymnArray *array = hymn_as_array(value);
hymn_array_clear(H, array);
push(H, value);
break;
}
case HYMN_VALUE_TABLE: {
HymnTable *table = hymn_as_table(value);
table_clear(H, table);
push(H, value);
break;
}
case HYMN_VALUE_UNDEFINED:
case HYMN_VALUE_NONE:
case HYMN_VALUE_FUNC:
case HYMN_VALUE_FUNC_NATIVE:
case HYMN_VALUE_POINTER:
push(H, hymn_new_none());
break;
default:
break;
}
goto dispatch;
}
case OP_KEYS: {
HymnValue value = pop(H);
if (!hymn_is_table(value)) {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("call to 'keys' can't use %s (expected table)", is)
} else {
HymnTable *table = hymn_as_table(value);
HymnArray *array = table_keys(table);
HymnValue keys = hymn_new_array_value(array);
hymn_reference(keys);
push(H, keys);
hymn_dereference(H, value);
}
goto dispatch;
}
case OP_INDEX: {
HymnValue b = pop(H);
HymnValue a = pop(H);
switch (a.is) {
case HYMN_VALUE_STRING: {
if (!hymn_is_string(b)) {
const char *is = hymn_value_type(b.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("call to 'index' can't use %s for 2nd argument (expected string)", is)
}
size_t index = 0;
bool found = string_find(hymn_as_string(a), hymn_as_string(b), &index);
if (found) {
push(H, hymn_new_int((HymnInt)index));
} else {
push(H, hymn_new_int(-1));
}
hymn_dereference(H, a);
hymn_dereference(H, b);
break;
}
case HYMN_VALUE_ARRAY:
push(H, hymn_new_int(hymn_array_index_of(hymn_as_array(a), b)));
hymn_dereference(H, a);
hymn_dereference(H, b);
break;
case HYMN_VALUE_TABLE: {
HymnObjectString *key = table_key_of(hymn_as_table(a), b);
if (key == NULL) {
push(H, hymn_new_none());
} else {
push(H, hymn_new_string_value(key));
}
hymn_dereference(H, a);
hymn_dereference(H, b);
break;
}
default: {
const char *is = hymn_value_type(a.is);
hymn_dereference(H, a);
hymn_dereference(H, b);
THROW("call to 'index' can't use %s for 1st argument (expected string, array, or table)", is)
}
}
goto dispatch;
}
case OP_TYPE: {
HymnValue value = pop(H);
const char *is = hymn_value_type(value.is);
push_string(H, hymn_new_string(is));
hymn_dereference(H, value);
goto dispatch;
}
case OP_INT: {
HymnValue value = pop(H);
if (hymn_is_int(value)) {
push(H, value);
} else if (hymn_is_float(value)) {
HymnInt number = (HymnInt)hymn_as_float(value);
push(H, hymn_new_int(number));
} else if (hymn_is_string(value)) {
HymnString *string = hymn_as_string(value);
char *end = NULL;
double number = strtod(string, &end);
if (string == end) {
push(H, hymn_new_none());
} else {
push(H, hymn_new_int((HymnInt)number));
}
hymn_dereference(H, value);
} else {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("can't cast %s to integer", is)
}
goto dispatch;
}
case OP_FLOAT: {
HymnValue value = pop(H);
if (hymn_is_int(value)) {
HymnFloat number = (HymnFloat)hymn_as_int(value);
push(H, hymn_new_float(number));
} else if (hymn_is_float(value)) {
push(H, value);
} else if (hymn_is_string(value)) {
HymnString *string = hymn_as_string(value);
char *end = NULL;
double number = strtod(string, &end);
if (string == end) {
push(H, hymn_new_none());
} else {
push(H, hymn_new_float(number));
}
hymn_dereference(H, value);
} else {
const char *is = hymn_value_type(value.is);
hymn_dereference(H, value);
THROW("can't cast %s to float", is)
}
goto dispatch;
}
case OP_STRING: {
HymnValue value = pop(H);
push_string(H, hymn_value_to_string(value));
hymn_dereference(H, value);
goto dispatch;
}
case OP_ECHO: {
HymnValue value = pop(H);
HymnString *string = hymn_value_to_string(value);
H->print("%s\n", string);
hymn_string_delete(string);
hymn_dereference(H, value);
goto dispatch;
}
case OP_PRINT: {
HymnValue value = pop(H);
HymnValue route = pop(H);
HymnString *string = hymn_value_to_string(value);
if (hymn_value_false(route)) {
H->print("%s", string);
} else {
H->print_error("%s", string);
}
hymn_string_delete(string);
hymn_dereference(H, value);
hymn_dereference(H, route);
goto dispatch;
}
case OP_SOURCE: {
HymnValue value = pop(H);
HymnString *inspect = NULL;
if (hymn_is_func(value)) {
HymnFunction *func = hymn_as_func(value);
if (func->source != NULL) inspect = hymn_string_copy(func->source);
}
if (inspect == NULL) inspect = hymn_value_to_string(value);
push_string(H, inspect);
hymn_dereference(H, value);
goto dispatch;
}
case OP_CODES: {
HymnValue value = pop(H);
HymnString *debug = NULL;
if (hymn_is_func(value)) {
HymnFunction *func = hymn_as_func(value);
debug = disassemble_byte_code(&func->code);
}
if (debug == NULL) debug = hymn_value_to_string(value);
push_string(H, debug);
hymn_dereference(H, value);
goto dispatch;
}
case OP_STACK: {
if (H->stack_top != H->stack) {
HymnString *debug = hymn_new_string("");
for (HymnValue *i = H->stack; i != H->stack_top; i++) {
debug = hymn_string_append_char(debug, '[');
HymnString *stack_debug = debug_value_to_string(*i);
debug = hymn_string_append(debug, stack_debug);
hymn_string_delete(stack_debug);
debug = hymn_string_append(debug, "]\n");
}
push_string(H, debug);
} else {
push_string(H, hymn_new_string(""));
}
goto dispatch;
}
case OP_REFERENCE: {
HymnValue value = pop(H);
int count = 0;
switch (value.is) {
case HYMN_VALUE_STRING:
count = ((HymnObjectString *)value.as.o)->count;
break;
case HYMN_VALUE_ARRAY:
count = ((HymnArray *)value.as.o)->count;
break;
case HYMN_VALUE_TABLE:
count = ((HymnTable *)value.as.o)->count;
break;
case HYMN_VALUE_FUNC:
count = ((HymnFunction *)value.as.o)->count;
break;
case HYMN_VALUE_FUNC_NATIVE:
count = ((HymnNativeFunction *)value.as.o)->count;
break;
default:
break;
}
push(H, hymn_new_int(count));
hymn_dereference(H, value);
goto dispatch;
}
case OP_FORMAT: {
HymnValue value = pop(H);
HymnString *inspect = NULL;
if (hymn_is_func(value)) {
HymnFunction *func = hymn_as_func(value);
if (func->source != NULL) {
char *new = hymn_format(func->source);
inspect = hymn_new_string(new);
free(new);
}
} else if (hymn_is_string(value)) {
char *new = hymn_format(hymn_as_string(value));
inspect = hymn_new_string(new);
free(new);
}
if (inspect == NULL) inspect = hymn_value_to_string(value);
push_string(H, inspect);
hymn_dereference(H, value);
goto dispatch;
}
case OP_THROW: {
frame = exception(H);
if (frame == NULL) {
return;
}
goto dispatch;
}
case OP_DUPLICATE: {
HymnValue top = peek(H, 1);
push(H, top);
hymn_reference(top);
goto dispatch;
}
case OP_USE: {
HymnValue file = pop(H);
if (hymn_is_string(file)) {
frame = import(H, hymn_as_hymn_string(file));
hymn_dereference(H, file);
if (frame == NULL) {
return;
}
} else {
const char *is = hymn_value_type(file.is);
hymn_dereference(H, file);
THROW("import can't use %s (expected string)", is)
}
goto dispatch;
}
default:
UNREACHABLE();
}
}
static char *interpret(Hymn *H) {
run(H);
char *error = NULL;
if (H->error) {
error = string_to_chars(H->error);
hymn_string_delete(H->error);
H->error = NULL;
}
return error;
}
static void print_stdout(const char *format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
static void print_stderr(const char *format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
Hymn *new_hymn(void) {
Hymn *H = hymn_calloc(1, sizeof(Hymn));
reset_stack(H);
// STRINGS
set_init(&H->strings);
HymnObjectString *search_this = hymn_new_intern_string(H, "<parent>" PATH_SEP_STRING "<path>.hm");
hymn_reference_string(search_this);
HymnObjectString *search_relative = hymn_new_intern_string(H, "." PATH_SEP_STRING "<path>.hm");
hymn_reference_string(search_relative);
HymnObjectString *search_libs = hymn_new_intern_string(H, "." PATH_SEP_STRING "libs" PATH_SEP_STRING "<path>.hm");
hymn_reference_string(search_libs);
#ifndef HYMN_NO_DYNAMIC_LIBS
HymnObjectString *search_this_dynamic = hymn_new_intern_string(H, "<parent>" PATH_SEP_STRING "<path>" HYMN_DLIB_EXTENSION);
hymn_reference_string(search_this_dynamic);
HymnObjectString *search_relative_dynamic = hymn_new_intern_string(H, "." PATH_SEP_STRING "<path>" HYMN_DLIB_EXTENSION);
hymn_reference_string(search_relative_dynamic);
HymnObjectString *search_libs_dynamic = hymn_new_intern_string(H, "." PATH_SEP_STRING "libs" PATH_SEP_STRING "<path>" HYMN_DLIB_EXTENSION);
hymn_reference_string(search_libs_dynamic);
#endif
// GLOBALS
table_init(&H->globals);
HymnObjectString *globals = hymn_new_intern_string(H, "GLOBALS");
hymn_reference_string(globals);
HymnValue globals_value = hymn_new_table_value(&H->globals);
table_put(&H->globals, globals, globals_value);
hymn_reference_string(globals);
hymn_reference(globals_value);
hymn_reference(globals_value);
// PATHS
HymnObjectString *paths = hymn_new_intern_string(H, "PATHS");
hymn_reference_string(paths);
#ifndef HYMN_NO_DYNAMIC_LIBS
H->paths = hymn_new_array(6);
H->paths->items[0] = hymn_new_string_value(search_this);
H->paths->items[1] = hymn_new_string_value(search_this_dynamic);
H->paths->items[2] = hymn_new_string_value(search_relative);
H->paths->items[3] = hymn_new_string_value(search_relative_dynamic);
H->paths->items[4] = hymn_new_string_value(search_libs);
H->paths->items[5] = hymn_new_string_value(search_libs_dynamic);
#else
H->paths = hymn_new_array(3);
H->paths->items[0] = hymn_new_string_value(search_this);
H->paths->items[1] = hymn_new_string_value(search_relative);
H->paths->items[2] = hymn_new_string_value(search_libs);
#endif
HymnValue paths_value = hymn_new_array_value(H->paths);
table_put(&H->globals, paths, paths_value);
hymn_reference_string(paths);
hymn_reference(paths_value);
hymn_reference(paths_value);
// IMPORTS
H->imports = hymn_new_table();
HymnObjectString *imports = hymn_new_intern_string(H, "IMPORTS");
hymn_reference_string(imports);
HymnValue imports_value = hymn_new_table_value(H->imports);
table_put(&H->globals, imports, imports_value);
hymn_reference_string(imports);
hymn_reference(imports_value);
hymn_reference(imports_value);
H->print = print_stdout;
H->print_error = print_stderr;
return H;
}
void hymn_delete(Hymn *H) {
{
HymnTable *globals_table = &H->globals;
HymnObjectString *globals = hymn_new_intern_string(H, "GLOBALS");
table_remove(globals_table, globals);
hymn_dereference_string(H, globals);
table_release(H, globals_table);
assert(globals_table->size == 0);
}
hymn_array_delete(H, H->paths);
table_delete(H, H->imports);
HymnSet *strings = &H->strings;
{
unsigned int bins = strings->bins;
for (unsigned int i = 0; i < bins; i++) {
HymnSetItem *item = strings->items[i];
while (item != NULL) {
HymnSetItem *next = item->next;
hymn_dereference_string(H, item->string);
item = next;
}
}
}
assert(strings->size == 0);
free(strings->items);
hymn_string_delete(H->error);
#ifndef HYMN_NO_DYNAMIC_LIBS
HymnLibList *lib = H->libraries;
while (lib != NULL) {
hymn_close_dlib(lib->lib);
HymnLibList *next = lib->next;
free(lib);
lib = next;
}
#endif
free(H);
}
HymnValue hymn_get(Hymn *H, const char *name) {
return hymn_table_get(&H->globals, name);
}
void hymn_add(Hymn *H, const char *name, HymnValue value) {
HymnObjectString *string = hymn_new_intern_string(H, name);
HymnValue previous = table_put(&H->globals, string, value);
if (hymn_is_undefined(previous)) {
hymn_reference_string(string);
} else {
hymn_dereference(H, previous);
}
hymn_reference(value);
}
void hymn_add_string(Hymn *H, const char *name, const char *string) {
HymnObjectString *object = hymn_new_intern_string(H, string);
hymn_add(H, name, hymn_new_string_value(object));
}
void hymn_add_table(Hymn *H, const char *name, HymnTable *table) {
hymn_add(H, name, hymn_new_table_value(table));
}
void hymn_add_pointer(Hymn *H, const char *name, void *pointer) {
hymn_add(H, name, hymn_new_pointer(pointer));
}
void hymn_add_string_to_table(Hymn *H, HymnTable *table, const char *name, const char *string) {
HymnObjectString *object = hymn_new_intern_string(H, string);
hymn_set_property_const(H, table, name, hymn_new_string_value(object));
}
void hymn_add_function_to_table(Hymn *H, HymnTable *table, const char *name, HymnNativeCall func) {
HymnObjectString *string = hymn_new_intern_string(H, name);
HymnNativeFunction *native = new_native_function(string, func);
HymnValue value = hymn_new_native(native);
hymn_set_property(H, table, string, value);
}
void hymn_add_function(Hymn *H, const char *name, HymnNativeCall func) {
hymn_add_function_to_table(H, &H->globals, name, func);
}
char *hymn_call(Hymn *H, const char *name, int arguments) {
HymnValue function = hymn_table_get(&H->globals, name);
if (hymn_is_undefined(function)) {
return NULL;
}
hymn_reference(function);
push(H, function);
call_value(H, function, arguments);
char *error = interpret(H);
if (error != NULL) return error;
assert(H->stack_top == H->stack);
reset_stack(H);
return NULL;
}
char *hymn_debug(Hymn *H, const char *script, const char *source) {
HymnString *code = NULL;
if (source == NULL) {
code = hymn_read_file(script);
if (code == NULL) {
HymnString *format = hymn_string_format("file not found: %s\n", script);
char *error = string_to_chars(format);
hymn_string_delete(format);
return error;
}
} else {
code = hymn_new_string(source);
}
CompileResult result = compile(H, script, code, TYPE_SCRIPT);
char *error = result.error;
if (error != NULL) {
hymn_string_delete(code);
return error;
}
HymnFunction *main = result.func;
HymnString *debug = disassemble_byte_code(&main->code);
printf("\n-- %s --\n%s\n", script != NULL ? script : "script", debug);
hymn_string_delete(debug);
HymnValuePool *constants = &main->code.constants;
int count = constants->count;
HymnValue *values = constants->values;
for (int i = 0; i < count; i++) {
HymnValue value = values[i];
if (hymn_is_func(value)) {
HymnFunction *func = hymn_as_func(value);
debug = disassemble_byte_code(&func->code);
printf("\n-- %s --\n%s\n", func->name != NULL ? func->name : "script", debug);
hymn_string_delete(debug);
}
}
function_delete(main);
hymn_string_delete(code);
assert(H->stack_top == H->stack);
reset_stack(H);
return NULL;
}
static char *exec(Hymn *H, const char *script, const char *source, enum FunctionType type) {
CompileResult result = compile(H, script, source, type);
char *error = result.error;
if (error != NULL) return error;
HymnFunction *func = result.func;
HymnValue function = hymn_new_func_value(func);
func->count = 1;
push(H, function);
call(H, func, 0);
error = interpret(H);
if (error != NULL) return error;
assert(H->stack_top == H->stack);
reset_stack(H);
return NULL;
}
char *hymn_run(Hymn *H, const char *script, const char *source) {
return exec(H, script, source, TYPE_SCRIPT);
}
char *hymn_do(Hymn *H, const char *source) {
return exec(H, NULL, source, TYPE_SCRIPT);
}
char *hymn_direct(Hymn *H, const char *source) {
return exec(H, NULL, source, TYPE_DIRECT);
}
char *hymn_script(Hymn *H, const char *script) {
HymnString *source = hymn_read_file(script);
if (source == NULL) {
HymnString *format = hymn_string_format("file not found: %s\n", script);
char *error = string_to_chars(format);
hymn_string_delete(format);
return error;
}
char *error = exec(H, script, source, TYPE_SCRIPT);
hymn_string_delete(source);
return error;
}
#ifndef HYMN_NO_REPL
#include <ctype.h>
#define INPUT_LIMIT 256
typedef struct History History;
struct History {
HymnString *input;
History *previous;
History *next;
};
#ifndef _MSC_VER
#include <termios.h>
#define CURSOR_RESET "\033[2K\r"
#define cursor_backspace() printf("\b \b")
#define cursor_forward() printf("\033[1C")
#define cursor_backward() printf("\033[1D")
#define cursor_clear() printf("\033[s\033[K")
#define cursor_erase() printf("\033[1D\033[s\033[K")
#define cursor_unsave() printf("\033[u")
#define cursor_reset() printf(CURSOR_RESET)
enum Keyboard {
ARROW_UP = 1000,
ARROW_DOWN,
ARROW_LEFT,
ARROW_RIGHT,
PAGE_UP,
PAGE_DOWN,
HOME_KEY,
END_KEY,
DELETE_KEY
};
static char letters[] =
"0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static struct termios save_termios;
static int read_key(void) {
do {
char c;
ssize_t e = read(STDIN_FILENO, &c, 1);
if (e == 1) {
if (c == '\x1b') {
char sequence[3];
if (read(STDIN_FILENO, &sequence[0], 1) != 1) {
return '\x1b';
}
if (read(STDIN_FILENO, &sequence[1], 1) != 1) {
return '\x1b';
}
if (sequence[0] == '[') {
if (sequence[1] >= '0' && sequence[1] <= '9') {
if (read(STDIN_FILENO, &sequence[2], 1) != 1) {
return '\x1b';
}
if (sequence[2] == '~') {
switch (sequence[1]) {
case '1': return HOME_KEY;
case '3': return DELETE_KEY;
case '4': return END_KEY;
case '5': return PAGE_UP;
case '6': return PAGE_DOWN;
case '7': return HOME_KEY;
case '8': return END_KEY;
default: break;
}
}
} else {
switch (sequence[1]) {
case 'A': return ARROW_UP;
case 'B': return ARROW_DOWN;
case 'D': return ARROW_LEFT;
case 'C': return ARROW_RIGHT;
case 'H': return HOME_KEY;
case 'F': return END_KEY;
default: break;
}
}
} else if (sequence[0] == 'O') {
switch (sequence[1]) {
case 'H': return HOME_KEY;
case 'F': return END_KEY;
default: break;
}
}
return '\x1b';
} else {
return c;
}
} else if (e == -1 && errno != EAGAIN) {
printf("\nuser interrupt");
return EOF;
}
} while (true);
}
static void reset_terminal(void) {
if (tcsetattr(STDIN_FILENO, TCSANOW, &save_termios) == -1) {
perror("tcsetattr");
}
}
#else
static void remove_newline(char *line) {
size_t i = 0;
while (line[i] != '\0') {
i++;
}
while (i > 0) {
i--;
if (line[i] != '\n') {
return;
}
line[i] = '\0';
}
}
#endif
static void call_function(Hymn *H, HymnFunction *func) {
HymnValue function = hymn_new_func_value(func);
hymn_reference(function);
push(H, function);
call(H, func, 0);
char *error = interpret(H);
if (error != NULL) {
fprintf(stderr, "%s\n", error);
fflush(stderr);
free(error);
}
assert(H->stack_top == H->stack);
reset_stack(H);
}
void hymn_repl(Hymn *H) {
printf("welcome to hymn v" HYMN_VERSION "\ntype .help for more information\n");
#ifdef _MSC_VER
char temp_dir[MAX_PATH];
GetTempPath(MAX_PATH, temp_dir);
#else
if (tcgetattr(STDIN_FILENO, &save_termios) == -1) {
perror("tcgetattr");
return;
}
struct termios new_term = save_termios;
new_term.c_lflag &= ~(0U | ECHO | ICANON);
new_term.c_cc[VMIN] = 1;
new_term.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &new_term) == -1) {
perror("tcsetattr");
return;
}
atexit(reset_terminal);
int index = 0;
int count = 0;
History *lines = NULL;
History *cursor = NULL;
#endif
char line[INPUT_LIMIT];
HymnString *input = hymn_new_string_with_capacity(INPUT_LIMIT);
History *history = NULL;
bool open_editor = false;
while (true) {
printf(input[0] == '\0' ? "> " : "... ");
fflush(stdout);
#ifdef _MSC_VER
if (fgets(line, sizeof(line), stdin) == NULL) {
break;
}
remove_newline(line);
#else
#define CONTROL_KEY(c) ((c)&0x1f)
index = 0;
count = 0;
while (true) {
int c = read_key();
switch (c) {
case CONTROL_KEY('d'):
printf("\n");
goto quit;
case CONTROL_KEY('e'):
open_editor = true;
goto scan;
case PAGE_UP:
case ARROW_UP:
if (lines != NULL) {
if (cursor == NULL) {
cursor = lines;
} else if (cursor->previous == NULL) {
continue;
} else {
cursor = cursor->previous;
}
size_t len = hymn_string_len(cursor->input);
memcpy(line, cursor->input, len + 1);
index = (int)len;
count = index;
printf(CURSOR_RESET "%s %s", input[0] == '\0' ? ">" : "...", line);
fflush(stdout);
}
continue;
case PAGE_DOWN:
case ARROW_DOWN:
if (cursor != NULL) {
cursor = cursor->next;
if (cursor != NULL) {
size_t len = hymn_string_len(cursor->input);
memcpy(line, cursor->input, len + 1);
index = (int)len;
count = index;
} else {
line[0] = '\0';
index = 0;
count = 0;
}
printf(CURSOR_RESET "%s %s", input[0] == '\0' ? ">" : "...", line);
fflush(stdout);
}
continue;
case HOME_KEY:
case ARROW_LEFT:
if (index > 0) {
index--;
cursor_backward();
fflush(stdout);
}
continue;
case END_KEY:
case ARROW_RIGHT:
if (index < count) {
index++;
cursor_forward();
fflush(stdout);
}
continue;
case DELETE_KEY:
if (index < count) {
count--;
for (int i = index; i < count; i++) {
line[i] = line[i + 1];
}
cursor_clear();
line[count] = '\0';
printf("%s", &line[index]);
cursor_unsave();
fflush(stdout);
}
continue;
case EOF:
hymn_string_zero(input);
count = 0;
goto scan;
case '\n':
case '\r':
goto scan;
default:
if (iscntrl(c)) {
if (c == 8 || c == 127) {
// delete or backspace
if (index > 0) {
index--;
for (int i = index; i < count - 1; i++) {
line[i] = line[i + 1];
}
count--;
if (index == count) {
cursor_backspace();
} else {
cursor_erase();
line[count] = '\0';
printf("%s", &line[index]);
cursor_unsave();
}
fflush(stdout);
}
} else {
printf("<%d>", c);
fflush(stdout);
}
} else if (c != '\0') {
if (count < INPUT_LIMIT) {
count++;
for (int i = count - 1; i > index; i--) {
line[i] = line[i - 1];
}
line[index] = (char)c;
index++;
if (index == count) {
printf("%c", c);
} else {
cursor_clear();
line[count] = '\0';
printf("%s", &line[index - 1]);
cursor_unsave();
cursor_forward();
}
fflush(stdout);
}
}
}
}
scan:
line[count] = '\0';
printf("\n");
cursor = NULL;
#endif
if (line[0] == '.') {
if (hymn_string_equal(line, ".exit") || hymn_string_equal(line, ".quit")) {
goto quit;
} else if (hymn_string_equal(line, ".edit")) {
line[0] = '\0';
open_editor = true;
goto editing;
} else if (hymn_string_equal(line, ".help")) {
printf(".exit Exit interactive mode\n"
".quit Alias for .exit\n"
".edit Edit input using $EDITOR\n"
".save Save history to [FILE]\n"
".load Read history from [FILE]\n"
".help Print this help message\n"
"press ^E to use $EDITOR\n"
"press ^C to cancel expression\n"
"press ^D to exit interactive mode\n");
} else if (string_starts_with(line, ".save ")) {
char path[PATH_MAX];
#ifdef _MSC_VER
strcpy_s(path, PATH_MAX, &line[6]);
#else
strcpy(path, &line[6]);
#endif
if (path[0] == '\0') {
printf("bad file path\n");
} else if (history == NULL) {
printf("no history to save\n");
} else if (hymn_file_exists(path)) {
printf("history can't overwrite an existing file: %s\n", path);
} else {
FILE *open = hymn_open_file(path, "w");
if (open == NULL) {
printf("failed to write history: %s\n", path);
} else {
History *head = history;
while (head->previous != NULL) {
head = head->previous;
}
while (head != NULL) {
fprintf(open, "%s\n", head->input);
head = head->next;
}
fclose(open);
printf("history saved to: %s\n", path);
}
}
} else if (string_starts_with(line, ".load ")) {
char path[PATH_MAX];
#ifdef _MSC_VER
strcpy_s(path, PATH_MAX, &line[6]);
#else
strcpy(path, &line[6]);
#endif
if (path[0] == '\0') {
printf("bad file path\n");
} else if (hymn_file_exists(path)) {
HymnString *source = hymn_read_file(path);
if (source == NULL) {
printf("failed to read history: %s\n", path);
} else {
char *error = hymn_do(H, source);
hymn_string_delete(source);
if (error != NULL) {
fprintf(stderr, "%s\n", error);
fflush(stderr);
free(error);
}
}
} else {
printf("history file not found: %s\n", path);
}
} else if (hymn_string_equal(line, ".save")) {
printf("specify a path\n");
} else if (hymn_string_equal(line, ".load")) {
printf("specify a path\n");
} else {
printf("invalid interactive command\n");
}
continue;
}
editing:
if (line[0] != '\0') {
#ifndef _MSC_VER
History *save = hymn_calloc(1, sizeof(History));
save->input = hymn_new_string(line);
if (lines == NULL) {
lines = save;
} else {
lines->next = save;
save->previous = lines;
lines = save;
}
#endif
if (input[0] != '\0') input = hymn_string_append_char(input, '\n');
input = hymn_string_append(input, line);
hymn_string_trim(input);
}
if (open_editor) {
open_editor = false;
FILE *temp = NULL;
#ifdef _MSC_VER
char *editor = NULL;
size_t size = 0;
errno_t error = _dupenv_s(&editor, &size, "EDITOR");
if (error != 0) {
free(editor);
printf("no EDITOR set\n");
continue;
}
char path[MAX_PATH];
GetTempFileName(temp_dir, "hymn", 0, path);
temp = hymn_open_file(path, "w");
#else
char *editor = getenv("EDITOR");
if (editor == NULL) {
printf("no EDITOR set\n");
continue;
}
char path[] = "/tmp/hymn.XXXXXX";
for (int a = 0; a < 64; a++) {
for (int x = 0; x < 6; x++) {
path[10 + x] = letters[(size_t)((double)rand() / RAND_MAX * (sizeof(letters) - 1))];
}
temp = hymn_open_file(path, "wx");
if (temp != NULL) {
break;
}
}
#endif
if (temp == NULL) {
printf("failed to create temporary file\n");
#ifdef _MSC_VER
free(editor);
#endif
continue;
}
hymn_string_trim(input);
if (hymn_string_len(input) > 0) {
fprintf(temp, "%s", input);
hymn_string_zero(input);
}
fclose(temp);
#ifdef _MSC_VER
HymnString *edit;
if (editor != NULL) {
edit = hymn_string_format("%s %s", editor, path);
free(editor);
} else {
edit = hymn_string_format("%s %s", "notepad", path);
}
#else
HymnString *edit = hymn_string_format("%s %s", editor, path);
#endif
int result = system(edit);
if (result == -1) {
printf("failed edit: %d\n", result);
}
hymn_string_delete(edit);
HymnString *content = hymn_read_file(path);
#ifdef _MSC_VER
_unlink(path);
#else
unlink(path);
#endif
if (content == NULL) {
printf("failed to read temporary file\n");
continue;
}
input = hymn_string_append(input, content);
hymn_string_delete(content);
hymn_string_trim(input);
if (input[0] == '\0') {
continue;
}
printf("%s\n", input);
}
if (input[0] == '\0') continue;
CompileResult result = compile(H, NULL, input, TYPE_REPL);
char *error = result.error;
if (error != NULL) {
if (!hymn_string_equal(error, "<eof>")) {
hymn_string_zero(input);
fprintf(stderr, "%s\n", error);
fflush(stderr);
free(error);
}
continue;
}
HymnFunction *func = result.func;
History *save = hymn_calloc(1, sizeof(History));
save->input = hymn_new_string(input);
if (history == NULL) {
history = save;
} else {
history->next = save;
save->previous = history;
history = save;
}
hymn_string_zero(input);
call_function(H, func);
}
quit:
hymn_string_delete(input);
#ifndef _MSC_VER
while (lines != NULL) {
hymn_string_delete(lines->input);
History *previous = lines->previous;
free(lines);
lines = previous;
}
#endif
while (history != NULL) {
hymn_string_delete(history->input);
History *previous = history->previous;
free(history);
history = previous;
}
}
#endif
#ifndef HYMN_NO_DYNAMIC_LIBS
void hymn_add_dlib(Hymn *H, void *library) {
HymnLibList *tail = H->libraries;
HymnLibList *head = hymn_calloc(1, sizeof(HymnLibList));
head->lib = library;
head->next = tail;
H->libraries = head;
}
#ifdef _MSC_VER
void hymn_close_dlib(void *library) {
FreeLibrary(library);
}
HymnString *hymn_use_dlib(Hymn *H, const char *path, const char *func) {
HINSTANCE lib = LoadLibrary(path);
if (lib != NULL) {
FARPROC proc = GetProcAddress(lib, func);
if (proc != NULL) {
proc(H);
hymn_add_dlib(H, lib);
return NULL;
}
}
HymnString *message = NULL;
unsigned long error = GetLastError();
char buffer[128];
if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, 0, buffer, sizeof(buffer), 0)) {
message = hymn_new_string(buffer);
} else {
message = hymn_string_format("windows error: %d\n", error);
}
if (lib != NULL) {
hymn_close_dlib(lib);
}
return message;
}
#else
#include <dlfcn.h>
void hymn_close_dlib(void *library) {
dlclose(library);
}
HymnString *hymn_use_dlib(Hymn *H, const char *path, const char *func) {
void *lib = dlopen(path, RTLD_NOW);
if (lib != NULL) {
void *(*proc)(Hymn *);
*(void **)(&proc) = dlsym(lib, func);
if (proc != NULL) {
proc(H);
hymn_add_dlib(H, lib);
return NULL;
}
}
HymnString *message = hymn_new_string(dlerror());
if (lib != NULL) {
hymn_close_dlib(lib);
}
return message;
}
#endif
#endif
typedef struct Format Format;
struct Format {
HymnString *source;
char *dest;
char *nest;
bool *compact;
size_t size;
size_t s;
size_t capacity;
size_t n;
size_t deep;
size_t compact_capacity;
size_t compact_len;
size_t nest_capacity;
size_t nest_len;
};
static bool important_word(Format *F, size_t start, const char *word) {
size_t a = 0;
do {
if (F->dest[start + a] != word[a]) {
return false;
}
a++;
} while (word[a] != '\0');
return true;
}
static bool is_important(Format *F, size_t len) {
if (len < 2) {
return false;
}
size_t start = len - 2;
while (true) {
if (!is_ident(F->dest[start])) {
start++;
break;
} else if (start == 0) {
break;
}
start--;
}
const size_t size = len - start;
switch (size) {
case 2:
return important_word(F, start, "in") ||
important_word(F, start, "if");
case 3:
return important_word(F, start, "for") ||
important_word(F, start, "not");
case 4:
return important_word(F, start, "else") ||
important_word(F, start, "elif") ||
important_word(F, start, "echo");
case 5:
return important_word(F, start, "while");
case 6:
return important_word(F, start, "return");
default:
return false;
}
}
static void append(Format *F, char c) {
const size_t len = ++F->n;
if (len >= F->capacity) {
F->capacity += 256;
F->dest = hymn_realloc(F->dest, (F->capacity + 1) * sizeof(char));
}
F->dest[len - 1] = c;
}
static void indent(Format *F) {
for (size_t a = 0; a < F->deep; a++) {
append(F, ' ');
append(F, ' ');
}
}
static void space(Format *F, char t) {
switch (t) {
case ' ':
case '\n':
case '\t':
case '\r':
return;
default:
break;
}
const size_t len = F->n;
if (len == 0) {
return;
}
const char p = F->dest[len - 1];
if (p == '\n') {
indent(F);
return;
} else if (p == ':') {
if (F->nest_len == 0) {
append(F, ' ');
} else {
const char nest = F->nest[F->nest_len - 1];
if (nest != '[') {
append(F, ' ');
}
}
return;
} else if (p == '~') {
return;
}
if (is_digit(t) || is_ident(t)) {
switch (p) {
case ' ':
case '.':
case '(':
case '[':
return;
case '>':
if (len >= 2 && F->dest[len - 2] == '-') {
return;
}
break;
case '-':
if (len >= 3) {
const char b = F->dest[len - 3];
if (b == ',' || b == '{') {
return;
} else if (is_ident(b) && is_important(F, len - 2)) {
return;
}
}
if (len >= 2) {
const char b = F->dest[len - 2];
if (b != ')' && b != ' ') {
return;
}
}
break;
default:
break;
}
append(F, ' ');
return;
}
switch (t) {
case '{':
if (p == '(') {
return;
}
break;
case '(':
if (is_digit(p)) {
return;
} else if (is_ident(p)) {
if (is_important(F, len)) {
break;
}
return;
}
switch (p) {
case ' ':
case '(':
case ')':
case '[':
case '}':
case ']':
return;
default:
break;
}
break;
case '[':
if (is_ident(p)) {
if (is_important(F, len)) {
break;
}
return;
}
switch (p) {
case '+':
case '=':
case ',':
case '{':
break;
default:
return;
}
break;
case '<':
if (p == '<') {
return;
}
break;
case '>':
if (p == '-' || p == '>') {
return;
}
break;
case '=':
switch (p) {
case '-':
case '+':
case '*':
case '/':
case '<':
case '>':
case '~':
case '^':
case '%':
case '&':
case '|':
case '!':
case '=':
return;
default:
break;
}
break;
case '-':
if (F->s < F->size && F->source[F->s] == '>') {
return;
} else if (p == ',' || p == '{' || p == ')') {
break;
} else if (is_ident(p) || is_digit(p)) {
break;
}
return;
case '"':
case '\'':
switch (p) {
case '(':
case '[':
return;
default:
break;
}
break;
case ':':
case ',':
case '.':
case ')':
case ']':
return;
default:
break;
}
append(F, ' ');
}
static void compact(Format *F, bool v) {
const size_t len = ++F->compact_len;
if (len >= F->compact_capacity) {
F->compact_capacity += 16;
if (F->compact == NULL) {
F->compact = hymn_malloc(F->compact_capacity * sizeof(bool));
} else {
F->compact = hymn_realloc(F->compact, F->compact_capacity * sizeof(bool));
}
}
F->compact[len - 1] = v;
}
static bool is_compact(Format *F) {
if (F->compact_len > 0) {
return F->compact[--F->compact_len];
}
return false;
}
static void nest(Format *F, char c) {
const size_t len = ++F->nest_len;
if (len >= F->nest_capacity) {
F->nest_capacity += 16;
if (F->nest == NULL) {
F->nest = hymn_malloc(F->nest_capacity * sizeof(char));
} else {
F->nest = hymn_realloc(F->nest, F->nest_capacity * sizeof(char));
}
}
F->nest[len - 1] = c;
}
static void nest_pop(Format *F) {
if (F->nest_len > 0) {
F->nest_len--;
}
}
static void skip(Format *F) {
if (F->s >= F->size) {
return;
}
char c = F->source[F->s];
while (c == ' ' || c == '\t' || c == '\r') {
F->s++;
if (F->s >= F->size) {
return;
}
c = F->source[F->s];
}
}
static void newline(Format *F) {
append(F, '\n');
skip(F);
if (F->s >= F->size) {
return;
}
char c = F->source[F->s];
if (c == ')' || c == ']' || c == '}') {
F->s++;
if (F->deep > 0) {
F->deep--;
}
indent(F);
append(F, c);
}
}
static void possible_newline(Format *F) {
size_t p = F->n;
while (true) {
if (p == 0) {
return;
}
p--;
char o = F->dest[p];
if (o == '\n') {
return;
} else if (o == ' ' || o == '\t' || o == '\r') {
continue;
} else {
newline(F);
return;
}
}
}
static void stringly(Format *F, char c) {
append(F, c);
while (F->s < F->size) {
const char n = F->source[F->s];
append(F, n);
F->s++;
if (n == '\\') {
append(F, F->source[F->s++]);
} else if (n == c) {
break;
}
}
size_t a = F->s;
bool multiple = false;
loop:
while (a < F->size) {
char n = F->source[a];
if (n == '\n') {
for (size_t d = a + 1; d < F->size; d++) {
const char m = F->source[d];
if (m == '"' || m == '\'') {
if (!multiple) {
multiple = true;
F->deep++;
}
append(F, '\n');
indent(F);
append(F, m);
F->s = d + 1;
while (F->s < F->size) {
const char i = F->source[F->s];
append(F, i);
F->s++;
if (i == '\\') {
append(F, F->source[F->s++]);
} else if (i == m) {
a = F->s;
goto loop;
}
}
goto end;
}
if (m != ' ' && m != '\t' && m != '\r' && m != '\n') {
goto end;
}
}
goto end;
}
if (n == '"' || n == '\'') {
space(F, n);
append(F, n);
F->s = a + 1;
while (F->s < F->size) {
const char i = F->source[F->s];
append(F, i);
F->s++;
if (i == '\\') {
append(F, F->source[F->s++]);
} else if (i == n) {
a = F->s;
goto loop;
}
}
}
if (n != ' ' && n != '\t' && n != '\r') {
break;
}
a++;
}
end:
if (multiple) {
if (F->deep > 0) F->deep--;
possible_newline(F);
}
}
char *hymn_format(HymnString *source) {
size_t size = hymn_string_len(source);
Format F = {0};
F.source = source;
F.size = size;
F.capacity = size;
F.dest = hymn_malloc((size + 1) * sizeof(char));
skip(&F);
while (F.s < size) {
char c = source[F.s++];
space(&F, c);
if (is_digit(c)) {
append(&F, c);
if (F.s < F.size) {
if (c == '0') {
char n = source[F.s];
if (n == 'x') {
append(&F, c);
F.s++;
while (F.s < F.size) {
char m = source[F.s];
if (!(m >= '0' && m <= '9') && !(m >= 'a' && m <= 'f')) {
break;
}
append(&F, m);
F.s++;
}
continue;
} else if (n == 'b') {
append(&F, c);
F.s++;
while (F.s < F.size) {
char m = source[F.s];
if (m != '0' && m != '1') {
break;
}
append(&F, m);
F.s++;
}
continue;
}
}
while (F.s < F.size) {
char n = source[F.s];
if (!is_digit(n)) {
if (n == '.') {
append(&F, n);
F.s++;
while (F.s < F.size) {
char d = source[F.s];
if (!is_digit(d)) {
if (d == 'e' || d == 'E') {
append(&F, d);
F.s++;
if (F.s < F.size) {
const char m = F.source[F.s];
if (m == '+' || m == '-') {
append(&F, m);
F.s++;
}
while (F.s < F.size) {
const char e = F.source[F.s];
if (!is_digit(e)) {
break;
}
append(&F, e);
F.s++;
}
}
break;
}
break;
}
append(&F, d);
F.s++;
}
break;
} else if (n == 'e' || n == 'E') {
append(&F, n);
F.s++;
if (F.s < F.size) {
const char m = F.source[F.s];
if (m == '+' || m == '-') {
append(&F, m);
F.s++;
}
while (F.s < F.size) {
const char e = F.source[F.s];
if (!is_digit(e)) {
break;
}
append(&F, e);
F.s++;
}
}
break;
} else {
break;
}
}
append(&F, n);
F.s++;
}
}
continue;
} else if (is_ident(c)) {
append(&F, c);
while (F.s < F.size) {
const char n = F.source[F.s];
if (is_ident(n)) {
append(&F, n);
F.s++;
} else if (n == '-' && F.s + 1 < F.size) {
const char m = F.source[F.s + 1];
if (is_ident(m)) {
append(&F, '-');
append(&F, m);
F.s += 2;
} else {
break;
}
} else {
break;
}
}
continue;
}
switch (c) {
case '(': {
append(&F, c);
nest(&F, c);
for (size_t a = F.s; a < F.size; a++) {
const char n = F.source[a];
if (n == '\n') {
F.deep++;
newline(&F);
compact(&F, false);
break;
} else if (n == ')') {
compact(&F, true);
break;
}
}
break;
}
case '[': {
append(&F, c);
nest(&F, c);
for (size_t a = F.s; a < F.size; a++) {
const char n = F.source[a];
if (n == '\n') {
F.deep++;
newline(&F);
compact(&F, false);
break;
} else if (n == ']') {
compact(&F, true);
break;
}
}
break;
}
case '{': {
append(&F, c);
nest(&F, c);
bool any = false;
for (size_t a = F.s; a < F.size; a++) {
const char n = F.source[a];
if (n == '\n') {
bool ok = true;
if (!any) {
for (size_t d = a + 1; d < F.size; d++) {
const char m = F.source[d];
if (m == '}') {
ok = false;
append(&F, '\n');
indent(&F);
append(&F, '}');
F.s = d + 1;
break;
} else if (m != ' ' && m != '\t' && m != '\r' && m != '\n') {
break;
}
}
}
if (ok) {
F.deep++;
newline(&F);
compact(&F, false);
}
break;
} else if (n == '}') {
if (any) {
compact(&F, true);
} else {
append(&F, '}');
F.s = a + 1;
}
break;
} else if (n != ' ' && n != '\t' && n != '\r') {
any = true;
}
}
break;
}
case ')':
case ']':
case '}': {
nest_pop(&F);
if (!is_compact(&F)) {
if (F.deep > 0) {
F.deep--;
}
possible_newline(&F);
}
append(&F, c);
break;
}
case '\'':
case '"':
stringly(&F, c);
break;
case '#': {
append(&F, c);
while (F.s < F.size) {
const char n = F.source[F.s];
append(&F, n);
if (n == '\n') {
break;
}
F.s++;
}
break;
}
case '\n': {
bool twice = false;
while (F.s < F.size) {
const char n = F.source[F.s];
if (n == '\n') {
F.s++;
twice = true;
} else if (n == ' ' || n == '\t' || n == '\r') {
F.s++;
} else {
break;
}
}
possible_newline(&F);
if (twice) {
newline(&F);
}
break;
}
case ' ':
case '\t':
case '\r':
break;
default:
append(&F, c);
break;
}
}
F.dest[F.capacity] = '\0';
F.dest[F.n] = '\0';
free(F.compact);
free(F.nest);
return F.dest;
}