448 lines
14 KiB
C
448 lines
14 KiB
C
// The MIT License (MIT)
|
|
//
|
|
// Copyright (C) 2026 Scott Duensing
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
|
|
|
|
// genExpr.c -- DVX BASIC generator: expressions and references
|
|
|
|
#include "front.h"
|
|
#include "thirdparty/stb_ds_wrap.h"
|
|
|
|
|
|
// Function prototypes (alphabetical)
|
|
uint8_t basBinaryOpcode(uint8_t tok);
|
|
void basGenArgs(BasCompilerT *c, const BasNodeT *list);
|
|
void basGenExpr(BasCompilerT *c, const BasNodeT *n);
|
|
void basGenObjMember(BasCompilerT *c, uint8_t op, const BasCallT *call);
|
|
void basGenRef(BasCompilerT *c, const BasNodeT *n);
|
|
static uint8_t nullGuardMask(const BasNodeT *args, uint8_t mask);
|
|
|
|
|
|
// The VM opcode of a binary operator token.
|
|
uint8_t basBinaryOpcode(uint8_t tok) {
|
|
switch (tok) {
|
|
case TOK_PLUS: return OP_ADD_INT;
|
|
case TOK_MINUS: return OP_SUB_INT;
|
|
case TOK_STAR: return OP_MUL_INT;
|
|
case TOK_SLASH: return OP_DIV_FLT;
|
|
case TOK_BACKSLASH: return OP_IDIV_INT;
|
|
case TOK_MOD: return OP_MOD_INT;
|
|
case TOK_CARET: return OP_POW;
|
|
case TOK_AMPERSAND: return OP_STR_CONCAT;
|
|
case TOK_EQ: return OP_CMP_EQ;
|
|
case TOK_NE: return OP_CMP_NE;
|
|
case TOK_LT: return OP_CMP_LT;
|
|
case TOK_GT: return OP_CMP_GT;
|
|
case TOK_LE: return OP_CMP_LE;
|
|
case TOK_GE: return OP_CMP_GE;
|
|
case TOK_IS: return OP_IS;
|
|
case TOK_AND: return OP_AND;
|
|
case TOK_OR: return OP_OR;
|
|
case TOK_XOR: return OP_XOR;
|
|
case TOK_EQV: return OP_EQV;
|
|
default: return OP_IMP;
|
|
}
|
|
}
|
|
|
|
|
|
// Pushes call arguments in order (a reference for an NK_REF).
|
|
void basGenArgs(BasCompilerT *c, const BasNodeT *list) {
|
|
for (const BasNodeT *a = list; a != NULL; a = a->next) {
|
|
basGenExpr(c, a);
|
|
}
|
|
}
|
|
|
|
|
|
// Pushes the value of expression n.
|
|
void basGenExpr(BasCompilerT *c, const BasNodeT *n) {
|
|
if (n == NULL || c->hasError) {
|
|
return;
|
|
}
|
|
|
|
switch (n->kind) {
|
|
case NK_INT: {
|
|
const BasLitT *lit = (const BasLitT *)n;
|
|
|
|
if (n->type == BAS_TYPE_LONG) {
|
|
basEmit8(&c->cg, OP_PUSH_INT32);
|
|
basEmit32(&c->cg, lit->ival);
|
|
} else {
|
|
basGenPushInt16(c, (int16_t)lit->ival);
|
|
}
|
|
|
|
// A BYTE literal (a folded value or constant) is the INTEGER
|
|
// made a BYTE
|
|
if (n->type == BAS_TYPE_BYTE) {
|
|
basEmit8(&c->cg, OP_CONV_BYTE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case NK_DBL: {
|
|
const BasLitT *lit = (const BasLitT *)n;
|
|
|
|
if (n->type == BAS_TYPE_CURRENCY) {
|
|
basEmit8(&c->cg, OP_PUSH_CUR);
|
|
basEmitI64(&c->cg, lit->cval);
|
|
break;
|
|
}
|
|
|
|
basEmit8(&c->cg, OP_PUSH_FLT64);
|
|
basEmitDouble(&c->cg, lit->dval);
|
|
|
|
if (n->type == BAS_TYPE_SINGLE) {
|
|
basEmit8(&c->cg, OP_CONV_INT_FLT);
|
|
} else if (n->type == BAS_TYPE_DATE) {
|
|
basEmit8(&c->cg, OP_CONV_DATE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case NK_STR: {
|
|
const BasLitT *lit = (const BasLitT *)n;
|
|
|
|
basGenPushStr(c, lit->str, lit->slen);
|
|
break;
|
|
}
|
|
|
|
case NK_BOOL:
|
|
basEmit8(&c->cg, ((const BasLitT *)n)->ival ? OP_PUSH_TRUE : OP_PUSH_FALSE);
|
|
break;
|
|
|
|
case NK_SPECIAL:
|
|
basGenPushSpecial(c, (uint8_t)((const BasLitT *)n)->ival);
|
|
break;
|
|
|
|
case NK_VAR:
|
|
basGenLoadVar(c, ((const BasNameT *)n)->sym);
|
|
break;
|
|
|
|
case NK_RESULT:
|
|
basEmit8(&c->cg, OP_LOAD_LOCAL);
|
|
basEmitU16(&c->cg, BAS_FUNC_RESULT_SLOT);
|
|
break;
|
|
|
|
case NK_ME:
|
|
basEmit8(&c->cg, (uint8_t)((const BasNameT *)n)->aux);
|
|
break;
|
|
|
|
case NK_LOADFORM:
|
|
basGenPushCStr(c, ((const BasNameT *)n)->name);
|
|
basEmit8(&c->cg, OP_LOAD_FORM);
|
|
break;
|
|
|
|
case NK_NEW:
|
|
basEmit8(&c->cg, OP_NEW_OBJ);
|
|
basEmitU16(&c->cg, (uint16_t)((const BasNameT *)n)->aux);
|
|
break;
|
|
|
|
case NK_ELEM:
|
|
case NK_INDEX: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
|
|
basGenExpr(c, call->callee);
|
|
basGenArgs(c, call->args);
|
|
basEmit8(&c->cg, OP_LOAD_ARRAY);
|
|
basEmit8(&c->cg, (uint8_t)call->argc);
|
|
break;
|
|
}
|
|
|
|
case NK_FIELD: {
|
|
const BasMemberT *m = (const BasMemberT *)n;
|
|
|
|
basGenExpr(c, m->base);
|
|
basEmit8(&c->cg, OP_LOAD_FIELD);
|
|
basEmitU16(&c->cg, (uint16_t)m->aux);
|
|
break;
|
|
}
|
|
|
|
case NK_FCALL: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
|
|
basGenArgs(c, call->args);
|
|
basGenCall(c, (BasSymT *)call->target, call->argc);
|
|
break;
|
|
}
|
|
|
|
case NK_BUILTIN: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
const BasBuiltinT *b = (const BasBuiltinT *)call->target;
|
|
|
|
basGenArgs(c, call->args);
|
|
|
|
// A Variant form given something that may be Null: OP_NULL_PROP
|
|
// gives Null and skips the function
|
|
uint8_t guard = nullGuardMask(call->args, b->nullMask);
|
|
|
|
if (guard != 0) {
|
|
basEmit8(&c->cg, OP_NULL_PROP);
|
|
basEmit8(&c->cg, (uint8_t)call->argc);
|
|
basEmit8(&c->cg, guard);
|
|
}
|
|
|
|
basEmit8(&c->cg, (uint8_t)call->aux);
|
|
|
|
if (call->aux == OP_BUILTIN) {
|
|
basEmit8(&c->cg, b->func);
|
|
basEmit8(&c->cg, (uint8_t)call->argc);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case NK_INTRINSIC: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
|
|
basGenArgs(c, call->args);
|
|
basEmit8(&c->cg, (uint8_t)call->aux);
|
|
|
|
if (call->aux2 != BAS_NO_OPERAND) {
|
|
basEmit8(&c->cg, (uint8_t)call->aux2);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case NK_METHOD: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
|
|
// The names of arguments given by name follow them (basLateArgs)
|
|
basGenExpr(c, call->callee);
|
|
basGenPushCStr(c, (const char *)call->target);
|
|
basGenArgs(c, call->args);
|
|
basEmit8(&c->cg, (call->aux2 > 0) ? OP_CALL_NAMED : OP_CALL_METHOD);
|
|
basEmit8(&c->cg, (uint8_t)call->argc);
|
|
|
|
if (call->aux2 > 0) {
|
|
basEmit8(&c->cg, (uint8_t)call->aux2);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case NK_PROP: {
|
|
const BasMemberT *m = (const BasMemberT *)n;
|
|
|
|
basGenExpr(c, m->base);
|
|
basGenPushCStr(c, m->name);
|
|
basEmit8(&c->cg, OP_LOAD_PROP);
|
|
break;
|
|
}
|
|
|
|
case NK_OBJMEMBER: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
|
|
basGenExpr(c, call->callee);
|
|
basGenArgs(c, call->args);
|
|
basGenObjMember(c, (n->flags & NF_STATEMENT) ? OP_OBJ_CALL : OP_OBJ_GET, call);
|
|
break;
|
|
}
|
|
|
|
case NK_FINDCTRL: {
|
|
const BasMemberT *m = (const BasMemberT *)n;
|
|
|
|
if (m->base != NULL) {
|
|
basGenExpr(c, m->base);
|
|
} else {
|
|
// NULL form reference: the current form
|
|
basGenPushInt16(c, 0);
|
|
}
|
|
|
|
basGenPushCStr(c, m->name);
|
|
basEmit8(&c->cg, OP_FIND_CTRL);
|
|
break;
|
|
}
|
|
|
|
case NK_FINDCTRLIDX: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
|
|
if (call->callee != NULL) {
|
|
basGenExpr(c, call->callee);
|
|
} else {
|
|
basGenPushInt16(c, 0);
|
|
}
|
|
|
|
basGenPushCStr(c, (const char *)call->target);
|
|
basGenExpr(c, call->args);
|
|
basEmit8(&c->cg, OP_FIND_CTRL_IDX);
|
|
break;
|
|
}
|
|
|
|
case NK_TYPEOF: {
|
|
const BasMemberT *t = (const BasMemberT *)n;
|
|
|
|
basGenExpr(c, t->base);
|
|
basGenPushCStr(c, t->name);
|
|
basEmit8(&c->cg, OP_BUILTIN);
|
|
basEmit8(&c->cg, BAS_FN_TYPEOF);
|
|
basEmit8(&c->cg, BAS_TYPEOF_ARGS);
|
|
break;
|
|
}
|
|
|
|
case NK_UNARY: {
|
|
const BasOpT *op = (const BasOpT *)n;
|
|
|
|
basGenExpr(c, op->a);
|
|
basEmit8(&c->cg, (op->op == TOK_MINUS) ? OP_NEG_INT : OP_NOT);
|
|
break;
|
|
}
|
|
|
|
case NK_BINARY: {
|
|
const BasOpT *op = (const BasOpT *)n;
|
|
uint8_t opcode = basBinaryOpcode(op->op);
|
|
uint8_t k16 = basK16Pair(opcode, true);
|
|
|
|
basGenExpr(c, op->a);
|
|
|
|
// An INTEGER literal right operand goes into the instruction
|
|
if (k16 != OP_NOP && op->b->kind == NK_INT && op->b->type == BAS_TYPE_INTEGER) {
|
|
basEmit8(&c->cg, k16);
|
|
basEmit16(&c->cg, (int16_t)((const BasLitT *)op->b)->ival);
|
|
break;
|
|
}
|
|
|
|
basGenExpr(c, op->b);
|
|
basEmit8(&c->cg, opcode);
|
|
break;
|
|
}
|
|
|
|
case NK_CONV: {
|
|
const BasOpT *op = (const BasOpT *)n;
|
|
|
|
basGenExpr(c, op->a);
|
|
basEmit8(&c->cg, op->op);
|
|
|
|
if (op->op == OP_STR_FIXLEN || op->op == OP_OBJ_CHECK) {
|
|
basEmitU16(&c->cg, (uint16_t)op->aux);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case NK_REF:
|
|
basGenRef(c, ((const BasOpT *)n)->a);
|
|
break;
|
|
|
|
case NK_PARAMARRAY: {
|
|
const BasCallT *pack = (const BasCallT *)n;
|
|
|
|
basGenPushInt16(c, (int16_t)pack->aux);
|
|
basGenArgs(c, pack->args);
|
|
basEmit8(&c->cg, OP_BUILTIN);
|
|
basEmit8(&c->cg, BAS_FN_PARAMARRAY);
|
|
basEmit8(&c->cg, (uint8_t)(pack->argc + 1));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
basErrorNode(c, n, "Internal error: expression kind %d", (int)n->kind);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// An early-bound member instruction op for call (its object, arguments
|
|
// and, a put, the value pushed): the class and member operands (a class
|
|
// member's index is filled in when every class is compiled) and the
|
|
// argument count.
|
|
void basGenObjMember(BasCompilerT *c, uint8_t op, const BasCallT *call) {
|
|
basEmit8(&c->cg, op);
|
|
basEmitU16(&c->cg, (uint16_t)call->aux);
|
|
|
|
if (call->target != NULL) {
|
|
BasMemberPatchT patch;
|
|
|
|
patch.pos = basCodePos(&c->cg);
|
|
patch.classIdx = call->aux;
|
|
patch.sym = (const BasSymT *)call->target;
|
|
arrput(c->memberPatches, patch);
|
|
}
|
|
|
|
basEmitU16(&c->cg, (uint16_t)call->aux2);
|
|
basEmit8(&c->cg, (uint8_t)call->argc);
|
|
}
|
|
|
|
|
|
// Pushes a reference to what n names (a ByRef argument): a variable or
|
|
// whole array, an element (of an array, or of what a Variant holds; an
|
|
// object's default member gives its value), a TYPE field (one
|
|
// OP_PUSH_FIELD_ADDR per field of the chain). Stores through it convert
|
|
// to the target's declared type and keep a STRING * n's length.
|
|
void basGenRef(BasCompilerT *c, const BasNodeT *n) {
|
|
switch (n->kind) {
|
|
case NK_VAR:
|
|
basGenAddrOf(c, ((const BasNameT *)n)->sym);
|
|
break;
|
|
|
|
case NK_ELEM: {
|
|
const BasCallT *call = (const BasCallT *)n;
|
|
const BasSymT *sym = ((const BasNameT *)call->callee)->sym;
|
|
|
|
basGenLoadVar(c, sym);
|
|
basGenArgs(c, call->args);
|
|
basEmit8(&c->cg, OP_PUSH_ARR_ADDR);
|
|
basEmit8(&c->cg, (uint8_t)call->argc);
|
|
basEmit8(&c->cg, (sym->flags & SF_ARRAY) ? basDeclaredStoreType(sym) : BAS_TYPE_VARIANT);
|
|
break;
|
|
}
|
|
|
|
case NK_FIELD: {
|
|
const BasMemberT *m = (const BasMemberT *)n;
|
|
const BasFieldT *f = (const BasFieldT *)m->target;
|
|
|
|
basGenRef(c, m->base);
|
|
basEmit8(&c->cg, OP_PUSH_FIELD_ADDR);
|
|
basEmitU16(&c->cg, (uint16_t)m->aux);
|
|
basEmit8(&c->cg, f->dataType);
|
|
|
|
if (f->fixedLen > 0) {
|
|
basEmit8(&c->cg, OP_REF_FIXLEN);
|
|
basEmitU16(&c->cg, (uint16_t)f->fixedLen);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
basErrorNode(c, n, "Internal error: reference kind %d", (int)n->kind);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// The arguments of mask (a built-in's nullMask) that may be Null when the
|
|
// program runs: those whose type is not known statically.
|
|
static uint8_t nullGuardMask(const BasNodeT *args, uint8_t mask) {
|
|
uint8_t guard = 0;
|
|
uint8_t bit = BAS_NULL_ARG_1;
|
|
|
|
for (const BasNodeT *a = args; a != NULL && bit != 0; a = a->next) {
|
|
if ((mask & bit) && basStaticType(a) == BAS_NODE_TYPE_UNKNOWN) {
|
|
guard |= bit;
|
|
}
|
|
|
|
bit = (uint8_t)(bit << 1);
|
|
}
|
|
|
|
return guard;
|
|
}
|