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