renamed compile() to codegen() and created new compile() that transforms a Source to an Executable directily
This commit is contained in:
@@ -0,0 +1,732 @@
|
|||||||
|
|
||||||
|
/* +--------------------------------------------------------------------------+
|
||||||
|
** | _ _ _ |
|
||||||
|
** | | \ | | (_) |
|
||||||
|
** | | \| | ___ _ __ _ |
|
||||||
|
** | | . ` |/ _ \| |/ _` | |
|
||||||
|
** | | |\ | (_) | | (_| | |
|
||||||
|
** | |_| \_|\___/| |\__,_| |
|
||||||
|
** | _/ | |
|
||||||
|
** | |__/ |
|
||||||
|
** +--------------------------------------------------------------------------+
|
||||||
|
** | Copyright (c) 2022 Francesco Cozzuto <francesco.cozzuto@gmail.com> |
|
||||||
|
** +--------------------------------------------------------------------------+
|
||||||
|
** | This file is part of The Noja Interpreter. |
|
||||||
|
** | |
|
||||||
|
** | The Noja Interpreter is free software: you can redistribute it and/or |
|
||||||
|
** | modify it under the terms of the GNU General Public License as published |
|
||||||
|
** | by the Free Software Foundation, either version 3 of the License, or (at |
|
||||||
|
** | your option) any later version. |
|
||||||
|
** | |
|
||||||
|
** | The Noja Interpreter is distributed in the hope that it will be useful, |
|
||||||
|
** | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
|
** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
||||||
|
** | Public License for more details. |
|
||||||
|
** | |
|
||||||
|
** | You should have received a copy of the GNU General Public License along |
|
||||||
|
** | with The Noja Interpreter. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
|
** +--------------------------------------------------------------------------+
|
||||||
|
** | WHAT IS THIS FILE? |
|
||||||
|
** | |
|
||||||
|
** | This file implements the routines that transform the AST into a list of |
|
||||||
|
** | bytecodes. The functionalities of this file are exposed through the |
|
||||||
|
** | `compile` function, that takes as input an `AST` and outputs an |
|
||||||
|
** | `Executable`. |
|
||||||
|
** | |
|
||||||
|
** | The function that does the heavy lifting is `emitInstrForNode` which |
|
||||||
|
** | walks the tree and writes instructions to the `ExeBuilder`. |
|
||||||
|
** | |
|
||||||
|
** | Some semantic errors are catched at this phase, in which case, they are |
|
||||||
|
** | reported by filling out the `error` structure and aborting. It's also |
|
||||||
|
** | possible that the compilation fails bacause of internal errors (which |
|
||||||
|
** | usually means "out of memory"). |
|
||||||
|
** +--------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "../utils/defs.h"
|
||||||
|
#include "compile.h"
|
||||||
|
#include "ASTi.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Error *error;
|
||||||
|
BPAlloc *alloc;
|
||||||
|
ExeBuilder *builder;
|
||||||
|
bool own_alloc;
|
||||||
|
jmp_buf env;
|
||||||
|
} CodegenContext;
|
||||||
|
|
||||||
|
typedef Promise Label;
|
||||||
|
|
||||||
|
static void setLabel(Label *label, long long int value)
|
||||||
|
{
|
||||||
|
Promise *promise = (Promise*) label;
|
||||||
|
Promise_Resolve(promise, &value, sizeof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setLabelHere(Label *label, CodegenContext *ctx)
|
||||||
|
{
|
||||||
|
long long int value = ExeBuilder_InstrCount(ctx->builder);
|
||||||
|
setLabel(label, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeLabel(Label *label)
|
||||||
|
{
|
||||||
|
Promise *promise = (Promise*) label;
|
||||||
|
Promise_Free(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void okNowJump(CodegenContext *ctx)
|
||||||
|
{
|
||||||
|
longjmp(ctx->env, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reportErrorAndJump_(CodegenContext *ctx, const char *file,
|
||||||
|
const char *func, int line, bool internal,
|
||||||
|
const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
_Error_Report2(ctx->error, internal, file, func, line, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
okNowJump(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define reportErrorAndJump(ctx, int, fmt, ...) \
|
||||||
|
reportErrorAndJump_(ctx, __FILE__, __func__, __LINE__, int, fmt, ## __VA_ARGS__)
|
||||||
|
|
||||||
|
static void freeCodegenContext(CodegenContext *ctx)
|
||||||
|
{
|
||||||
|
if(ctx->own_alloc)
|
||||||
|
BPAlloc_Free(ctx->alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setOrcatchJump(CodegenContext *ctx)
|
||||||
|
{
|
||||||
|
bool jumped = setjmp(ctx->env);
|
||||||
|
return jumped;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool initCodegenContext(CodegenContext *ctx, Error *error, BPAlloc *alloc)
|
||||||
|
{
|
||||||
|
if(alloc == NULL) {
|
||||||
|
alloc = BPAlloc_Init(-1);
|
||||||
|
if(alloc == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx->own_alloc = true;
|
||||||
|
} else {
|
||||||
|
ctx->own_alloc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExeBuilder *builder = ExeBuilder_New(alloc);
|
||||||
|
if(builder == NULL) {
|
||||||
|
if(ctx->own_alloc)
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->error = error;
|
||||||
|
ctx->alloc = alloc;
|
||||||
|
ctx->builder = builder;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr(CodegenContext *ctx, Opcode opcode, Operand *opv, int opc, int off, int len)
|
||||||
|
{
|
||||||
|
if(!ExeBuilder_Append(ctx->builder, ctx->error, opcode, opv, opc, off, len))
|
||||||
|
okNowJump(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr_POP(CodegenContext *ctx,
|
||||||
|
long long int op0,
|
||||||
|
int off, int len)
|
||||||
|
{
|
||||||
|
Operand opv[1] = {
|
||||||
|
{ .type = OPTP_INT, .as_int = op0 }
|
||||||
|
};
|
||||||
|
emitInstr(ctx, OPCODE_POP, opv, 1, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr_POP1(CodegenContext *ctx, int off, int len)
|
||||||
|
{
|
||||||
|
emitInstr_POP(ctx, 1, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr_ASS(CodegenContext *ctx, const char *name, int off, int len)
|
||||||
|
{
|
||||||
|
Operand opv[] = {
|
||||||
|
{ .type = OPTP_STRING, .as_string = name },
|
||||||
|
};
|
||||||
|
emitInstr(ctx, OPCODE_ASS, opv, 1, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr_RETURN(CodegenContext *ctx,
|
||||||
|
long long int op0,
|
||||||
|
int off, int len)
|
||||||
|
{
|
||||||
|
Operand opv[1] = {
|
||||||
|
{ .type = OPTP_INT, .as_int = op0 }
|
||||||
|
};
|
||||||
|
emitInstr(ctx, OPCODE_RETURN, opv, 1, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr_JUMP(CodegenContext *ctx,
|
||||||
|
Promise *op0,
|
||||||
|
int off, int len)
|
||||||
|
{
|
||||||
|
Operand opv[1] = {
|
||||||
|
{ .type = OPTP_PROMISE, .as_promise = op0 }
|
||||||
|
};
|
||||||
|
emitInstr(ctx, OPCODE_JUMP, opv, 1, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr_JUMPIFNOTANDPOP(CodegenContext *ctx,
|
||||||
|
Promise *op0,
|
||||||
|
int off, int len)
|
||||||
|
{
|
||||||
|
Operand opv[1] = {
|
||||||
|
{ .type = OPTP_PROMISE, .as_promise = op0 }
|
||||||
|
};
|
||||||
|
emitInstr(ctx, OPCODE_JUMPIFNOTANDPOP, opv, 1, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstr_JUMPIFANDPOP(CodegenContext *ctx,
|
||||||
|
long long int op0,
|
||||||
|
int off, int len)
|
||||||
|
{
|
||||||
|
Operand opv[1] = {
|
||||||
|
{ .type = OPTP_INT, .as_int = op0 }
|
||||||
|
};
|
||||||
|
emitInstr(ctx, OPCODE_JUMPIFANDPOP, opv, 1, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Label *newLabel(CodegenContext *ctx)
|
||||||
|
{
|
||||||
|
Promise *promise = Promise_New(ctx->alloc, sizeof(long long int));
|
||||||
|
if(promise != NULL)
|
||||||
|
return promise;
|
||||||
|
|
||||||
|
reportErrorAndJump(ctx, 1, "No memory");
|
||||||
|
UNREACHABLE;
|
||||||
|
return NULL; // For the compiler warning.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstrForNode(CodegenContext *ctx, Node *node, Label *label_break);
|
||||||
|
|
||||||
|
static Opcode exprkind_to_opcode(ExprKind kind)
|
||||||
|
{
|
||||||
|
switch(kind)
|
||||||
|
{
|
||||||
|
case EXPR_NOT: return OPCODE_NOT;
|
||||||
|
case EXPR_POS: return OPCODE_POS;
|
||||||
|
case EXPR_NEG: return OPCODE_NEG;
|
||||||
|
case EXPR_ADD: return OPCODE_ADD;
|
||||||
|
case EXPR_SUB: return OPCODE_SUB;
|
||||||
|
case EXPR_MUL: return OPCODE_MUL;
|
||||||
|
case EXPR_DIV: return OPCODE_DIV;
|
||||||
|
case EXPR_EQL: return OPCODE_EQL;
|
||||||
|
case EXPR_NQL: return OPCODE_NQL;
|
||||||
|
case EXPR_LSS: return OPCODE_LSS;
|
||||||
|
case EXPR_LEQ: return OPCODE_LEQ;
|
||||||
|
case EXPR_GRT: return OPCODE_GRT;
|
||||||
|
case EXPR_GEQ: return OPCODE_GEQ;
|
||||||
|
case EXPR_AND: return OPCODE_AND;
|
||||||
|
case EXPR_OR: return OPCODE_OR;
|
||||||
|
default:
|
||||||
|
UNREACHABLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
UNREACHABLE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstrForFuncCallNode(CodegenContext *ctx, CallExprNode *expr,
|
||||||
|
Label *label_break, int returns)
|
||||||
|
{
|
||||||
|
Node *arg = expr->argv;
|
||||||
|
|
||||||
|
while(arg)
|
||||||
|
{
|
||||||
|
emitInstrForNode(ctx, arg, label_break);
|
||||||
|
arg = arg->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitInstrForNode(ctx, expr->func, label_break);
|
||||||
|
|
||||||
|
Operand ops[2];
|
||||||
|
ops[0] = (Operand) { .type = OPTP_INT, .as_int = expr->argc };
|
||||||
|
ops[1] = (Operand) { .type = OPTP_INT, .as_int = returns };
|
||||||
|
emitInstr(ctx, OPCODE_CALL, ops, 2, expr->base.base.offset, expr->base.base.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstrForFuncNode(CodegenContext *ctx, FunctionNode *func)
|
||||||
|
{
|
||||||
|
Label *label_func = newLabel(ctx);
|
||||||
|
Label *label_jump = newLabel(ctx);
|
||||||
|
|
||||||
|
// Push function.
|
||||||
|
{
|
||||||
|
Operand ops[2] = {
|
||||||
|
{ .type = OPTP_PROMISE, .as_promise = label_func },
|
||||||
|
{ .type = OPTP_INT, .as_int = func->argc },
|
||||||
|
};
|
||||||
|
emitInstr(ctx, OPCODE_PUSHFUN, ops, 2, func->base.offset, func->base.length);
|
||||||
|
}
|
||||||
|
emitInstr_ASS(ctx, func->name, func->base.offset, func->base.length); // Assign variable
|
||||||
|
emitInstr_POP1(ctx, func->base.offset, func->base.length); // Pop function object
|
||||||
|
emitInstr_JUMP(ctx, label_jump, func->base.offset, func->base.length); // Jump after the function code
|
||||||
|
setLabelHere(label_func, ctx); // This is the function code index.
|
||||||
|
|
||||||
|
// Compile the function body.
|
||||||
|
{
|
||||||
|
// Assign the arguments.
|
||||||
|
ArgumentNode *arg = (ArgumentNode*) func->argv;
|
||||||
|
while(arg)
|
||||||
|
{
|
||||||
|
emitInstr_ASS(ctx, arg->name, arg->base.offset, arg->base.length);
|
||||||
|
emitInstr_POP1(ctx, arg->base.offset, arg->base.length);
|
||||||
|
arg = (ArgumentNode*) arg->base.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitInstrForNode(ctx, func->body, NULL);
|
||||||
|
|
||||||
|
if(func->body->kind == NODE_EXPR)
|
||||||
|
emitInstr_POP1(ctx, func->body->offset + func->body->length, 0);
|
||||||
|
|
||||||
|
// Write a return instruction, just
|
||||||
|
// in case it didn't already return.
|
||||||
|
emitInstr_RETURN(ctx, 0, func->body->offset, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the first index after the function code.
|
||||||
|
setLabelHere(label_jump, ctx);
|
||||||
|
|
||||||
|
freeLabel(label_func);
|
||||||
|
freeLabel(label_jump);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstrForIfElseNode(CodegenContext *ctx, IfElseNode *ifelse, Label *label_break)
|
||||||
|
{
|
||||||
|
emitInstrForNode(ctx, ifelse->condition, label_break);
|
||||||
|
if(ifelse->false_branch)
|
||||||
|
{
|
||||||
|
Label *label_else = newLabel(ctx);
|
||||||
|
Label *label_done = newLabel(ctx);
|
||||||
|
emitInstr_JUMPIFNOTANDPOP(ctx, label_else, ifelse->base.offset, ifelse->base.length);
|
||||||
|
emitInstrForNode(ctx, ifelse->true_branch, label_break);
|
||||||
|
if(ifelse->true_branch->kind == NODE_EXPR)
|
||||||
|
emitInstr_POP(ctx, 1, ifelse->true_branch->offset, 0);
|
||||||
|
emitInstr_JUMP(ctx, label_done, ifelse->base.offset, ifelse->base.length);
|
||||||
|
setLabelHere(label_else, ctx);
|
||||||
|
emitInstrForNode(ctx, ifelse->false_branch, label_break);
|
||||||
|
if(ifelse->false_branch->kind == NODE_EXPR)
|
||||||
|
emitInstr_POP1(ctx, ifelse->false_branch->offset, 0);
|
||||||
|
setLabelHere(label_done, ctx);
|
||||||
|
freeLabel(label_else);
|
||||||
|
freeLabel(label_done);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Label *label_done = newLabel(ctx);
|
||||||
|
emitInstr_JUMPIFNOTANDPOP(ctx, label_done, ifelse->base.offset, ifelse->base.length);
|
||||||
|
emitInstrForNode(ctx, ifelse->true_branch, label_break);
|
||||||
|
if(ifelse->true_branch->kind == NODE_EXPR)
|
||||||
|
emitInstr_POP1(ctx, ifelse->true_branch->offset, 0);
|
||||||
|
setLabelHere(label_done, ctx);
|
||||||
|
freeLabel(label_done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void flattenTupleTree(CodegenContext *ctx, ExprNode *root, ExprNode *tuple[], int max, int *count)
|
||||||
|
{
|
||||||
|
if(root->kind == EXPR_PAIR)
|
||||||
|
{
|
||||||
|
flattenTupleTree(ctx, (ExprNode*) ((OperExprNode*) root)->head, tuple, max, count);
|
||||||
|
flattenTupleTree(ctx, (ExprNode*) ((OperExprNode*) root)->head->next, tuple, max, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
if(max == *count)
|
||||||
|
{
|
||||||
|
reportErrorAndJump(ctx, 0, "Static buffer is too small");
|
||||||
|
UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
tuple[(*count)++] = root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstrForAssignmentNode(CodegenContext *ctx, OperExprNode *asgn, Label *label_break)
|
||||||
|
{
|
||||||
|
Node *lop, *rop;
|
||||||
|
lop = asgn->head;
|
||||||
|
rop = lop->next;
|
||||||
|
|
||||||
|
ExprNode *tuple[32];
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
flattenTupleTree(ctx, (ExprNode*) lop, tuple, sizeof(tuple)/sizeof(tuple[0]), &count);
|
||||||
|
|
||||||
|
assert(count > 0);
|
||||||
|
|
||||||
|
if(count == 1) /* No tuple. */
|
||||||
|
emitInstrForNode(ctx, rop, label_break);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(((ExprNode*) rop)->kind == EXPR_CALL)
|
||||||
|
emitInstrForFuncCallNode(ctx, (CallExprNode*) rop, label_break, count);
|
||||||
|
else {
|
||||||
|
reportErrorAndJump(ctx, 0, "Assigning to %d variables only 1 value", count);
|
||||||
|
UNREACHABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < count; i += 1)
|
||||||
|
{
|
||||||
|
ExprNode *tuple_item = tuple[count-i-1];
|
||||||
|
switch(tuple_item->kind)
|
||||||
|
{
|
||||||
|
case EXPR_IDENT:
|
||||||
|
{
|
||||||
|
const char *name = ((IdentExprNode*) tuple_item)->val;
|
||||||
|
emitInstr_ASS(ctx, name, tuple_item->base.offset, tuple_item->base.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_SELECT:
|
||||||
|
{
|
||||||
|
Node *idx = ((IndexSelectionExprNode*) tuple_item)->idx;
|
||||||
|
Node *set = ((IndexSelectionExprNode*) tuple_item)->set;
|
||||||
|
emitInstrForNode(ctx, set, label_break);
|
||||||
|
emitInstrForNode(ctx, idx, label_break);
|
||||||
|
emitInstr(ctx, OPCODE_INSERT2, NULL, 0, tuple_item->base.offset, tuple_item->base.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
reportErrorAndJump(ctx, 0, "Assigning to something that it can't be assigned to");
|
||||||
|
UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i+1 < count)
|
||||||
|
emitInstr_POP1(ctx, asgn->base.base.offset, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitInstrForNode(CodegenContext *ctx, Node *node, Label *label_break)
|
||||||
|
{
|
||||||
|
assert(node != NULL);
|
||||||
|
|
||||||
|
switch(node->kind)
|
||||||
|
{
|
||||||
|
case NODE_EXPR:
|
||||||
|
{
|
||||||
|
ExprNode *expr = (ExprNode*) node;
|
||||||
|
switch(expr->kind)
|
||||||
|
{
|
||||||
|
case EXPR_PAIR:
|
||||||
|
reportErrorAndJump(ctx, 0, "Tuple outside of assignment or return statement");
|
||||||
|
UNREACHABLE;
|
||||||
|
return; // For the compiler warning.
|
||||||
|
|
||||||
|
case EXPR_NOT:
|
||||||
|
case EXPR_POS:
|
||||||
|
case EXPR_NEG:
|
||||||
|
case EXPR_ADD:
|
||||||
|
case EXPR_SUB:
|
||||||
|
case EXPR_MUL:
|
||||||
|
case EXPR_DIV:
|
||||||
|
case EXPR_EQL:
|
||||||
|
case EXPR_NQL:
|
||||||
|
case EXPR_LSS:
|
||||||
|
case EXPR_LEQ:
|
||||||
|
case EXPR_GRT:
|
||||||
|
case EXPR_GEQ:
|
||||||
|
case EXPR_AND:
|
||||||
|
case EXPR_OR:
|
||||||
|
{
|
||||||
|
OperExprNode *oper = (OperExprNode*) expr;
|
||||||
|
for(Node *operand = oper->head; operand; operand = operand->next)
|
||||||
|
emitInstrForNode(ctx, operand, label_break);
|
||||||
|
emitInstr(ctx, exprkind_to_opcode(expr->kind), NULL, 0, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_ASS:
|
||||||
|
emitInstrForAssignmentNode(ctx, (OperExprNode*) expr, label_break);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EXPR_INT:
|
||||||
|
{
|
||||||
|
IntExprNode *p = (IntExprNode*) expr;
|
||||||
|
Operand op = { .type = OPTP_INT, .as_int = p->val };
|
||||||
|
emitInstr(ctx, OPCODE_PUSHINT, &op, 1, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_FLOAT:
|
||||||
|
{
|
||||||
|
FloatExprNode *p = (FloatExprNode*) expr;
|
||||||
|
Operand op = { .type = OPTP_FLOAT, .as_float = p->val };
|
||||||
|
emitInstr(ctx, OPCODE_PUSHFLT, &op, 1, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_STRING:
|
||||||
|
{
|
||||||
|
StringExprNode *p = (StringExprNode*) expr;
|
||||||
|
Operand op = { .type = OPTP_STRING, .as_string = p->val };
|
||||||
|
emitInstr(ctx, OPCODE_PUSHSTR, &op, 1, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_IDENT:
|
||||||
|
{
|
||||||
|
IdentExprNode *p = (IdentExprNode*) expr;
|
||||||
|
Operand op = { .type = OPTP_STRING, .as_string = p->val };
|
||||||
|
emitInstr(ctx, OPCODE_PUSHVAR, &op, 1, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_LIST:
|
||||||
|
{
|
||||||
|
// PUSHLST
|
||||||
|
// PUSHINT
|
||||||
|
// <expr>
|
||||||
|
// INSERT
|
||||||
|
|
||||||
|
ListExprNode *l = (ListExprNode*) node;
|
||||||
|
|
||||||
|
Operand op;
|
||||||
|
|
||||||
|
op = (Operand) { .type = OPTP_INT, .as_int = l->itemc };
|
||||||
|
emitInstr(ctx, OPCODE_PUSHLST, &op, 1, node->offset, node->length);
|
||||||
|
|
||||||
|
Node *item = l->items;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while(item)
|
||||||
|
{
|
||||||
|
op = (Operand) { .type = OPTP_INT, .as_int = i };
|
||||||
|
emitInstr(ctx, OPCODE_PUSHINT, &op, 1, item->offset, item->length);
|
||||||
|
emitInstrForNode(ctx, item, label_break);
|
||||||
|
emitInstr(ctx, OPCODE_INSERT, NULL, 0, item->offset, item->length);
|
||||||
|
i += 1;
|
||||||
|
item = item->next;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_MAP:
|
||||||
|
{
|
||||||
|
MapExprNode *m = (MapExprNode*) node;
|
||||||
|
|
||||||
|
Operand op;
|
||||||
|
|
||||||
|
op = (Operand) { .type = OPTP_INT, .as_int = m->itemc };
|
||||||
|
emitInstr(ctx, OPCODE_PUSHMAP, &op, 1, node->offset, node->length);
|
||||||
|
|
||||||
|
Node *key = m->keys;
|
||||||
|
Node *item = m->items;
|
||||||
|
|
||||||
|
while(item)
|
||||||
|
{
|
||||||
|
emitInstrForNode(ctx, key, label_break);
|
||||||
|
emitInstrForNode(ctx, item, label_break);
|
||||||
|
emitInstr(ctx, OPCODE_INSERT, NULL, 0, item->offset, item->length);
|
||||||
|
key = key->next;
|
||||||
|
item = item->next;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_CALL:
|
||||||
|
emitInstrForFuncCallNode(ctx, (CallExprNode*) expr, label_break, 1);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EXPR_SELECT:
|
||||||
|
{
|
||||||
|
IndexSelectionExprNode *sel = (IndexSelectionExprNode*) expr;
|
||||||
|
emitInstrForNode(ctx, sel->set, label_break);
|
||||||
|
emitInstrForNode(ctx, sel->idx, label_break);
|
||||||
|
emitInstr(ctx, OPCODE_SELECT, NULL, 0, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPR_NONE:
|
||||||
|
emitInstr(ctx, OPCODE_PUSHNNE, NULL, 0, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EXPR_TRUE:
|
||||||
|
emitInstr(ctx, OPCODE_PUSHTRU, NULL, 0, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EXPR_FALSE:
|
||||||
|
emitInstr(ctx, OPCODE_PUSHFLS, NULL, 0, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UNREACHABLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NODE_BREAK:
|
||||||
|
if(label_break == NULL)
|
||||||
|
reportErrorAndJump(ctx, 0, "Break not inside a loop");
|
||||||
|
emitInstr_JUMP(ctx, label_break, node->offset, node->length);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case NODE_IFELSE:
|
||||||
|
emitInstrForIfElseNode(ctx, (IfElseNode*) node, label_break);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case NODE_WHILE:
|
||||||
|
{
|
||||||
|
WhileNode *whl = (WhileNode*) node;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* start:
|
||||||
|
* <condition>
|
||||||
|
* JUMPIFNOTANDPOP end
|
||||||
|
* <body>
|
||||||
|
* JUMP start
|
||||||
|
* end:
|
||||||
|
*/
|
||||||
|
|
||||||
|
Label *label_start = newLabel(ctx);
|
||||||
|
Label *label_end = newLabel(ctx);
|
||||||
|
setLabelHere(label_start, ctx);
|
||||||
|
emitInstrForNode(ctx, whl->condition, label_break);
|
||||||
|
emitInstr_JUMPIFNOTANDPOP(ctx, label_end, whl->condition->offset, whl->condition->length);
|
||||||
|
emitInstrForNode(ctx, whl->body, label_end);
|
||||||
|
if(whl->body->kind == NODE_EXPR)
|
||||||
|
emitInstr_POP1(ctx, whl->body->offset, 0);
|
||||||
|
emitInstr_JUMP(ctx, label_start, node->offset, node->length);
|
||||||
|
setLabelHere(label_end, ctx);
|
||||||
|
freeLabel(label_start);
|
||||||
|
freeLabel(label_end);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NODE_DOWHILE:
|
||||||
|
{
|
||||||
|
DoWhileNode *dowhl = (DoWhileNode*) node;
|
||||||
|
/*
|
||||||
|
* start:
|
||||||
|
* <body>
|
||||||
|
* <condition>
|
||||||
|
* JUMPIFANDPOP start
|
||||||
|
*/
|
||||||
|
|
||||||
|
Label *label_end = newLabel(ctx);
|
||||||
|
long long int start = ExeBuilder_InstrCount(ctx->builder);
|
||||||
|
emitInstrForNode(ctx, dowhl->body, label_end);
|
||||||
|
if(dowhl->body->kind == NODE_EXPR)
|
||||||
|
emitInstr_POP1(ctx, dowhl->body->offset, 0);
|
||||||
|
emitInstrForNode(ctx, dowhl->condition, label_break);
|
||||||
|
emitInstr_JUMPIFANDPOP(ctx, start, dowhl->condition->offset, dowhl->condition->length);
|
||||||
|
setLabelHere(label_end, ctx);
|
||||||
|
freeLabel(label_end);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NODE_COMP:
|
||||||
|
{
|
||||||
|
CompoundNode *comp = (CompoundNode*) node;
|
||||||
|
|
||||||
|
Node *stmt = comp->head;
|
||||||
|
|
||||||
|
while(stmt)
|
||||||
|
{
|
||||||
|
emitInstrForNode(ctx, stmt, label_break);
|
||||||
|
if(stmt->kind == NODE_EXPR)
|
||||||
|
emitInstr_POP1(ctx, stmt->offset, 0);
|
||||||
|
stmt = stmt->next;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NODE_RETURN:
|
||||||
|
{
|
||||||
|
ReturnNode *ret = (ReturnNode*) node;
|
||||||
|
|
||||||
|
ExprNode *tuple[32];
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
flattenTupleTree(ctx, (ExprNode*) ret->val, tuple, sizeof(tuple)/sizeof(tuple[0]), &count);
|
||||||
|
|
||||||
|
for(int i = 0; i < count; i += 1)
|
||||||
|
emitInstrForNode(ctx, (Node*) tuple[i], label_break);
|
||||||
|
emitInstr_RETURN(ctx, count, ret->base.offset, ret->base.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NODE_FUNC:
|
||||||
|
emitInstrForFuncNode(ctx, (FunctionNode*) node);
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UNREACHABLE;
|
||||||
|
}
|
||||||
|
UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Executable *makeExecutableAndFreeCodegenContext(CodegenContext *ctx, Source *src)
|
||||||
|
{
|
||||||
|
Executable *exe = ExeBuilder_Finalize(ctx->builder, ctx->error);
|
||||||
|
if(exe == NULL)
|
||||||
|
okNowJump(ctx);
|
||||||
|
|
||||||
|
Executable_SetSource(exe, src);
|
||||||
|
freeCodegenContext(ctx);
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Symbol: codegen
|
||||||
|
*
|
||||||
|
* Serializes an AST into bytecode format.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
*
|
||||||
|
* ast: The AST to be serialized.
|
||||||
|
* alloc: The allocator that will be used to get new
|
||||||
|
* memory. (optional)
|
||||||
|
* error: Error information structure that is filled out if
|
||||||
|
* an error occurres.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* A pointer to an `Executable` that is the object that
|
||||||
|
* contains the bytecode. If an error occurres, NULL is
|
||||||
|
* returned and the `error` structure is filled out.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Executable *codegen(AST *ast, BPAlloc *alloc, Error *error)
|
||||||
|
{
|
||||||
|
assert(ast != NULL);
|
||||||
|
assert(error != NULL);
|
||||||
|
|
||||||
|
CodegenContext ctx;
|
||||||
|
if(!initCodegenContext(&ctx, error, alloc))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
bool jumped = setOrcatchJump(&ctx);
|
||||||
|
if(jumped) {
|
||||||
|
freeCodegenContext(&ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitInstrForNode(&ctx, ast->root, NULL);
|
||||||
|
emitInstr_RETURN(&ctx, 0, Source_GetSize(ast->src), 0);
|
||||||
|
return makeExecutableAndFreeCodegenContext(&ctx, ast->src);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
/* +--------------------------------------------------------------------------+
|
||||||
|
** | _ _ _ |
|
||||||
|
** | | \ | | (_) |
|
||||||
|
** | | \| | ___ _ __ _ |
|
||||||
|
** | | . ` |/ _ \| |/ _` | |
|
||||||
|
** | | |\ | (_) | | (_| | |
|
||||||
|
** | |_| \_|\___/| |\__,_| |
|
||||||
|
** | _/ | |
|
||||||
|
** | |__/ |
|
||||||
|
** +--------------------------------------------------------------------------+
|
||||||
|
** | Copyright (c) 2022 Francesco Cozzuto <francesco.cozzuto@gmail.com> |
|
||||||
|
** +--------------------------------------------------------------------------+
|
||||||
|
** | This file is part of The Noja Interpreter. |
|
||||||
|
** | |
|
||||||
|
** | The Noja Interpreter is free software: you can redistribute it and/or |
|
||||||
|
** | modify it under the terms of the GNU General Public License as published |
|
||||||
|
** | by the Free Software Foundation, either version 3 of the License, or (at |
|
||||||
|
** | your option) any later version. |
|
||||||
|
** | |
|
||||||
|
** | The Noja Interpreter is distributed in the hope that it will be useful, |
|
||||||
|
** | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
|
** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
||||||
|
** | Public License for more details. |
|
||||||
|
** | |
|
||||||
|
** | You should have received a copy of the GNU General Public License along |
|
||||||
|
** | with The Noja Interpreter. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
|
** +--------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CODEGEN_H
|
||||||
|
#define CODEGEN_H
|
||||||
|
#include "../utils/error.h"
|
||||||
|
#include "../utils/bpalloc.h"
|
||||||
|
#include "../common/executable.h"
|
||||||
|
#include "AST.h"
|
||||||
|
Executable *codegen(AST *ast, BPAlloc *alloc, Error *error);
|
||||||
|
#endif /* CODEGEN_H */
|
||||||
+35
-716
@@ -1,733 +1,52 @@
|
|||||||
|
|
||||||
/* +--------------------------------------------------------------------------+
|
|
||||||
** | _ _ _ |
|
|
||||||
** | | \ | | (_) |
|
|
||||||
** | | \| | ___ _ __ _ |
|
|
||||||
** | | . ` |/ _ \| |/ _` | |
|
|
||||||
** | | |\ | (_) | | (_| | |
|
|
||||||
** | |_| \_|\___/| |\__,_| |
|
|
||||||
** | _/ | |
|
|
||||||
** | |__/ |
|
|
||||||
** +--------------------------------------------------------------------------+
|
|
||||||
** | Copyright (c) 2022 Francesco Cozzuto <francesco.cozzuto@gmail.com> |
|
|
||||||
** +--------------------------------------------------------------------------+
|
|
||||||
** | This file is part of The Noja Interpreter. |
|
|
||||||
** | |
|
|
||||||
** | The Noja Interpreter is free software: you can redistribute it and/or |
|
|
||||||
** | modify it under the terms of the GNU General Public License as published |
|
|
||||||
** | by the Free Software Foundation, either version 3 of the License, or (at |
|
|
||||||
** | your option) any later version. |
|
|
||||||
** | |
|
|
||||||
** | The Noja Interpreter is distributed in the hope that it will be useful, |
|
|
||||||
** | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
||||||
** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
|
||||||
** | Public License for more details. |
|
|
||||||
** | |
|
|
||||||
** | You should have received a copy of the GNU General Public License along |
|
|
||||||
** | with The Noja Interpreter. If not, see <http://www.gnu.org/licenses/>. |
|
|
||||||
** +--------------------------------------------------------------------------+
|
|
||||||
** | WHAT IS THIS FILE? |
|
|
||||||
** | |
|
|
||||||
** | This file implements the routines that transform the AST into a list of |
|
|
||||||
** | bytecodes. The functionalities of this file are exposed through the |
|
|
||||||
** | `compile` function, that takes as input an `AST` and outputs an |
|
|
||||||
** | `Executable`. |
|
|
||||||
** | |
|
|
||||||
** | The function that does the heavy lifting is `emitInstrForNode` which |
|
|
||||||
** | walks the tree and writes instructions to the `ExeBuilder`. |
|
|
||||||
** | |
|
|
||||||
** | Some semantic errors are catched at this phase, in which case, they are |
|
|
||||||
** | reported by filling out the `error` structure and aborting. It's also |
|
|
||||||
** | possible that the compilation fails bacause of internal errors (which |
|
|
||||||
** | usually means "out of memory"). |
|
|
||||||
** +--------------------------------------------------------------------------+
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <setjmp.h>
|
#include "../utils/bpalloc.h"
|
||||||
#include <stdlib.h>
|
#include "AST.h"
|
||||||
#include <stdbool.h>
|
#include "parse.h"
|
||||||
#include "../utils/defs.h"
|
#include "codegen.h"
|
||||||
#include "compile.h"
|
#include "compile.h"
|
||||||
#include "ASTi.h"
|
|
||||||
|
|
||||||
typedef struct {
|
Executable *compile(Source *src, Error *error, CompilationErrorType *errtyp)
|
||||||
Error *error;
|
|
||||||
BPAlloc *alloc;
|
|
||||||
ExeBuilder *builder;
|
|
||||||
bool own_alloc;
|
|
||||||
jmp_buf env;
|
|
||||||
} CodegenContext;
|
|
||||||
|
|
||||||
typedef Promise Label;
|
|
||||||
|
|
||||||
static void setLabel(Label *label, long long int value)
|
|
||||||
{
|
{
|
||||||
Promise *promise = (Promise*) label;
|
// Create a bump-pointer allocator to hold the AST.
|
||||||
Promise_Resolve(promise, &value, sizeof(value));
|
BPAlloc *alloc = BPAlloc_Init(-1);
|
||||||
}
|
|
||||||
|
|
||||||
static void setLabelHere(Label *label, CodegenContext *ctx)
|
if(alloc == NULL)
|
||||||
{
|
{
|
||||||
long long int value = ExeBuilder_InstrCount(ctx->builder);
|
*errtyp = CompilationErrorType_INTERNAL;
|
||||||
setLabel(label, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void freeLabel(Label *label)
|
|
||||||
{
|
|
||||||
Promise *promise = (Promise*) label;
|
|
||||||
Promise_Free(promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void okNowJump(CodegenContext *ctx)
|
|
||||||
{
|
|
||||||
longjmp(ctx->env, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void reportErrorAndJump_(CodegenContext *ctx, const char *file,
|
|
||||||
const char *func, int line, bool internal,
|
|
||||||
const char *format, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
_Error_Report2(ctx->error, internal, file, func, line, format, args);
|
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
okNowJump(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define reportErrorAndJump(ctx, int, fmt, ...) \
|
|
||||||
reportErrorAndJump_(ctx, __FILE__, __func__, __LINE__, int, fmt, ## __VA_ARGS__)
|
|
||||||
|
|
||||||
static void freeCodegenContext(CodegenContext *ctx)
|
|
||||||
{
|
|
||||||
if(ctx->own_alloc)
|
|
||||||
BPAlloc_Free(ctx->alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool setOrcatchJump(CodegenContext *ctx)
|
|
||||||
{
|
|
||||||
bool jumped = setjmp(ctx->env);
|
|
||||||
return jumped;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool initCodegenContext(CodegenContext *ctx, Error *error, BPAlloc *alloc)
|
|
||||||
{
|
|
||||||
if(alloc == NULL) {
|
|
||||||
alloc = BPAlloc_Init(-1);
|
|
||||||
if(alloc == NULL) {
|
|
||||||
Error_Report(error, 1, "No memory");
|
Error_Report(error, 1, "No memory");
|
||||||
return false;
|
return NULL;
|
||||||
}
|
|
||||||
ctx->own_alloc = true;
|
|
||||||
} else {
|
|
||||||
ctx->own_alloc = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExeBuilder *builder = ExeBuilder_New(alloc);
|
// NOTE: The AST is stored in the BPAlloc. It's
|
||||||
if(builder == NULL) {
|
// lifetime is the same as the pool.
|
||||||
if(ctx->own_alloc)
|
AST *ast = parse(src, alloc, error);
|
||||||
|
|
||||||
|
if(ast == NULL)
|
||||||
|
{
|
||||||
|
assert(error->occurred);
|
||||||
|
if(error->internal)
|
||||||
|
*errtyp = CompilationErrorType_INTERNAL;
|
||||||
|
else
|
||||||
|
*errtyp = CompilationErrorType_SYNTAX;
|
||||||
BPAlloc_Free(alloc);
|
BPAlloc_Free(alloc);
|
||||||
return false;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->error = error;
|
Executable *exe = codegen(ast, alloc, error);
|
||||||
ctx->alloc = alloc;
|
|
||||||
ctx->builder = builder;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr(CodegenContext *ctx, Opcode opcode, Operand *opv, int opc, int off, int len)
|
// We're done with the AST, independently from
|
||||||
{
|
// the compilation result.
|
||||||
if(!ExeBuilder_Append(ctx->builder, ctx->error, opcode, opv, opc, off, len))
|
BPAlloc_Free(alloc);
|
||||||
okNowJump(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr_POP(CodegenContext *ctx,
|
|
||||||
long long int op0,
|
|
||||||
int off, int len)
|
|
||||||
{
|
|
||||||
Operand opv[1] = {
|
|
||||||
{ .type = OPTP_INT, .as_int = op0 }
|
|
||||||
};
|
|
||||||
emitInstr(ctx, OPCODE_POP, opv, 1, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr_POP1(CodegenContext *ctx, int off, int len)
|
|
||||||
{
|
|
||||||
emitInstr_POP(ctx, 1, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr_ASS(CodegenContext *ctx, const char *name, int off, int len)
|
|
||||||
{
|
|
||||||
Operand opv[] = {
|
|
||||||
{ .type = OPTP_STRING, .as_string = name },
|
|
||||||
};
|
|
||||||
emitInstr(ctx, OPCODE_ASS, opv, 1, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr_RETURN(CodegenContext *ctx,
|
|
||||||
long long int op0,
|
|
||||||
int off, int len)
|
|
||||||
{
|
|
||||||
Operand opv[1] = {
|
|
||||||
{ .type = OPTP_INT, .as_int = op0 }
|
|
||||||
};
|
|
||||||
emitInstr(ctx, OPCODE_RETURN, opv, 1, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr_JUMP(CodegenContext *ctx,
|
|
||||||
Promise *op0,
|
|
||||||
int off, int len)
|
|
||||||
{
|
|
||||||
Operand opv[1] = {
|
|
||||||
{ .type = OPTP_PROMISE, .as_promise = op0 }
|
|
||||||
};
|
|
||||||
emitInstr(ctx, OPCODE_JUMP, opv, 1, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr_JUMPIFNOTANDPOP(CodegenContext *ctx,
|
|
||||||
Promise *op0,
|
|
||||||
int off, int len)
|
|
||||||
{
|
|
||||||
Operand opv[1] = {
|
|
||||||
{ .type = OPTP_PROMISE, .as_promise = op0 }
|
|
||||||
};
|
|
||||||
emitInstr(ctx, OPCODE_JUMPIFNOTANDPOP, opv, 1, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstr_JUMPIFANDPOP(CodegenContext *ctx,
|
|
||||||
long long int op0,
|
|
||||||
int off, int len)
|
|
||||||
{
|
|
||||||
Operand opv[1] = {
|
|
||||||
{ .type = OPTP_INT, .as_int = op0 }
|
|
||||||
};
|
|
||||||
emitInstr(ctx, OPCODE_JUMPIFANDPOP, opv, 1, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Label *newLabel(CodegenContext *ctx)
|
|
||||||
{
|
|
||||||
Promise *promise = Promise_New(ctx->alloc, sizeof(long long int));
|
|
||||||
if(promise != NULL)
|
|
||||||
return promise;
|
|
||||||
|
|
||||||
reportErrorAndJump(ctx, 1, "No memory");
|
|
||||||
UNREACHABLE;
|
|
||||||
return NULL; // For the compiler warning.
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstrForNode(CodegenContext *ctx, Node *node, Promise *break_dest);
|
|
||||||
|
|
||||||
static Opcode exprkind_to_opcode(ExprKind kind)
|
|
||||||
{
|
|
||||||
switch(kind)
|
|
||||||
{
|
|
||||||
case EXPR_NOT: return OPCODE_NOT;
|
|
||||||
case EXPR_POS: return OPCODE_POS;
|
|
||||||
case EXPR_NEG: return OPCODE_NEG;
|
|
||||||
case EXPR_ADD: return OPCODE_ADD;
|
|
||||||
case EXPR_SUB: return OPCODE_SUB;
|
|
||||||
case EXPR_MUL: return OPCODE_MUL;
|
|
||||||
case EXPR_DIV: return OPCODE_DIV;
|
|
||||||
case EXPR_EQL: return OPCODE_EQL;
|
|
||||||
case EXPR_NQL: return OPCODE_NQL;
|
|
||||||
case EXPR_LSS: return OPCODE_LSS;
|
|
||||||
case EXPR_LEQ: return OPCODE_LEQ;
|
|
||||||
case EXPR_GRT: return OPCODE_GRT;
|
|
||||||
case EXPR_GEQ: return OPCODE_GEQ;
|
|
||||||
case EXPR_AND: return OPCODE_AND;
|
|
||||||
case EXPR_OR: return OPCODE_OR;
|
|
||||||
default:
|
|
||||||
UNREACHABLE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
UNREACHABLE;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstrForFuncCallNode(CodegenContext *ctx, CallExprNode *expr,
|
|
||||||
Promise *break_dest, int returns)
|
|
||||||
{
|
|
||||||
Node *arg = expr->argv;
|
|
||||||
|
|
||||||
while(arg)
|
|
||||||
{
|
|
||||||
emitInstrForNode(ctx, arg, break_dest);
|
|
||||||
arg = arg->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitInstrForNode(ctx, expr->func, break_dest);
|
|
||||||
|
|
||||||
Operand ops[2];
|
|
||||||
ops[0] = (Operand) { .type = OPTP_INT, .as_int = expr->argc };
|
|
||||||
ops[1] = (Operand) { .type = OPTP_INT, .as_int = returns };
|
|
||||||
emitInstr(ctx, OPCODE_CALL, ops, 2, expr->base.base.offset, expr->base.base.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstrForFuncNode(CodegenContext *ctx, FunctionNode *func, Promise *break_dest)
|
|
||||||
{
|
|
||||||
Label *func_index = newLabel(ctx);
|
|
||||||
Label *jump_index = newLabel(ctx);
|
|
||||||
|
|
||||||
// Push function.
|
|
||||||
{
|
|
||||||
Operand ops[2] = {
|
|
||||||
{ .type = OPTP_PROMISE, .as_promise = func_index },
|
|
||||||
{ .type = OPTP_INT, .as_int = func->argc },
|
|
||||||
};
|
|
||||||
emitInstr(ctx, OPCODE_PUSHFUN, ops, 2, func->base.offset, func->base.length);
|
|
||||||
}
|
|
||||||
emitInstr_ASS(ctx, func->name, func->base.offset, func->base.length); // Assign variable
|
|
||||||
emitInstr_POP1(ctx, func->base.offset, func->base.length); // Pop function object
|
|
||||||
emitInstr_JUMP(ctx, jump_index, func->base.offset, func->base.length); // Jump after the function code
|
|
||||||
setLabelHere(func_index, ctx); // This is the function code index.
|
|
||||||
|
|
||||||
// Compile the function body.
|
|
||||||
{
|
|
||||||
// Assign the arguments.
|
|
||||||
ArgumentNode *arg = (ArgumentNode*) func->argv;
|
|
||||||
while(arg)
|
|
||||||
{
|
|
||||||
emitInstr_ASS(ctx, arg->name, arg->base.offset, arg->base.length);
|
|
||||||
emitInstr_POP1(ctx, arg->base.offset, arg->base.length);
|
|
||||||
arg = (ArgumentNode*) arg->base.next;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitInstrForNode(ctx, func->body, NULL);
|
|
||||||
|
|
||||||
if(func->body->kind == NODE_EXPR)
|
|
||||||
emitInstr_POP1(ctx, func->body->offset + func->body->length, 0);
|
|
||||||
|
|
||||||
// Write a return instruction, just
|
|
||||||
// in case it didn't already return.
|
|
||||||
emitInstr_RETURN(ctx, 0, func->body->offset, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the first index after the function code.
|
|
||||||
setLabelHere(jump_index, ctx);
|
|
||||||
|
|
||||||
freeLabel(func_index);
|
|
||||||
freeLabel(jump_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstrForIfElseNode(CodegenContext *ctx, IfElseNode *ifelse, Promise *break_dest)
|
|
||||||
{
|
|
||||||
emitInstrForNode(ctx, ifelse->condition, break_dest);
|
|
||||||
if(ifelse->false_branch)
|
|
||||||
{
|
|
||||||
Label *else_offset = newLabel(ctx);
|
|
||||||
Label *done_offset = newLabel(ctx);
|
|
||||||
emitInstr_JUMPIFNOTANDPOP(ctx, else_offset, ifelse->base.offset, ifelse->base.length);
|
|
||||||
emitInstrForNode(ctx, ifelse->true_branch, break_dest);
|
|
||||||
if(ifelse->true_branch->kind == NODE_EXPR)
|
|
||||||
emitInstr_POP(ctx, 1, ifelse->true_branch->offset, 0);
|
|
||||||
emitInstr_JUMP(ctx, done_offset, ifelse->base.offset, ifelse->base.length);
|
|
||||||
setLabelHere(else_offset, ctx);
|
|
||||||
emitInstrForNode(ctx, ifelse->false_branch, break_dest);
|
|
||||||
if(ifelse->false_branch->kind == NODE_EXPR)
|
|
||||||
emitInstr_POP1(ctx, ifelse->false_branch->offset, 0);
|
|
||||||
setLabelHere(done_offset, ctx);
|
|
||||||
freeLabel(else_offset);
|
|
||||||
freeLabel(done_offset);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Label *done_offset = newLabel(ctx);
|
|
||||||
emitInstr_JUMPIFNOTANDPOP(ctx, done_offset, ifelse->base.offset, ifelse->base.length);
|
|
||||||
emitInstrForNode(ctx, ifelse->true_branch, break_dest);
|
|
||||||
if(ifelse->true_branch->kind == NODE_EXPR)
|
|
||||||
emitInstr_POP1(ctx, ifelse->true_branch->offset, 0);
|
|
||||||
setLabelHere(done_offset, ctx);
|
|
||||||
freeLabel(done_offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void flattenTupleTree(CodegenContext *ctx, ExprNode *root, ExprNode *tuple[], int max, int *count)
|
|
||||||
{
|
|
||||||
if(root->kind == EXPR_PAIR)
|
|
||||||
{
|
|
||||||
flattenTupleTree(ctx, (ExprNode*) ((OperExprNode*) root)->head, tuple, max, count);
|
|
||||||
flattenTupleTree(ctx, (ExprNode*) ((OperExprNode*) root)->head->next, tuple, max, count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
if(max == *count)
|
|
||||||
{
|
|
||||||
reportErrorAndJump(ctx, 0, "Static buffer is too small");
|
|
||||||
UNREACHABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
tuple[(*count)++] = root;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstrForAssignmentNode(CodegenContext *ctx, OperExprNode *asgn, Promise *break_dest)
|
|
||||||
{
|
|
||||||
Node *lop, *rop;
|
|
||||||
lop = asgn->head;
|
|
||||||
rop = lop->next;
|
|
||||||
|
|
||||||
ExprNode *tuple[32];
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
flattenTupleTree(ctx, (ExprNode*) lop, tuple, sizeof(tuple)/sizeof(tuple[0]), &count);
|
|
||||||
|
|
||||||
assert(count > 0);
|
|
||||||
|
|
||||||
if(count == 1) /* No tuple. */
|
|
||||||
emitInstrForNode(ctx, rop, break_dest);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(((ExprNode*) rop)->kind == EXPR_CALL)
|
|
||||||
emitInstrForFuncCallNode(ctx, (CallExprNode*) rop, break_dest, count);
|
|
||||||
else {
|
|
||||||
reportErrorAndJump(ctx, 0, "Assigning to %d variables only 1 value", count);
|
|
||||||
UNREACHABLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < count; i += 1)
|
|
||||||
{
|
|
||||||
ExprNode *tuple_item = tuple[count-i-1];
|
|
||||||
switch(tuple_item->kind)
|
|
||||||
{
|
|
||||||
case EXPR_IDENT:
|
|
||||||
{
|
|
||||||
const char *name = ((IdentExprNode*) tuple_item)->val;
|
|
||||||
emitInstr_ASS(ctx, name, tuple_item->base.offset, tuple_item->base.length);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_SELECT:
|
|
||||||
{
|
|
||||||
Node *idx = ((IndexSelectionExprNode*) tuple_item)->idx;
|
|
||||||
Node *set = ((IndexSelectionExprNode*) tuple_item)->set;
|
|
||||||
emitInstrForNode(ctx, set, break_dest);
|
|
||||||
emitInstrForNode(ctx, idx, break_dest);
|
|
||||||
emitInstr(ctx, OPCODE_INSERT2, NULL, 0, tuple_item->base.offset, tuple_item->base.length);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
reportErrorAndJump(ctx, 0, "Assigning to something that it can't be assigned to");
|
|
||||||
UNREACHABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i+1 < count)
|
|
||||||
emitInstr_POP1(ctx, asgn->base.base.offset, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitInstrForNode(CodegenContext *ctx, Node *node, Promise *break_dest)
|
|
||||||
{
|
|
||||||
assert(node != NULL);
|
|
||||||
|
|
||||||
switch(node->kind)
|
|
||||||
{
|
|
||||||
case NODE_EXPR:
|
|
||||||
{
|
|
||||||
ExprNode *expr = (ExprNode*) node;
|
|
||||||
switch(expr->kind)
|
|
||||||
{
|
|
||||||
case EXPR_PAIR:
|
|
||||||
reportErrorAndJump(ctx, 0, "Tuple outside of assignment or return statement");
|
|
||||||
UNREACHABLE;
|
|
||||||
return; // For the compiler warning.
|
|
||||||
|
|
||||||
case EXPR_NOT:
|
|
||||||
case EXPR_POS:
|
|
||||||
case EXPR_NEG:
|
|
||||||
case EXPR_ADD:
|
|
||||||
case EXPR_SUB:
|
|
||||||
case EXPR_MUL:
|
|
||||||
case EXPR_DIV:
|
|
||||||
case EXPR_EQL:
|
|
||||||
case EXPR_NQL:
|
|
||||||
case EXPR_LSS:
|
|
||||||
case EXPR_LEQ:
|
|
||||||
case EXPR_GRT:
|
|
||||||
case EXPR_GEQ:
|
|
||||||
case EXPR_AND:
|
|
||||||
case EXPR_OR:
|
|
||||||
{
|
|
||||||
OperExprNode *oper = (OperExprNode*) expr;
|
|
||||||
for(Node *operand = oper->head; operand; operand = operand->next)
|
|
||||||
emitInstrForNode(ctx, operand, break_dest);
|
|
||||||
emitInstr(ctx, exprkind_to_opcode(expr->kind), NULL, 0, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_ASS:
|
|
||||||
emitInstrForAssignmentNode(ctx, (OperExprNode*) expr, break_dest);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case EXPR_INT:
|
|
||||||
{
|
|
||||||
IntExprNode *p = (IntExprNode*) expr;
|
|
||||||
Operand op = { .type = OPTP_INT, .as_int = p->val };
|
|
||||||
emitInstr(ctx, OPCODE_PUSHINT, &op, 1, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_FLOAT:
|
|
||||||
{
|
|
||||||
FloatExprNode *p = (FloatExprNode*) expr;
|
|
||||||
Operand op = { .type = OPTP_FLOAT, .as_float = p->val };
|
|
||||||
emitInstr(ctx, OPCODE_PUSHFLT, &op, 1, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_STRING:
|
|
||||||
{
|
|
||||||
StringExprNode *p = (StringExprNode*) expr;
|
|
||||||
Operand op = { .type = OPTP_STRING, .as_string = p->val };
|
|
||||||
emitInstr(ctx, OPCODE_PUSHSTR, &op, 1, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_IDENT:
|
|
||||||
{
|
|
||||||
IdentExprNode *p = (IdentExprNode*) expr;
|
|
||||||
Operand op = { .type = OPTP_STRING, .as_string = p->val };
|
|
||||||
emitInstr(ctx, OPCODE_PUSHVAR, &op, 1, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_LIST:
|
|
||||||
{
|
|
||||||
// PUSHLST
|
|
||||||
// PUSHINT
|
|
||||||
// <expr>
|
|
||||||
// INSERT
|
|
||||||
|
|
||||||
ListExprNode *l = (ListExprNode*) node;
|
|
||||||
|
|
||||||
Operand op;
|
|
||||||
|
|
||||||
op = (Operand) { .type = OPTP_INT, .as_int = l->itemc };
|
|
||||||
emitInstr(ctx, OPCODE_PUSHLST, &op, 1, node->offset, node->length);
|
|
||||||
|
|
||||||
Node *item = l->items;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
while(item)
|
|
||||||
{
|
|
||||||
op = (Operand) { .type = OPTP_INT, .as_int = i };
|
|
||||||
emitInstr(ctx, OPCODE_PUSHINT, &op, 1, item->offset, item->length);
|
|
||||||
emitInstrForNode(ctx, item, break_dest);
|
|
||||||
emitInstr(ctx, OPCODE_INSERT, NULL, 0, item->offset, item->length);
|
|
||||||
i += 1;
|
|
||||||
item = item->next;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_MAP:
|
|
||||||
{
|
|
||||||
MapExprNode *m = (MapExprNode*) node;
|
|
||||||
|
|
||||||
Operand op;
|
|
||||||
|
|
||||||
op = (Operand) { .type = OPTP_INT, .as_int = m->itemc };
|
|
||||||
emitInstr(ctx, OPCODE_PUSHMAP, &op, 1, node->offset, node->length);
|
|
||||||
|
|
||||||
Node *key = m->keys;
|
|
||||||
Node *item = m->items;
|
|
||||||
|
|
||||||
while(item)
|
|
||||||
{
|
|
||||||
emitInstrForNode(ctx, key, break_dest);
|
|
||||||
emitInstrForNode(ctx, item, break_dest);
|
|
||||||
emitInstr(ctx, OPCODE_INSERT, NULL, 0, item->offset, item->length);
|
|
||||||
key = key->next;
|
|
||||||
item = item->next;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_CALL:
|
|
||||||
emitInstrForFuncCallNode(ctx, (CallExprNode*) expr, break_dest, 1);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case EXPR_SELECT:
|
|
||||||
{
|
|
||||||
IndexSelectionExprNode *sel = (IndexSelectionExprNode*) expr;
|
|
||||||
emitInstrForNode(ctx, sel->set, break_dest);
|
|
||||||
emitInstrForNode(ctx, sel->idx, break_dest);
|
|
||||||
emitInstr(ctx, OPCODE_SELECT, NULL, 0, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPR_NONE:
|
|
||||||
emitInstr(ctx, OPCODE_PUSHNNE, NULL, 0, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case EXPR_TRUE:
|
|
||||||
emitInstr(ctx, OPCODE_PUSHTRU, NULL, 0, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case EXPR_FALSE:
|
|
||||||
emitInstr(ctx, OPCODE_PUSHFLS, NULL, 0, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UNREACHABLE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NODE_BREAK:
|
|
||||||
if(break_dest == NULL)
|
|
||||||
reportErrorAndJump(ctx, 0, "Break not inside a loop");
|
|
||||||
emitInstr_JUMP(ctx, break_dest, node->offset, node->length);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case NODE_IFELSE:
|
|
||||||
emitInstrForIfElseNode(ctx, (IfElseNode*) node, break_dest);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case NODE_WHILE:
|
|
||||||
{
|
|
||||||
WhileNode *whl = (WhileNode*) node;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* start:
|
|
||||||
* <condition>
|
|
||||||
* JUMPIFNOTANDPOP end
|
|
||||||
* <body>
|
|
||||||
* JUMP start
|
|
||||||
* end:
|
|
||||||
*/
|
|
||||||
|
|
||||||
Label *start_offset = newLabel(ctx);
|
|
||||||
Label *end_offset = newLabel(ctx);
|
|
||||||
setLabelHere(start_offset, ctx);
|
|
||||||
emitInstrForNode(ctx, whl->condition, break_dest);
|
|
||||||
emitInstr_JUMPIFNOTANDPOP(ctx, end_offset, whl->condition->offset, whl->condition->length);
|
|
||||||
emitInstrForNode(ctx, whl->body, end_offset);
|
|
||||||
if(whl->body->kind == NODE_EXPR)
|
|
||||||
emitInstr_POP1(ctx, whl->body->offset, 0);
|
|
||||||
emitInstr_JUMP(ctx, start_offset, node->offset, node->length);
|
|
||||||
setLabelHere(end_offset, ctx);
|
|
||||||
freeLabel(start_offset);
|
|
||||||
freeLabel(end_offset);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NODE_DOWHILE:
|
|
||||||
{
|
|
||||||
DoWhileNode *dowhl = (DoWhileNode*) node;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* start:
|
|
||||||
* <body>
|
|
||||||
* <condition>
|
|
||||||
* JUMPIFANDPOP start
|
|
||||||
*/
|
|
||||||
|
|
||||||
Label *end_offset = newLabel(ctx);
|
|
||||||
long long int start = ExeBuilder_InstrCount(ctx->builder);
|
|
||||||
emitInstrForNode(ctx, dowhl->body, end_offset);
|
|
||||||
if(dowhl->body->kind == NODE_EXPR)
|
|
||||||
emitInstr_POP1(ctx, dowhl->body->offset, 0);
|
|
||||||
emitInstrForNode(ctx, dowhl->condition, break_dest);
|
|
||||||
emitInstr_JUMPIFANDPOP(ctx, start, dowhl->condition->offset, dowhl->condition->length);
|
|
||||||
setLabelHere(end_offset, ctx);
|
|
||||||
freeLabel(end_offset);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NODE_COMP:
|
|
||||||
{
|
|
||||||
CompoundNode *comp = (CompoundNode*) node;
|
|
||||||
|
|
||||||
Node *stmt = comp->head;
|
|
||||||
|
|
||||||
while(stmt)
|
|
||||||
{
|
|
||||||
emitInstrForNode(ctx, stmt, break_dest);
|
|
||||||
if(stmt->kind == NODE_EXPR)
|
|
||||||
emitInstr_POP1(ctx, stmt->offset, 0);
|
|
||||||
stmt = stmt->next;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NODE_RETURN:
|
|
||||||
{
|
|
||||||
ReturnNode *ret = (ReturnNode*) node;
|
|
||||||
|
|
||||||
ExprNode *tuple[32];
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
flattenTupleTree(ctx, (ExprNode*) ret->val, tuple, sizeof(tuple)/sizeof(tuple[0]), &count);
|
|
||||||
|
|
||||||
for(int i = 0; i < count; i += 1)
|
|
||||||
emitInstrForNode(ctx, (Node*) tuple[i], break_dest);
|
|
||||||
emitInstr_RETURN(ctx, count, ret->base.offset, ret->base.length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NODE_FUNC:
|
|
||||||
emitInstrForFuncNode(ctx, (FunctionNode*) node, break_dest);
|
|
||||||
return;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UNREACHABLE;
|
|
||||||
}
|
|
||||||
UNREACHABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Executable *makeExecutableAndFreeCodegenContext(CodegenContext *ctx, Source *src)
|
|
||||||
{
|
|
||||||
Executable *exe = ExeBuilder_Finalize(ctx->builder, ctx->error);
|
|
||||||
if(exe == NULL)
|
if(exe == NULL)
|
||||||
okNowJump(ctx);
|
{
|
||||||
|
assert(error->occurred);
|
||||||
|
if(error->internal)
|
||||||
|
*errtyp = CompilationErrorType_INTERNAL;
|
||||||
|
else
|
||||||
|
*errtyp = CompilationErrorType_SEMANTIC;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
Executable_SetSource(exe, src);
|
|
||||||
freeCodegenContext(ctx);
|
|
||||||
return exe;
|
return exe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Symbol: compile
|
|
||||||
*
|
|
||||||
* Serializes an AST into bytecode format.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Arguments:
|
|
||||||
*
|
|
||||||
* ast: The AST to be serialized.
|
|
||||||
* alloc: The allocator that will be used to get new
|
|
||||||
* memory. (optional)
|
|
||||||
* error: Error information structure that is filled out if
|
|
||||||
* an error occurres.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* A pointer to an `Executable` that is the object that
|
|
||||||
* contains the bytecode. If an error occurres, NULL is
|
|
||||||
* returned and the `error` structure is filled out.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
Executable *compile(AST *ast, BPAlloc *alloc, Error *error)
|
|
||||||
{
|
|
||||||
assert(ast != NULL);
|
|
||||||
assert(error != NULL);
|
|
||||||
|
|
||||||
CodegenContext ctx;
|
|
||||||
if(!initCodegenContext(&ctx, error, alloc))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
bool jumped = setOrcatchJump(&ctx);
|
|
||||||
if(jumped) {
|
|
||||||
freeCodegenContext(&ctx);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitInstrForNode(&ctx, ast->root, NULL);
|
|
||||||
emitInstr_RETURN(&ctx, 0, Source_GetSize(ast->src), 0);
|
|
||||||
return makeExecutableAndFreeCodegenContext(&ctx, ast->src);
|
|
||||||
}
|
|
||||||
@@ -1,38 +1,12 @@
|
|||||||
|
|
||||||
/* +--------------------------------------------------------------------------+
|
|
||||||
** | _ _ _ |
|
|
||||||
** | | \ | | (_) |
|
|
||||||
** | | \| | ___ _ __ _ |
|
|
||||||
** | | . ` |/ _ \| |/ _` | |
|
|
||||||
** | | |\ | (_) | | (_| | |
|
|
||||||
** | |_| \_|\___/| |\__,_| |
|
|
||||||
** | _/ | |
|
|
||||||
** | |__/ |
|
|
||||||
** +--------------------------------------------------------------------------+
|
|
||||||
** | Copyright (c) 2022 Francesco Cozzuto <francesco.cozzuto@gmail.com> |
|
|
||||||
** +--------------------------------------------------------------------------+
|
|
||||||
** | This file is part of The Noja Interpreter. |
|
|
||||||
** | |
|
|
||||||
** | The Noja Interpreter is free software: you can redistribute it and/or |
|
|
||||||
** | modify it under the terms of the GNU General Public License as published |
|
|
||||||
** | by the Free Software Foundation, either version 3 of the License, or (at |
|
|
||||||
** | your option) any later version. |
|
|
||||||
** | |
|
|
||||||
** | The Noja Interpreter is distributed in the hope that it will be useful, |
|
|
||||||
** | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
||||||
** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
|
||||||
** | Public License for more details. |
|
|
||||||
** | |
|
|
||||||
** | You should have received a copy of the GNU General Public License along |
|
|
||||||
** | with The Noja Interpreter. If not, see <http://www.gnu.org/licenses/>. |
|
|
||||||
** +--------------------------------------------------------------------------+
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COMPILE_H
|
#ifndef COMPILE_H
|
||||||
#define COMPILE_H
|
#define COMPILE_H
|
||||||
#include "../utils/error.h"
|
#include "../utils/error.h"
|
||||||
#include "../utils/bpalloc.h"
|
#include "../utils/source.h"
|
||||||
#include "../common/executable.h"
|
#include "../common/executable.h"
|
||||||
#include "AST.h"
|
typedef enum {
|
||||||
Executable *compile(AST *ast, BPAlloc *alloc, Error *error);
|
CompilationErrorType_SYNTAX,
|
||||||
#endif
|
CompilationErrorType_SEMANTIC,
|
||||||
|
CompilationErrorType_INTERNAL,
|
||||||
|
} CompilationErrorType;
|
||||||
|
Executable *compile(Source *src, Error *error, CompilationErrorType *errtyp);
|
||||||
|
#endif /* COMPILE_H */
|
||||||
+28
-60
@@ -34,52 +34,6 @@ static void print_error(const char *type, Error *error)
|
|||||||
fprintf(stderr, "\n");
|
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(Executable *exe)
|
static _Bool interpret(Executable *exe)
|
||||||
{
|
{
|
||||||
Runtime *runt = Runtime_New(-1, 1024*1024, NULL, NULL);
|
Runtime *runt = Runtime_New(-1, 1024*1024, NULL, NULL);
|
||||||
@@ -140,13 +94,32 @@ static _Bool interpret(Executable *exe)
|
|||||||
return retc > -1;
|
return retc > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Executable *compile_source_and_print_error_on_failure(Source *src)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
Error_Init(&error);
|
||||||
|
CompilationErrorType errtyp;
|
||||||
|
Executable *exe = compile(src, &error, &errtyp);
|
||||||
|
if(exe == NULL) {
|
||||||
|
const char *errname;
|
||||||
|
switch(errtyp) {
|
||||||
|
default:
|
||||||
|
case CompilationErrorType_INTERNAL: errname = NULL; break;
|
||||||
|
case CompilationErrorType_SYNTAX: errname = "Syntax"; break;
|
||||||
|
case CompilationErrorType_SEMANTIC: errname = "Semantic"; break;
|
||||||
|
}
|
||||||
|
print_error(errname, &error);
|
||||||
|
Error_Free(&error);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Error_Free(&error);
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
static _Bool disassemble(Source *src)
|
static _Bool disassemble(Source *src)
|
||||||
{
|
{
|
||||||
Executable *exe = build(src);
|
Executable *exe = compile_source_and_print_error_on_failure(src);
|
||||||
|
if(exe == NULL) return 0;
|
||||||
if(exe == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
Executable_Dump(exe);
|
Executable_Dump(exe);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -157,7 +130,6 @@ static _Bool interpret_file(const char *file)
|
|||||||
Error_Init(&error);
|
Error_Init(&error);
|
||||||
|
|
||||||
Source *src = Source_FromFile(file, &error);
|
Source *src = Source_FromFile(file, &error);
|
||||||
|
|
||||||
if(src == NULL)
|
if(src == NULL)
|
||||||
{
|
{
|
||||||
assert(error.occurred == 1);
|
assert(error.occurred == 1);
|
||||||
@@ -166,10 +138,8 @@ static _Bool interpret_file(const char *file)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Executable *exe = build(src);
|
Executable *exe = compile_source_and_print_error_on_failure(src);
|
||||||
|
if(exe == NULL) return 0;
|
||||||
if(exe == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
_Bool r = interpret(exe);
|
_Bool r = interpret(exe);
|
||||||
|
|
||||||
@@ -193,10 +163,8 @@ static _Bool interpret_code(const char *code)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Executable *exe = build(src);
|
Executable *exe = compile_source_and_print_error_on_failure(src);
|
||||||
|
if(exe == NULL) return 0;
|
||||||
if(exe == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
_Bool r = interpret(exe);
|
_Bool r = interpret(exe);
|
||||||
|
|
||||||
|
|||||||
+14
-39
@@ -5,7 +5,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "../lib/utils/source.h"
|
#include "../lib/utils/source.h"
|
||||||
#include "../lib/compiler/parse.h"
|
|
||||||
#include "../lib/compiler/compile.h"
|
#include "../lib/compiler/compile.h"
|
||||||
#include "../lib/common/executable.h"
|
#include "../lib/common/executable.h"
|
||||||
#include "../lib/assembler/assemble.h"
|
#include "../lib/assembler/assemble.h"
|
||||||
@@ -328,49 +327,24 @@ static bool parseTestCaseSource(Source *source, TestCase *testcase)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Executable *build(Source *src)
|
static Executable *compile_source_and_print_error_on_failure(Source *src)
|
||||||
{
|
{
|
||||||
Executable *exe;
|
|
||||||
|
|
||||||
// Create a bump-pointer allocator to hold the AST.
|
|
||||||
BPAlloc *alloc = BPAlloc_Init(-1);
|
|
||||||
|
|
||||||
if(alloc == NULL)
|
|
||||||
{
|
|
||||||
fprintf(stderr, RED "Internal Error" CRESET " :: Couldn't allocate bump-pointer allocator to hold the AST.\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
Error_Init(&error);
|
Error_Init(&error);
|
||||||
|
CompilationErrorType errtyp;
|
||||||
// NOTE: The AST is stored in the BPAlloc. It's
|
Executable *exe = compile(src, &error, &errtyp);
|
||||||
// lifetime is the same as the pool.
|
if(exe == NULL) {
|
||||||
AST *ast = parse(src, alloc, &error);
|
const char *errname;
|
||||||
|
switch(errtyp) {
|
||||||
if(ast == NULL)
|
default:
|
||||||
{
|
case CompilationErrorType_INTERNAL: errname = NULL; break;
|
||||||
assert(error.occurred);
|
case CompilationErrorType_SYNTAX: errname = "Syntax"; break;
|
||||||
print_error("Parsing", &error);
|
case CompilationErrorType_SEMANTIC: errname = "Semantic"; break;
|
||||||
Error_Free(&error);
|
|
||||||
BPAlloc_Free(alloc);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
print_error(errname, &error);
|
||||||
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);
|
Error_Free(&error);
|
||||||
return 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error_Free(&error);
|
Error_Free(&error);
|
||||||
return exe;
|
return exe;
|
||||||
}
|
}
|
||||||
@@ -407,7 +381,7 @@ static TestResult runTest(const char *file)
|
|||||||
|
|
||||||
Source_Free(source);
|
Source_Free(source);
|
||||||
|
|
||||||
Executable *exe1 = build(testcase.source);
|
Executable *exe1 = compile_source_and_print_error_on_failure(testcase.source);
|
||||||
if(exe1 == NULL) {
|
if(exe1 == NULL) {
|
||||||
Source_Free(testcase.source);
|
Source_Free(testcase.source);
|
||||||
Source_Free(testcase.bytecode);
|
Source_Free(testcase.bytecode);
|
||||||
@@ -536,6 +510,7 @@ static void runTestSuite(const char *folder)
|
|||||||
fprintf(stdout, "| Passed : %-3ld |\n", passed_tests);
|
fprintf(stdout, "| Passed : %-3ld |\n", passed_tests);
|
||||||
fprintf(stdout, "| Failed : %-3ld |\n", failed_tests);
|
fprintf(stdout, "| Failed : %-3ld |\n", failed_tests);
|
||||||
fprintf(stdout, "| Aborted : %-3ld |\n", aborted_tests);
|
fprintf(stdout, "| Aborted : %-3ld |\n", aborted_tests);
|
||||||
|
fprintf(stdout, "| Skipped : %-3ld |\n", skipped_files);
|
||||||
fprintf(stdout, "@-----------------@\n");
|
fprintf(stdout, "@-----------------@\n");
|
||||||
|
|
||||||
closedir(handle);
|
closedir(handle);
|
||||||
|
|||||||
Reference in New Issue
Block a user