DVX_GUI/src/apps/kpunch/dvxbasic/compiler/genExpr.c
2026-10-05 20:01:04 -05:00

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;
}