the interpreter implementation is now hidden behing the src/libnoja/noja.h facade. Also I wrote a disassembler that lets you build noja executables by specifying the bytecode directly in a text format
This commit is contained in:
@@ -27,6 +27,7 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.c
|
|||||||
@ $(CC) $(CFLAGS) -c $^ -o $@
|
@ $(CC) $(CFLAGS) -c $^ -o $@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Clean all artifacts and rebuild the whole thing
|
# Clean all artifacts and rebuild the whole thing
|
||||||
all: clean $(OBJS) build
|
all: clean $(OBJS) build
|
||||||
|
|
||||||
|
|||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
FILES="tests/assembler_test.c \
|
||||||
|
src/assembler/assembler.c \
|
||||||
|
src/common/executable.c \
|
||||||
|
src/utils/error.c \
|
||||||
|
src/utils/source.c \
|
||||||
|
src/utils/bpalloc.c \
|
||||||
|
src/utils/promise.c \
|
||||||
|
src/utils/labellist.c \
|
||||||
|
src/utils/bucketlist.c"
|
||||||
|
|
||||||
|
gcc $FILES -o build/assembler_test_cov -Wall -Wextra -g --coverage
|
||||||
|
gcc $FILES -o build/assembler_test -Wall -Wextra -g
|
||||||
|
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "../utils/error.h"
|
||||||
|
#include "../utils/source.h"
|
||||||
|
#include "../utils/labellist.h"
|
||||||
|
#include "../common/executable.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *str;
|
||||||
|
size_t len;
|
||||||
|
size_t cur;
|
||||||
|
} Context;
|
||||||
|
|
||||||
|
static void skipIdentifier(Context *ctx)
|
||||||
|
{
|
||||||
|
while(ctx->cur < ctx->len && (isalpha(ctx->str[ctx->cur]) || isdigit(ctx->str[ctx->cur]) || ctx->str[ctx->cur] == '_'))
|
||||||
|
ctx->cur += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skipSpaces(Context *ctx)
|
||||||
|
{
|
||||||
|
while(ctx->cur < ctx->len && isspace(ctx->str[ctx->cur]))
|
||||||
|
ctx->cur += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t offset;
|
||||||
|
size_t length;
|
||||||
|
} Slice;
|
||||||
|
|
||||||
|
static bool parseLabelAndOpcode(Context *ctx, bool *no_label,
|
||||||
|
Slice *label, Slice *opcode,
|
||||||
|
Error *error)
|
||||||
|
{
|
||||||
|
assert(ctx != NULL && no_label != NULL
|
||||||
|
&& label != NULL && opcode != NULL);
|
||||||
|
|
||||||
|
// NOTE: This function must start at
|
||||||
|
// the first byte of the label or
|
||||||
|
// opcode. All whitespace must be
|
||||||
|
// consumed by the caller.
|
||||||
|
|
||||||
|
// Now we expect either a label and an
|
||||||
|
// opcode, or just an opcode
|
||||||
|
//
|
||||||
|
// <label>: <opcode> ..operands..
|
||||||
|
// <opcode> ..operands..
|
||||||
|
//
|
||||||
|
// Labels and opcodes can bot contain
|
||||||
|
// digits, letters and underscores.
|
||||||
|
|
||||||
|
Slice label_or_opcode;
|
||||||
|
{
|
||||||
|
char c = ctx->str[ctx->cur];
|
||||||
|
if(!isalpha(c) && c != '_') {
|
||||||
|
// ERROR: Missing opcode
|
||||||
|
Error_Report(error, 0, "Missing opcode");
|
||||||
|
}
|
||||||
|
|
||||||
|
label_or_opcode.offset = ctx->cur;
|
||||||
|
skipIdentifier(ctx);
|
||||||
|
label_or_opcode.length = ctx->cur - label_or_opcode.offset;
|
||||||
|
|
||||||
|
assert(label_or_opcode.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the character after the label or opcode
|
||||||
|
// (ignoring whitespace), and if it's a ':',
|
||||||
|
// then it was a label.
|
||||||
|
skipSpaces(ctx);
|
||||||
|
if(ctx->cur < ctx->len && ctx->str[ctx->cur] == ':') {
|
||||||
|
|
||||||
|
// Skip the ':' and the whitespace after it.
|
||||||
|
ctx->cur += 1;
|
||||||
|
skipSpaces(ctx);
|
||||||
|
|
||||||
|
if(ctx->cur == ctx->len || (!isalpha(ctx->str[ctx->cur]) && ctx->str[ctx->cur] != '_')) {
|
||||||
|
Error_Report(error, 0, "Missing opcode after label");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the opcode is expected.
|
||||||
|
opcode->offset = ctx->cur;
|
||||||
|
skipIdentifier(ctx);
|
||||||
|
opcode->length = ctx->cur - opcode->offset;
|
||||||
|
assert(opcode->length > 0);
|
||||||
|
|
||||||
|
*no_label = false;
|
||||||
|
*label = label_or_opcode;
|
||||||
|
skipSpaces(ctx);
|
||||||
|
} else {
|
||||||
|
*no_label = true;
|
||||||
|
*opcode = label_or_opcode;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseStringOperand(Context *ctx, Error *error, BPAlloc *alloc, Operand *op)
|
||||||
|
{
|
||||||
|
// Skip the first double quote.
|
||||||
|
assert(ctx->cur < ctx->len && ctx->str[ctx->cur] == '"');
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
size_t literal_offset = ctx->cur;
|
||||||
|
|
||||||
|
// For now do a dumb copy into the buffer
|
||||||
|
// without considering special characters.
|
||||||
|
while(ctx->cur < ctx->len && ctx->str[ctx->cur] != '"')
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
if(ctx->cur == ctx->len) {
|
||||||
|
Error_Report(error, 0, "End of source inside a string literal");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the ending double quotes.
|
||||||
|
assert(ctx->cur < ctx->len && ctx->str[ctx->cur] == '"');
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
size_t literal_length = ctx->cur - literal_offset;
|
||||||
|
|
||||||
|
char *copy = BPAlloc_Malloc(alloc, literal_length+1);
|
||||||
|
if(copy == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(copy, ctx->str + literal_offset, literal_length);
|
||||||
|
copy[literal_length] = '\0';
|
||||||
|
|
||||||
|
op->type = OPTP_STRING;
|
||||||
|
op->as_string = copy;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseIntegerOperand(Context *ctx, Error *error, Operand *op)
|
||||||
|
{
|
||||||
|
// It's ensured by the caller that the cursor is
|
||||||
|
// pointing to a sequence of one or more digits
|
||||||
|
// NOT followed by a dot and a digit after that
|
||||||
|
// (so this is an integer for sure, not a float).
|
||||||
|
assert(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
long long int buffer = 0;
|
||||||
|
do {
|
||||||
|
// Transform each digit into it's integer value.
|
||||||
|
int d = ctx->str[ctx->cur] - '0';
|
||||||
|
assert(d >= 0 && d <= 9);
|
||||||
|
|
||||||
|
// Will this overflow?
|
||||||
|
if(buffer > (LLONG_MAX - d) / 10) {
|
||||||
|
Error_Report(error, 0, "Integer literal is too big to be represented in %d bits", 8*sizeof(buffer));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = buffer * 10 + d;
|
||||||
|
ctx->cur += 1;
|
||||||
|
} while(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
// Not a float!
|
||||||
|
|
||||||
|
op->type = OPTP_INT;
|
||||||
|
op->as_int = buffer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseFloatingOperand(Context *ctx, Error *error, Operand *op)
|
||||||
|
{
|
||||||
|
(void) error; // At the moment this function doesn't report
|
||||||
|
// any error. This may change when overflows
|
||||||
|
// and underflows are detected.
|
||||||
|
|
||||||
|
// It's ensured by the caller that the cursor is
|
||||||
|
// pointing to a sequence of one or more digits
|
||||||
|
// followed by a dot and a digit after that.
|
||||||
|
assert(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
double buffer = 0;
|
||||||
|
do {
|
||||||
|
// Transform each digit into it's integer value.
|
||||||
|
int d = ctx->str[ctx->cur] - '0';
|
||||||
|
assert(d >= 0 && d <= 9);
|
||||||
|
|
||||||
|
// Should overflow be checked?
|
||||||
|
buffer = buffer * 10 + d;
|
||||||
|
|
||||||
|
ctx->cur += 1;
|
||||||
|
} while(ctx->str[ctx->cur] != '.');
|
||||||
|
|
||||||
|
assert(ctx->cur+1 < ctx->len && ctx->str[ctx->cur] == '.' && isdigit(ctx->str[ctx->cur+1]));
|
||||||
|
|
||||||
|
double q = 1;
|
||||||
|
do {
|
||||||
|
// Transform each digit into it's integer value.
|
||||||
|
int d = ctx->str[ctx->cur] - '0';
|
||||||
|
assert(d >= 0 && d <= 9);
|
||||||
|
|
||||||
|
buffer += q * d;
|
||||||
|
|
||||||
|
ctx->cur += 1;
|
||||||
|
} while(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
op->type = OPTP_FLOAT;
|
||||||
|
op->as_float = buffer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseOperands(Context *ctx, BPAlloc *alloc, Error *error,
|
||||||
|
LabelList *list, Operand *opv, int *opc, int opc_max)
|
||||||
|
{
|
||||||
|
// NOTE: The whitespace before the first operand must
|
||||||
|
// be consumed by the caller.
|
||||||
|
|
||||||
|
if(ctx->cur < ctx->len && ctx->str[ctx->cur] != ';')
|
||||||
|
while(1) {
|
||||||
|
|
||||||
|
Operand op;
|
||||||
|
|
||||||
|
char c = ctx->str[ctx->cur];
|
||||||
|
if(c == '"') {
|
||||||
|
|
||||||
|
if(!parseStringOperand(ctx, error, alloc, &op))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if(isdigit(c)) {
|
||||||
|
|
||||||
|
/* Integer or float operand */
|
||||||
|
|
||||||
|
size_t k = ctx->cur;
|
||||||
|
while(k < ctx->len && isdigit(ctx->str[k]))
|
||||||
|
k += 1;
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
if(k+1 >= ctx->len || ctx->str[k] != '.' || !isdigit(ctx->str[k+1]))
|
||||||
|
ok = parseIntegerOperand(ctx, error, &op);
|
||||||
|
else
|
||||||
|
ok = parseFloatingOperand(ctx, error, &op);
|
||||||
|
|
||||||
|
if(!ok)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if(isalpha(c) || c == '_') {
|
||||||
|
|
||||||
|
/* Label */
|
||||||
|
size_t offset = ctx->cur;
|
||||||
|
skipIdentifier(ctx);
|
||||||
|
size_t length = ctx->cur - offset;
|
||||||
|
|
||||||
|
Promise *promise = LabelList_GetLabel(list, ctx->str + offset, length);
|
||||||
|
if(promise == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
op.type = OPTP_PROMISE;
|
||||||
|
op.as_promise = promise;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ERROR: Unexpected character
|
||||||
|
Error_Report(error, 0, "Unexpected character '%c'", c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(*opc == opc_max) {
|
||||||
|
Error_Report(error, 0, "Too many operands");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
opv[*opc] = op;
|
||||||
|
*opc += 1;
|
||||||
|
// Now prepare for the next operand
|
||||||
|
skipSpaces(ctx);
|
||||||
|
|
||||||
|
if(ctx->cur == ctx->len || ctx->str[ctx->cur] == ';')
|
||||||
|
break;
|
||||||
|
|
||||||
|
c = ctx->str[ctx->cur];
|
||||||
|
if(c != ',') {
|
||||||
|
Error_Report(error, 0, "Unexpected character '%c' (',' or ';' were expected)", c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the comma
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
// Skip the spaces before the next operand
|
||||||
|
skipSpaces(ctx);
|
||||||
|
}
|
||||||
|
assert(ctx->cur == ctx->len || ctx->str[ctx->cur] == ';');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Executable *assemble(Source *src, Error *error)
|
||||||
|
{
|
||||||
|
Executable *exe = NULL;
|
||||||
|
|
||||||
|
BPAlloc *alloc = BPAlloc_Init(-1);
|
||||||
|
if(alloc == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelList *list = LabelList_New(alloc);
|
||||||
|
if(list == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExeBuilder *builder = ExeBuilder_New(alloc);
|
||||||
|
if(builder == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
LabelList_Free(list);
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context ctx = {
|
||||||
|
.str = Source_GetBody(src),
|
||||||
|
.len = Source_GetSize(src),
|
||||||
|
.cur = 0,
|
||||||
|
};
|
||||||
|
while(1) {
|
||||||
|
|
||||||
|
skipSpaces(&ctx);
|
||||||
|
|
||||||
|
if(ctx.cur == ctx.len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
bool no_label;
|
||||||
|
Slice label, opcode_name;
|
||||||
|
|
||||||
|
if(!parseLabelAndOpcode(&ctx, &no_label, &label, &opcode_name, error))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
// If a label was defined, add it to the list.
|
||||||
|
if(no_label == false) {
|
||||||
|
long long int value = ExeBuilder_InstrCount(builder);
|
||||||
|
if(!LabelList_SetLabel(list, ctx.str + label.offset, label.length, value)) {
|
||||||
|
Error_Report(error, 1, "Out of memory");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the opcode is valid (at this
|
||||||
|
// point it's just an unchecked identifier)
|
||||||
|
Opcode opcode;
|
||||||
|
const char *name = ctx.str + opcode_name.offset;
|
||||||
|
if(!Executable_GetOpcodeBinaryFromName(name, opcode_name.length, &opcode)) {
|
||||||
|
Error_Report(error, 0, "Opcode %.*s doesn't exist", (int) opcode_name.length, name);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse operands */
|
||||||
|
// (whitespace was already skipped)
|
||||||
|
|
||||||
|
Operand opv[8];
|
||||||
|
int opc = 0;
|
||||||
|
|
||||||
|
if(!parseOperands(&ctx, alloc, error, list, opv, &opc, sizeof(opv)/sizeof(opv[0])))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
// The operand list ended with a ';' or the
|
||||||
|
// end of the file. If the file didn't end,
|
||||||
|
// then the ';' must be consumed.
|
||||||
|
assert(ctx.cur == ctx.len || ctx.str[ctx.cur] == ';');
|
||||||
|
if(ctx.cur < ctx.len) ctx.cur += 1;
|
||||||
|
|
||||||
|
if(!ExeBuilder_Append(builder, error, opcode, opv, opc, opcode_name.offset, opcode_name.length))
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t unresolved_count = LabelList_GetUnresolvedCount(list);
|
||||||
|
if(unresolved_count > 0) {
|
||||||
|
Error_Report(error, 0, "%d unresolved labels", unresolved_count);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
exe = ExeBuilder_Finalize(builder, error);
|
||||||
|
|
||||||
|
if(exe != NULL)
|
||||||
|
(void) Executable_SetSource(exe, src);
|
||||||
|
|
||||||
|
done:
|
||||||
|
LabelList_Free(list);
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef ASSEMBLE_H
|
||||||
|
#define ASSEMBLE_H
|
||||||
|
#include "../utils/error.h"
|
||||||
|
#include "../utils/source.h"
|
||||||
|
#include "../common/executable.h"
|
||||||
|
Executable *assemble(Source *src, Error *error);
|
||||||
|
#endif /* ASSEMBLE_H */
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
** +--------------------------------------------------------------------------+
|
** +--------------------------------------------------------------------------+
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -112,6 +113,31 @@ static const InstrInfo instr_table[] = {
|
|||||||
[OPCODE_JUMP] = {"JUMP", 1, (OperandType[]) {OPTP_INT}},
|
[OPCODE_JUMP] = {"JUMP", 1, (OperandType[]) {OPTP_INT}},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_Bool Executable_GetOpcodeBinaryFromName(const char *name, size_t name_len, Opcode *opcode)
|
||||||
|
{
|
||||||
|
// The input name is assumed not zero-terminated,
|
||||||
|
// so to simplify things we duplicate it to add
|
||||||
|
// an extra null byte.
|
||||||
|
char buff[128]; // No opcode should have a name this big.
|
||||||
|
assert(name_len < sizeof(buff)); // If this is triggered, [buff] should be made bigger.
|
||||||
|
|
||||||
|
memcpy(buff, name, name_len);
|
||||||
|
buff[name_len] = '\0';
|
||||||
|
|
||||||
|
name = buff;
|
||||||
|
|
||||||
|
// Now name is zero terminated.
|
||||||
|
assert(name[name_len] == '\0');
|
||||||
|
|
||||||
|
for(size_t i = 0; i < sizeof(instr_table)/sizeof(instr_table[0]); i += 1) {
|
||||||
|
if(!strcmp(name, instr_table[i].name)) {
|
||||||
|
*opcode = i; // Is this safe?
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const char *Executable_GetOpcodeName(Opcode opcode)
|
const char *Executable_GetOpcodeName(Opcode opcode)
|
||||||
{
|
{
|
||||||
return instr_table[opcode].name;
|
return instr_table[opcode].name;
|
||||||
@@ -275,6 +301,103 @@ _Bool Executable_Fetch(Executable *exe, int index, Opcode *opcode, Operand *ops,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_Bool Executable_Equiv(Executable *exe1, Executable *exe2, FILE *log, const char *log_prefix)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
while(1) {
|
||||||
|
Operand exe1_opv[MAX_OPS];
|
||||||
|
Operand exe2_opv[MAX_OPS];
|
||||||
|
int exe1_opc = MAX_OPS;
|
||||||
|
int exe2_opc = MAX_OPS;
|
||||||
|
Opcode exe1_opcode;
|
||||||
|
Opcode exe2_opcode;
|
||||||
|
_Bool exe1_done = !Executable_Fetch(exe1, idx, &exe1_opcode, exe1_opv, &exe1_opc);
|
||||||
|
_Bool exe2_done = !Executable_Fetch(exe2, idx, &exe2_opcode, exe2_opv, &exe2_opc);
|
||||||
|
if(exe1_done != exe2_done) {
|
||||||
|
if(log != NULL)
|
||||||
|
fprintf(log, "%sExecutables have different sizes\n", log_prefix);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(exe1_done == true)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(exe1_opcode != exe2_opcode) {
|
||||||
|
if(log != NULL)
|
||||||
|
fprintf(log, "%sInstructions at index %d have different opcodes (\"%s\" != \"%s\")\n", log_prefix, idx,
|
||||||
|
Executable_GetOpcodeName(exe1_opcode), Executable_GetOpcodeName(exe2_opcode));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the instruction opcode is the
|
||||||
|
// same, the number of operands must be
|
||||||
|
// the same too. (Their type must be
|
||||||
|
// the same too.)
|
||||||
|
assert(exe1_opc == exe2_opc);
|
||||||
|
|
||||||
|
for(int opno = 0; opno < exe1_opc; opno += 1) {
|
||||||
|
|
||||||
|
assert(exe1_opv[opno].type == exe2_opv[opno].type);
|
||||||
|
|
||||||
|
// Also, an executable can never have
|
||||||
|
// a promise operand. That's only used
|
||||||
|
// when building the executable.
|
||||||
|
assert(exe1_opv[opno].type != OPTP_PROMISE);
|
||||||
|
|
||||||
|
switch(exe1_opv[opno].type) {
|
||||||
|
|
||||||
|
case OPTP_INT:
|
||||||
|
{
|
||||||
|
int v1 = exe1_opv[opno].as_int;
|
||||||
|
int v2 = exe2_opv[opno].as_int;
|
||||||
|
if(v1 != v2) {
|
||||||
|
if(log != NULL)
|
||||||
|
fprintf(log, "%s%s Instructions (at index %d) have different integer operands (at index %d) (%d != %d)\n",
|
||||||
|
log_prefix, Executable_GetOpcodeName(exe1_opcode), idx, opno, v1, v2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPTP_FLOAT:
|
||||||
|
{
|
||||||
|
double v1 = exe1_opv[opno].as_float;
|
||||||
|
double v2 = exe2_opv[opno].as_float;
|
||||||
|
if(v1 != v2) {
|
||||||
|
if(log != NULL)
|
||||||
|
fprintf(log, "%s%s Instructions (at index %d) have different floating operands (at index %d) (%f != %f)\n",
|
||||||
|
log_prefix, Executable_GetOpcodeName(exe1_opcode), idx, opno, v1, v2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPTP_STRING:
|
||||||
|
{
|
||||||
|
const char *v1 = exe1_opv[opno].as_string;
|
||||||
|
const char *v2 = exe2_opv[opno].as_string;
|
||||||
|
if(strcmp(v1, v2)) {
|
||||||
|
if(log != NULL)
|
||||||
|
// TODO: Escape the strings before printing them.
|
||||||
|
fprintf(log, "%s%s Instructions (at index %d) have different string operands (at index %d) (\"%s\" != \"%s\")\n",
|
||||||
|
log_prefix, Executable_GetOpcodeName(exe1_opcode), idx, opno, v1, v2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPTP_PROMISE:
|
||||||
|
/* Unreachable */
|
||||||
|
assert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ExeBuilder *ExeBuilder_New(BPAlloc *alloc)
|
ExeBuilder *ExeBuilder_New(BPAlloc *alloc)
|
||||||
{
|
{
|
||||||
assert(alloc != NULL);
|
assert(alloc != NULL);
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#ifndef EXECUTABLE_H
|
#ifndef EXECUTABLE_H
|
||||||
#define EXECUTABLE_H
|
#define EXECUTABLE_H
|
||||||
|
#include <stdio.h>
|
||||||
#include "../utils/source.h"
|
#include "../utils/source.h"
|
||||||
#include "../utils/promise.h"
|
#include "../utils/promise.h"
|
||||||
|
|
||||||
@@ -95,12 +96,14 @@ typedef struct xExeBuilder ExeBuilder;
|
|||||||
Executable *Executable_Copy(Executable *exe);
|
Executable *Executable_Copy(Executable *exe);
|
||||||
void Executable_Free(Executable *exe);
|
void Executable_Free(Executable *exe);
|
||||||
void Executable_Dump(Executable *exe);
|
void Executable_Dump(Executable *exe);
|
||||||
|
_Bool Executable_Equiv(Executable *exe1, Executable *exe2, FILE *log, const char *log_prefix);
|
||||||
_Bool Executable_Fetch(Executable *exe, int index, Opcode *opcode, Operand *ops, int *opc);
|
_Bool Executable_Fetch(Executable *exe, int index, Opcode *opcode, Operand *ops, int *opc);
|
||||||
_Bool Executable_SetSource(Executable *exe, Source *src);
|
_Bool Executable_SetSource(Executable *exe, Source *src);
|
||||||
Source *Executable_GetSource(Executable *exe);
|
Source *Executable_GetSource(Executable *exe);
|
||||||
int Executable_GetInstrOffset(Executable *exe, int index);
|
int Executable_GetInstrOffset(Executable *exe, int index);
|
||||||
int Executable_GetInstrLength(Executable *exe, int index);
|
int Executable_GetInstrLength(Executable *exe, int index);
|
||||||
const char *Executable_GetOpcodeName(Opcode opcode);
|
const char *Executable_GetOpcodeName(Opcode opcode);
|
||||||
|
_Bool Executable_GetOpcodeBinaryFromName(const char *name, size_t name_len, Opcode *opcode);
|
||||||
|
|
||||||
ExeBuilder *ExeBuilder_New(BPAlloc *alloc);
|
ExeBuilder *ExeBuilder_New(BPAlloc *alloc);
|
||||||
_Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand *opv, int opc, int off, int len);
|
_Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand *opv, int opc, int off, int len);
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "utils/error.h"
|
||||||
|
#include "utils/source.h"
|
||||||
|
#include "compiler/parse.h"
|
||||||
|
#include "compiler/compile.h"
|
||||||
|
#include "builtins/basic.h"
|
||||||
|
#include "noja.h"
|
||||||
|
|
||||||
|
static void print_error(const char *type, Error *error)
|
||||||
|
{
|
||||||
|
if(type == NULL)
|
||||||
|
fprintf(stderr, "Error");
|
||||||
|
else if(error->internal)
|
||||||
|
fprintf(stderr, "Internal Error");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "%s Error", type);
|
||||||
|
|
||||||
|
fprintf(stderr, ": %s.", error->message);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
if(error->file != NULL)
|
||||||
|
{
|
||||||
|
if(error->line > 0 && error->func != NULL)
|
||||||
|
fprintf(stderr, " (Reported in %s:%d in %s)", error->file, error->line, error->func);
|
||||||
|
else if(error->line > 0 && error->func == NULL)
|
||||||
|
fprintf(stderr, " (Reported in %s:%d)", error->file, error->line);
|
||||||
|
else if(error->line < 1 && error->func != NULL)
|
||||||
|
fprintf(stderr, " (Reported in %s in %s)", error->file, error->func);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Executable *build(Source *src)
|
||||||
|
{
|
||||||
|
Executable *exe;
|
||||||
|
|
||||||
|
// Create a bump-pointer allocator to hold the AST.
|
||||||
|
BPAlloc *alloc = BPAlloc_Init(-1);
|
||||||
|
|
||||||
|
if(alloc == NULL)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Internal Error: Couldn't allocate bump-pointer allocator to hold the AST.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
|
||||||
|
// NOTE: The AST is stored in the BPAlloc. It's
|
||||||
|
// lifetime is the same as the pool.
|
||||||
|
AST *ast = parse(src, alloc, &error);
|
||||||
|
|
||||||
|
if(ast == NULL)
|
||||||
|
{
|
||||||
|
assert(error.occurred);
|
||||||
|
print_error("Parsing", &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
exe = compile(ast, alloc, &error);
|
||||||
|
|
||||||
|
// We're done with the AST, independently from
|
||||||
|
// the compilation result.
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
|
||||||
|
if(exe == NULL)
|
||||||
|
{
|
||||||
|
assert(error.occurred);
|
||||||
|
print_error("Compilation", &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool interpret(Source *src)
|
||||||
|
{
|
||||||
|
Executable *exe = build(src);
|
||||||
|
|
||||||
|
if(exe == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
Runtime *runt = Runtime_New(-1, 1024*1024, NULL, NULL);
|
||||||
|
|
||||||
|
if(runt == NULL)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
Error_Report(&error, 1, "Couldn't initialize runtime");
|
||||||
|
print_error(NULL, &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
Executable_Free(exe);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a [RuntimeError] instead of a simple [Error]
|
||||||
|
// because the [RuntimeError] makes a snapshot of the
|
||||||
|
// runtime state when an error is reported. Other than
|
||||||
|
// this fact they are interchangable. Any function that
|
||||||
|
// expects a pointer to [Error] can receive a [RuntimeError]
|
||||||
|
// upcasted to [Error].
|
||||||
|
RuntimeError error;
|
||||||
|
RuntimeError_Init(&error, runt); // Here we specify the runtime to snapshot in case of failure.
|
||||||
|
|
||||||
|
Object *bins = Object_NewStaticMap(bins_basic, runt, (Error*) &error);
|
||||||
|
|
||||||
|
if(bins == NULL)
|
||||||
|
{
|
||||||
|
assert(error.base.occurred == 1);
|
||||||
|
print_error(NULL, (Error*) &error);
|
||||||
|
RuntimeError_Free(&error);
|
||||||
|
Executable_Free(exe);
|
||||||
|
Runtime_Free(runt);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runtime_SetBuiltins(runt, bins);
|
||||||
|
|
||||||
|
Object *rets[8];
|
||||||
|
unsigned int maxretc = sizeof(rets)/sizeof(rets[0]);
|
||||||
|
|
||||||
|
int retc = run(runt, (Error*) &error, exe, 0, NULL, NULL, 0, rets, maxretc);
|
||||||
|
|
||||||
|
// NOTE: The pointer to the builtins object is invalidated
|
||||||
|
// now because it may be moved by the garbage collector.
|
||||||
|
|
||||||
|
if(retc < 0)
|
||||||
|
{
|
||||||
|
print_error("Runtime", (Error*) &error);
|
||||||
|
|
||||||
|
if(error.snapshot == NULL)
|
||||||
|
fprintf(stderr, "No snapshot available.\n");
|
||||||
|
else
|
||||||
|
Snapshot_Print(error.snapshot, stderr);
|
||||||
|
|
||||||
|
RuntimeError_Free(&error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Runtime_Free(runt);
|
||||||
|
Executable_Free(exe);
|
||||||
|
|
||||||
|
return retc > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool disassemble(Source *src)
|
||||||
|
{
|
||||||
|
Executable *exe = build(src);
|
||||||
|
|
||||||
|
if(exe == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
Executable_Dump(exe);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool interpret_file(const char *file)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
|
||||||
|
Source *src = Source_FromFile(file, &error);
|
||||||
|
|
||||||
|
if(src == NULL)
|
||||||
|
{
|
||||||
|
assert(error.occurred == 1);
|
||||||
|
print_error(NULL, &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool r = interpret(src);
|
||||||
|
|
||||||
|
Source_Free(src);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool interpret_code(const char *code)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
|
||||||
|
Source *src = Source_FromString(NULL, code, -1, &error);
|
||||||
|
|
||||||
|
if(src == NULL)
|
||||||
|
{
|
||||||
|
assert(error.occurred);
|
||||||
|
print_error(NULL, &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool r = interpret(src);
|
||||||
|
|
||||||
|
Source_Free(src);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool disassemble_file(const char *file)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
|
||||||
|
Source *src = Source_FromFile(file, &error);
|
||||||
|
|
||||||
|
if(src == NULL)
|
||||||
|
{
|
||||||
|
assert(error.occurred == 1);
|
||||||
|
print_error(NULL, &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool r = disassemble(src);
|
||||||
|
|
||||||
|
Source_Free(src);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool disassemble_code(const char *code)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
|
||||||
|
Source *src = Source_FromString(NULL, code, -1, &error);
|
||||||
|
|
||||||
|
if(src == NULL)
|
||||||
|
{
|
||||||
|
assert(error.occurred);
|
||||||
|
print_error(NULL, &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool r = disassemble(src);
|
||||||
|
|
||||||
|
Source_Free(src);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool NOJA_runString(const char *str)
|
||||||
|
{
|
||||||
|
return interpret_code(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool NOJA_runFile(const char *file)
|
||||||
|
{
|
||||||
|
return interpret_file(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool NOJA_dumpFileBytecode(const char *file)
|
||||||
|
{
|
||||||
|
return disassemble_file(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool NOJA_dumpStringBytecode(const char *str)
|
||||||
|
{
|
||||||
|
return disassemble_code(str);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef NOJA_H
|
||||||
|
#define NOJA_H
|
||||||
|
_Bool NOJA_runString(const char *str);
|
||||||
|
_Bool NOJA_runFile(const char *file);
|
||||||
|
_Bool NOJA_dumpFileBytecode(const char *file);
|
||||||
|
_Bool NOJA_dumpStringBytecode(const char *str);
|
||||||
|
#endif /* NOJA_H */
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "labellist.h"
|
||||||
|
|
||||||
|
typedef struct LabelInfo LabelInfo;
|
||||||
|
struct LabelInfo {
|
||||||
|
const char *name;
|
||||||
|
size_t name_len;
|
||||||
|
Promise *promise;
|
||||||
|
LabelInfo *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LabelList {
|
||||||
|
BPAlloc *alloc;
|
||||||
|
LabelInfo *head;
|
||||||
|
};
|
||||||
|
|
||||||
|
LabelList *LabelList_New(BPAlloc *alloc)
|
||||||
|
{
|
||||||
|
LabelList *list = BPAlloc_Malloc(alloc, sizeof(LabelList));
|
||||||
|
if(list != NULL) {
|
||||||
|
list->head = NULL;
|
||||||
|
list->alloc = alloc;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelList_Free(LabelList *list)
|
||||||
|
{
|
||||||
|
LabelInfo *info = list->head;
|
||||||
|
while(info != NULL) {
|
||||||
|
Promise_Free(info->promise);
|
||||||
|
info = info->next;
|
||||||
|
}
|
||||||
|
list->head = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LabelList_SetLabel(LabelList *list, const char *name, size_t name_len, long long int value)
|
||||||
|
{
|
||||||
|
Promise *promise = LabelList_GetLabel(list, name, name_len);
|
||||||
|
if(promise == NULL)
|
||||||
|
return false;
|
||||||
|
Promise_Resolve(promise, &value, sizeof(value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise *LabelList_GetLabel(LabelList *list, const char *name, size_t name_len)
|
||||||
|
{
|
||||||
|
// Find the label with the given name.
|
||||||
|
{
|
||||||
|
LabelInfo *info = list->head;
|
||||||
|
while(info != NULL) {
|
||||||
|
|
||||||
|
if(name_len == info->name_len
|
||||||
|
&& strncmp(name, info->name, name_len) == 0)
|
||||||
|
return info->promise;
|
||||||
|
|
||||||
|
info = info->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No such label. Create a new one.
|
||||||
|
LabelInfo *new_info;
|
||||||
|
{
|
||||||
|
BPAlloc *alloc = list->alloc;
|
||||||
|
Promise *promise = Promise_New(alloc, sizeof(long long int));
|
||||||
|
if(promise == NULL)\
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
new_info = BPAlloc_Malloc(alloc, sizeof(LabelInfo));
|
||||||
|
if(new_info == NULL) {
|
||||||
|
Promise_Free(promise);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_info->next = NULL;
|
||||||
|
new_info->name = name;
|
||||||
|
new_info->name_len = name_len;
|
||||||
|
new_info->promise = promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to the list
|
||||||
|
new_info->next = list->head;
|
||||||
|
list->head = new_info;
|
||||||
|
|
||||||
|
return new_info->promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t LabelList_GetUnresolvedCount(LabelList *list)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
LabelInfo *info = list->head;
|
||||||
|
while(info != NULL) {
|
||||||
|
count += !Promise_hasResolved(info->promise);
|
||||||
|
info = info->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef LABELLIST_H
|
||||||
|
#define LABELLIST_H
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "bpalloc.h"
|
||||||
|
#include "promise.h"
|
||||||
|
typedef struct LabelList LabelList;
|
||||||
|
LabelList *LabelList_New(BPAlloc *alloc);
|
||||||
|
void LabelList_Free(LabelList *list);
|
||||||
|
bool LabelList_SetLabel(LabelList *list, const char *name, size_t name_len, long long int value);
|
||||||
|
Promise *LabelList_GetLabel(LabelList *list, const char *name, size_t name_len);
|
||||||
|
size_t LabelList_GetUnresolvedCount(LabelList *list);
|
||||||
|
#endif /* LABELLIST_H */
|
||||||
@@ -71,9 +71,15 @@ unsigned int Promise_Size(Promise *promise)
|
|||||||
return promise->size;
|
return promise->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_Bool Promise_hasResolved(Promise *promise)
|
||||||
|
{
|
||||||
|
return promise->set;
|
||||||
|
}
|
||||||
|
|
||||||
void Promise_Free(Promise *promise)
|
void Promise_Free(Promise *promise)
|
||||||
{
|
{
|
||||||
assert(promise->set == 1);
|
(void) promise;
|
||||||
|
//assert(promise->set == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Promise_Resolve(Promise *promise, const void *data, int size)
|
void Promise_Resolve(Promise *promise, const void *data, int size)
|
||||||
@@ -38,4 +38,5 @@ void Promise_Free(Promise *promise);
|
|||||||
void Promise_Resolve(Promise *promise, const void *data, int size);
|
void Promise_Resolve(Promise *promise, const void *data, int size);
|
||||||
_Bool Promise_Subscribe(Promise *promise, void *dest);
|
_Bool Promise_Subscribe(Promise *promise, void *dest);
|
||||||
_Bool Promise_Subscribe2(Promise *promise, void *dest, void *userp, void (*callback)(void*));
|
_Bool Promise_Subscribe2(Promise *promise, void *dest, void *userp, void (*callback)(void*));
|
||||||
|
_Bool Promise_hasResolved(Promise *promise);
|
||||||
#endif
|
#endif
|
||||||
+9
-262
@@ -32,10 +32,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "compiler/parse.h"
|
#include "libnoja/noja.h"
|
||||||
#include "compiler/compile.h"
|
|
||||||
#include "runtime/runtime.h"
|
|
||||||
#include "builtins/basic.h"
|
|
||||||
|
|
||||||
static const char usage[] =
|
static const char usage[] =
|
||||||
"Usage patterns:\n"
|
"Usage patterns:\n"
|
||||||
@@ -44,242 +41,6 @@ static const char usage[] =
|
|||||||
" $ noja dis file.noja\n"
|
" $ noja dis file.noja\n"
|
||||||
" $ noja dis inline \"print('some noja code');\"\n";
|
" $ noja dis inline \"print('some noja code');\"\n";
|
||||||
|
|
||||||
static void print_error(const char *type, Error *error)
|
|
||||||
{
|
|
||||||
if(type == NULL)
|
|
||||||
fprintf(stderr, "Error");
|
|
||||||
else if(error->internal)
|
|
||||||
fprintf(stderr, "Internal Error");
|
|
||||||
else
|
|
||||||
fprintf(stderr, "%s Error", type);
|
|
||||||
|
|
||||||
fprintf(stderr, ": %s.", error->message);
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
if(error->file != NULL)
|
|
||||||
{
|
|
||||||
if(error->line > 0 && error->func != NULL)
|
|
||||||
fprintf(stderr, " (Reported in %s:%d in %s)", error->file, error->line, error->func);
|
|
||||||
else if(error->line > 0 && error->func == NULL)
|
|
||||||
fprintf(stderr, " (Reported in %s:%d)", error->file, error->line);
|
|
||||||
else if(error->line < 1 && error->func != NULL)
|
|
||||||
fprintf(stderr, " (Reported in %s in %s)", error->file, error->func);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static Executable *build(Source *src)
|
|
||||||
{
|
|
||||||
Executable *exe;
|
|
||||||
|
|
||||||
// Create a bump-pointer allocator to hold the AST.
|
|
||||||
BPAlloc *alloc = BPAlloc_Init(-1);
|
|
||||||
|
|
||||||
if(alloc == NULL)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Internal Error: Couldn't allocate bump-pointer allocator to hold the AST.\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
|
|
||||||
// NOTE: The AST is stored in the BPAlloc. It's
|
|
||||||
// lifetime is the same as the pool.
|
|
||||||
AST *ast = parse(src, alloc, &error);
|
|
||||||
|
|
||||||
if(ast == NULL)
|
|
||||||
{
|
|
||||||
assert(error.occurred);
|
|
||||||
print_error("Parsing", &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
BPAlloc_Free(alloc);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
exe = compile(ast, alloc, &error);
|
|
||||||
|
|
||||||
// We're done with the AST, independently from
|
|
||||||
// the compilation result.
|
|
||||||
BPAlloc_Free(alloc);
|
|
||||||
|
|
||||||
if(exe == NULL)
|
|
||||||
{
|
|
||||||
assert(error.occurred);
|
|
||||||
print_error("Compilation", &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return exe;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Bool interpret(Source *src)
|
|
||||||
{
|
|
||||||
Executable *exe = build(src);
|
|
||||||
|
|
||||||
if(exe == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
Runtime *runt = Runtime_New(-1, 1024*1024, NULL, NULL);
|
|
||||||
|
|
||||||
if(runt == NULL)
|
|
||||||
{
|
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
Error_Report(&error, 1, "Couldn't initialize runtime");
|
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
Executable_Free(exe);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use a [RuntimeError] instead of a simple [Error]
|
|
||||||
// because the [RuntimeError] makes a snapshot of the
|
|
||||||
// runtime state when an error is reported. Other than
|
|
||||||
// this fact they are interchangable. Any function that
|
|
||||||
// expects a pointer to [Error] can receive a [RuntimeError]
|
|
||||||
// upcasted to [Error].
|
|
||||||
RuntimeError error;
|
|
||||||
RuntimeError_Init(&error, runt); // Here we specify the runtime to snapshot in case of failure.
|
|
||||||
|
|
||||||
Object *bins = Object_NewStaticMap(bins_basic, runt, (Error*) &error);
|
|
||||||
|
|
||||||
if(bins == NULL)
|
|
||||||
{
|
|
||||||
assert(error.base.occurred == 1);
|
|
||||||
print_error(NULL, (Error*) &error);
|
|
||||||
RuntimeError_Free(&error);
|
|
||||||
Executable_Free(exe);
|
|
||||||
Runtime_Free(runt);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Runtime_SetBuiltins(runt, bins);
|
|
||||||
|
|
||||||
Object *rets[8];
|
|
||||||
unsigned int maxretc = sizeof(rets)/sizeof(rets[0]);
|
|
||||||
|
|
||||||
int retc = run(runt, (Error*) &error, exe, 0, NULL, NULL, 0, rets, maxretc);
|
|
||||||
|
|
||||||
// NOTE: The pointer to the builtins object is invalidated
|
|
||||||
// now because it may be moved by the garbage collector.
|
|
||||||
|
|
||||||
if(retc < 0)
|
|
||||||
{
|
|
||||||
print_error("Runtime", (Error*) &error);
|
|
||||||
|
|
||||||
if(error.snapshot == NULL)
|
|
||||||
fprintf(stderr, "No snapshot available.\n");
|
|
||||||
else
|
|
||||||
Snapshot_Print(error.snapshot, stderr);
|
|
||||||
|
|
||||||
RuntimeError_Free(&error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Runtime_Free(runt);
|
|
||||||
Executable_Free(exe);
|
|
||||||
|
|
||||||
return retc > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Bool disassemble(Source *src)
|
|
||||||
{
|
|
||||||
Executable *exe = build(src);
|
|
||||||
|
|
||||||
if(exe == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
Executable_Dump(exe);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Bool interpret_file(const char *file)
|
|
||||||
{
|
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
|
|
||||||
Source *src = Source_FromFile(file, &error);
|
|
||||||
|
|
||||||
if(src == NULL)
|
|
||||||
{
|
|
||||||
assert(error.occurred == 1);
|
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_Bool r = interpret(src);
|
|
||||||
|
|
||||||
Source_Free(src);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Bool interpret_code(const char *code)
|
|
||||||
{
|
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
|
|
||||||
Source *src = Source_FromString(NULL, code, -1, &error);
|
|
||||||
|
|
||||||
if(src == NULL)
|
|
||||||
{
|
|
||||||
assert(error.occurred);
|
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_Bool r = interpret(src);
|
|
||||||
|
|
||||||
Source_Free(src);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Bool disassemble_file(const char *file)
|
|
||||||
{
|
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
|
|
||||||
Source *src = Source_FromFile(file, &error);
|
|
||||||
|
|
||||||
if(src == NULL)
|
|
||||||
{
|
|
||||||
assert(error.occurred == 1);
|
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_Bool r = disassemble(src);
|
|
||||||
|
|
||||||
Source_Free(src);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Bool disassemble_code(const char *code)
|
|
||||||
{
|
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
|
|
||||||
Source *src = Source_FromString(NULL, code, -1, &error);
|
|
||||||
|
|
||||||
if(src == NULL)
|
|
||||||
{
|
|
||||||
assert(error.occurred);
|
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_Bool r = disassemble(src);
|
|
||||||
|
|
||||||
Source_Free(src);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
assert(argc > 0);
|
assert(argc > 0);
|
||||||
@@ -294,14 +55,9 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
if(!strcmp(argv[1], "run"))
|
if(!strcmp(argv[1], "run"))
|
||||||
{
|
{
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
|
|
||||||
if(argc == 2)
|
if(argc == 2)
|
||||||
{
|
{
|
||||||
Error_Report(&error, 0, "Missing source file");
|
fprintf(stderr, "Error: Missing source file.\n");
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,28 +67,21 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
if(argc == 3)
|
if(argc == 3)
|
||||||
{
|
{
|
||||||
Error_Report(&error, 0, "Missing source string");
|
fprintf(stderr, "Error: Missing source string.\n");
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
r = interpret_code(argv[3]);
|
r = NOJA_runString(argv[3]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
r = interpret_file(argv[2]);
|
r = NOJA_runFile(argv[2]);
|
||||||
return r ? 0 : -1;
|
return r ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!strcmp(argv[1], "dis"))
|
if(!strcmp(argv[1], "dis"))
|
||||||
{
|
{
|
||||||
Error error;
|
|
||||||
Error_Init(&error);
|
|
||||||
|
|
||||||
if(argc == 2)
|
if(argc == 2)
|
||||||
{
|
{
|
||||||
Error_Report(&error, 0, "Missing source file");
|
fprintf(stderr, "Error: Missing source file.\n");
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,16 +91,14 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
if(argc == 3)
|
if(argc == 3)
|
||||||
{
|
{
|
||||||
Error_Report(&error, 0, "Missing source string");
|
fprintf(stderr, "Error: Missing source string.\n");
|
||||||
print_error(NULL, &error);
|
|
||||||
Error_Free(&error);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = disassemble_code(argv[3]);
|
r = NOJA_dumpStringBytecode(argv[3]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
r = disassemble_file(argv[2]);
|
r = NOJA_dumpFileBytecode(argv[2]);
|
||||||
return r ? 0 : -1;
|
return r ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ADD
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
0: PUSHLST 4
|
||||||
|
1: PUSHINT 0
|
||||||
|
2: PUSHINT 1
|
||||||
|
3: INSERT
|
||||||
|
4: PUSHINT 1
|
||||||
|
5: PUSHINT 2
|
||||||
|
6: INSERT
|
||||||
|
7: PUSHINT 2
|
||||||
|
8: PUSHINT 3
|
||||||
|
9: INSERT
|
||||||
|
10: PUSHINT 3
|
||||||
|
11: PUSHINT 4
|
||||||
|
12: INSERT
|
||||||
|
13: ASS [l]
|
||||||
|
14: POP 1
|
||||||
|
15: PUSHSTR [ items.
|
||||||
|
]
|
||||||
|
16: PUSHVAR [l]
|
||||||
|
17: PUSHVAR [count]
|
||||||
|
18: CALL 1 1
|
||||||
|
19: PUSHSTR [The list contains ]
|
||||||
|
20: PUSHVAR [print]
|
||||||
|
21: CALL 3 1
|
||||||
|
22: POP 1
|
||||||
|
23: PUSHSTR [.
|
||||||
|
]
|
||||||
|
24: PUSHVAR [l]
|
||||||
|
25: PUSHSTR [The list is: ]
|
||||||
|
26: PUSHVAR [print]
|
||||||
|
27: CALL 3 1
|
||||||
|
28: POP 1
|
||||||
|
29: RETURN 0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
MUL
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
NOPE
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "../src/utils/bpalloc.h"
|
||||||
|
#include "../src/assembler/assembler.h"
|
||||||
|
|
||||||
|
static void stopTesting_(const char *file, int line)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "\n%s:%d :: Failed to set up test environment\n", file, line);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#define stopTestingIf(exp) \
|
||||||
|
do { \
|
||||||
|
if(exp) { \
|
||||||
|
stopTesting_(__FILE__, __LINE__); \
|
||||||
|
} \
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
static void testCase(int line, bool exp, const char *msg)
|
||||||
|
{
|
||||||
|
if(exp == false)
|
||||||
|
fprintf(stdout, "Test case failed (line %d) :: %s\n", line, msg);
|
||||||
|
else
|
||||||
|
fprintf(stdout, "Test case passed (line %d)\n", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Opcode opcode;
|
||||||
|
int operand_count;
|
||||||
|
Operand *operands;
|
||||||
|
} StaticInstruction;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int instr_count;
|
||||||
|
StaticInstruction *instr_list;
|
||||||
|
} StaticExecutable;
|
||||||
|
|
||||||
|
static Executable *buildExpectedExecutable(BPAlloc *alloc, StaticExecutable *static_exp_exe)
|
||||||
|
{
|
||||||
|
ExeBuilder *builder = ExeBuilder_New(alloc);
|
||||||
|
stopTestingIf(builder == NULL);
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
for(int i = 0; i < static_exp_exe->instr_count; i += 1) {
|
||||||
|
StaticInstruction instr = static_exp_exe->instr_list[i];
|
||||||
|
bool ok = ExeBuilder_Append(builder, &error, instr.opcode, instr.operands, instr.operand_count, -1, -1);
|
||||||
|
stopTestingIf(ok == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Executable *exe = ExeBuilder_Finalize(builder, &error);
|
||||||
|
stopTestingIf(exe == NULL);
|
||||||
|
|
||||||
|
Error_Free(&error);
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
int line;
|
||||||
|
bool fail;
|
||||||
|
const char *text;
|
||||||
|
StaticExecutable expected;
|
||||||
|
} tests[] = {
|
||||||
|
|
||||||
|
// Test the assembly of an empty string
|
||||||
|
// into an executable with no instructions.
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = "",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 0,
|
||||||
|
.instr_list = NULL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Test the assembly of the simplest
|
||||||
|
// executable possible
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = "POS",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 1,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Invalid opcode
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = "GUCCI",
|
||||||
|
.fail = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Label but no opcode
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = "label:",
|
||||||
|
.fail = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Basic executable with a label
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = "label: POS",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 1,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = " label : POS ",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 1,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = " label : POS ; ",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 1,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = " label0 : POS ; "
|
||||||
|
" label1 : NEG ; ",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 2,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_NEG,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = " label0 : POS ; "
|
||||||
|
" label1 : NEG ",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 2,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_NEG,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = " POS ; "
|
||||||
|
" label1 : NEG ",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 2,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_NEG,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.line = __LINE__,
|
||||||
|
.text = " label0 : POS ; "
|
||||||
|
" NEG ",
|
||||||
|
.fail = false,
|
||||||
|
.expected = (StaticExecutable) {
|
||||||
|
.instr_count = 2,
|
||||||
|
.instr_list = (StaticInstruction[]) {
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_POS,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.opcode = OPCODE_NEG,
|
||||||
|
.operands = NULL,
|
||||||
|
.operand_count = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
char pool[4096];
|
||||||
|
|
||||||
|
for(size_t i = 0; i < sizeof(tests)/sizeof(tests[0]); i += 1) {
|
||||||
|
|
||||||
|
// Use an allocator backed by a static
|
||||||
|
// buffer to avoid reallocating a pool
|
||||||
|
// for each test.
|
||||||
|
BPAlloc *alloc = BPAlloc_Init3(pool, sizeof(pool), -1, NULL, NULL, NULL);
|
||||||
|
stopTestingIf(alloc == NULL);
|
||||||
|
|
||||||
|
Error_Init(&error);
|
||||||
|
|
||||||
|
Source *src = Source_FromString(NULL, tests[i].text, -1, &error);
|
||||||
|
stopTestingIf(src == NULL);
|
||||||
|
|
||||||
|
Executable *got_exe = assemble(src, &error);
|
||||||
|
|
||||||
|
if(tests[i].fail == true) {
|
||||||
|
testCase(tests[i].line, got_exe == NULL, "Managed to assemble an invalid source");
|
||||||
|
} else {
|
||||||
|
Executable *exp_exe = buildExpectedExecutable(alloc, &tests[i].expected);
|
||||||
|
|
||||||
|
testCase(tests[i].line, got_exe != NULL, "Failed to assemble a valid source");
|
||||||
|
/*
|
||||||
|
fprintf(stderr, "== Generated ==\n");
|
||||||
|
Executable_Dump(got_exe);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "== Expected ==\n");
|
||||||
|
Executable_Dump(exp_exe);
|
||||||
|
*/
|
||||||
|
testCase(tests[i].line, Executable_Equiv(got_exe, exp_exe, stderr, "Executable diff :: "), "Invalid assemblation result");
|
||||||
|
|
||||||
|
Executable_Free(got_exe);
|
||||||
|
Executable_Free(exp_exe);
|
||||||
|
}
|
||||||
|
Source_Free(src);
|
||||||
|
Error_Free(&error);
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user