muddle/src/interpreter.c
2024-02-07 18:58:53 -06:00

373 lines
9.8 KiB
C

/*
* Copyright (c) 2024 Scott Duensing, scott@kangaroopunch.com
*
* 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.
*/
#include "stddclmr.h"
#include "interpreter.h"
#include "portme.h"
#include "messages.h"
#include "story.h"
#include "state.h"
#include "variable.h"
#include "memory.h"
#include "lib.h"
#include "text.h"
void interpreterDoBranch(int32_t truth) {
uint8_t branch = ZPEEK(__state.pc++);
int32_t farJump = (branch & (1 << 6)) == 0;
int32_t onTruth = (branch & (1 << 7)) ? 1 : 0;
uint8_t byte2 = farJump ? ZPEEK(__state.pc++) : 0;
int16_t offset;
if (onTruth == truth) {
offset = (int16_t)(branch & 0x3f);
if (farJump) {
if (offset & (1 << 5)) offset |= 0xc0;
offset = (offset << 8) | ((int16_t)byte2);
}
if (offset == 0) {
interpreterDoReturn(0);
} else if (offset == 1) {
interpreterDoReturn(1);
} else {
__state.pc = (__state.pc + offset) - 2;
}
}
}
void interpreterDoReturn(uint16_t r) {
uint8_t storeId;
//***TODO*** Newer versions start in a real routine, but still aren't allowed to return from it.
if (__state.bp == 0) portDie(MSG_INT_STACK_UNDERFLOW);
#ifdef DEBUGGINGx
printf("Returning: initial pc=%X, bp=%u, sp=%u\n", (unsigned int)__state.pc, __state.bp, __state.sp);
#endif
// Dump all locals and data pushed on stack during the routine.
__state.sp = __state.bp;
// Dump our copy of numLocals.
__state.sp--;
// Restore previous frame's base pointer.
__state.bp = __state.stack[--__state.sp];
// Point to start of saved program counter;
__state.sp -= 2;
__state.pc = ((uint32_t)__state.stack[__state.sp]) | (((uint32_t)__state.stack[__state.sp + 1]) << 16);
// Pop the result storage location.
storeId = (uint8_t)__state.stack[--__state.sp];
#ifdef DEBUGGINGx
printf("Returning: new pc=%X, bp=%u, sp=%u\n", (unsigned int)__state.pc, __state.bp, __state.sp);
#endif
// Store the routine result.
variableStore(storeId, r);
}
bool interpreterParseOperand(uint8_t opType, uint8_t operandId) {
switch(opType) {
// Large constant.
case 0:
__state.operands[operandId] = ZPEEKW(__state.pc);
__state.pc += 2;
return true;
// Small constant.
case 1:
__state.operands[operandId] = ZPEEK(__state.pc++);
return true;
// Variable.
case 2:
__state.operands[operandId] = variableLoad(ZPEEK(__state.pc++));
return true;
}
return false;
}
uint8_t interpreterParseVariableOperands(uint8_t index) {
uint8_t operandTypes = ZPEEK(__state.pc++);
uint8_t shifter = 6;
uint8_t i;
uint8_t opType;
for (i=0; i<4; i++) {
opType = (operandTypes >> shifter) & 0x3;
shifter -= 2;
if (!interpreterParseOperand(opType, index + i)) break;
}
return i;
}
void interpreterRun(void) {
uint8_t opcode;
bool extended;
opcodeT op;
bool eight;
// Go!
while (__state.quit == 0) {
opcode = ZPEEK(__state.pc++);
extended = ((opcode == 190) && (storyVersion() >= 5)) ? true : false;
if (extended) {
opcode = ZPEEK(__state.pc++);
if (opcode >= (sizeof(__state.extOpcodes) / sizeof(__state.extOpcodes[0]))) portDie(MSG_INT_INVALID_EXT_OPCODE);
__state.operandCount = interpreterParseVariableOperands(0);
op = __state.extOpcodes[opcode];
} else {
if (opcode <= 127) {
// 2OP
__state.operandCount = 2;
interpreterParseOperand(((opcode >> 6) & 0x1) ? 2 : 1, 0);
interpreterParseOperand(((opcode >> 5) & 0x1) ? 2 : 1, 1);
} else if (opcode <= 175) {
// 1OP
__state.operandCount = 1;
interpreterParseOperand((opcode >> 4) & 0x3, 0);
} else if (opcode <= 191) {
// 0OP
__state.operandCount = 0;
} else if (opcode > 191) {
// Variable Opcodes
eight = (opcode == 236) || (opcode == 250);
if (!eight) {
// call_vs2 and call_vn2 take up to EIGHT arguments!
__state.operandCount = interpreterParseVariableOperands(0);
} else {
__state.operandCount = interpreterParseVariableOperands(0);
if (__state.operandCount == 4) {
__state.operandCount += interpreterParseVariableOperands(4);
} else {
__state.pc++;
}
}
}
op = __state.opcodes[opcode];
} // extended
if (op == 0) portDie(MSG_INT_UNIMPLEMENTED_OPCODE, extended ? "ext" : "", opcode);
#ifdef DEBUGGINGx
printf("ins=%u pc=%X sp=%u bp=%u %sopcode=%u ('%s') [",
__state.instructionsRun,
(unsigned int)__state.pc,
__state.sp,
__state.bp,
extended ? "ext " : "",
opcode,
extended ? __state.extOpcodesName[opcode] : __state.opcodesName[opcode]);
if (__state.operandCount) {
uint8_t i;
for (i=0; i<__state.operandCount - 1; i++)
printf("%X,", (unsigned int)__state.operands[i]);
printf("%X", (unsigned int)__state.operands[i]);
}
printf("]\n");
fflush(stdout);
if (__state.instructionsRun == 261) {
printf("\n*** BREAKING ***\n");
fflush(stdout);
printf(" "); // Set breakpoint here.
}
#endif
op();
#ifdef DEBUGGING
__state.instructionsRun++;
#endif
}
}
void interpreterTokenizeInput(void) {
static char tableA2v1[] = "0123456789.,!?_#\'\"/\\<-:()";
static char tableA2v2plus[] = "\n0123456789.,!?_#\'\"/\\-:()";
char *tableA2 = (storyVersion() <= 1) ? tableA2v1 : tableA2v2plus;
uint32_t input = __state.operands[0];
uint32_t parse = __state.operands[1];
byte parseLen = ZPEEK(parse++);
uint32_t seps = storyDictionaryAddress();
byte numSeps = ZPEEK(seps++);
uint32_t dict = seps + numSeps;
byte entryLen = ZPEEK(dict++);
uint16_t numEntries = ZPEEKW(dict); dict += 2;
byte numToks = 0;
uint32_t strStart;
uint32_t ptr;
byte *ptr2;
bool isSep;
byte ch;
uint32_t i;
uint16_t encoded[3];
byte tokLen;
byte zChars[12];
byte zChIdx;
byte pos;
uint32_t dictPtr;
uint16_t zscii1;
uint16_t zscii2;
uint16_t zscii3;
#ifdef DEBUGGING
printf("Max parse: %u\n", parseLen);
printf("Parse Input: [");
#endif
if (parseLen == 0) portDie(MSG_INT_PARSE_BUFFER_TOO_SMALL);
input++; // Skip inputLen byte.
parse++; // Skip where we put final token count.
// Tokenize input.
strStart = input;
ptr = input;
while (1) {
isSep = false;
ch = ZPEEK(ptr);
#ifdef DEBUGGING
printf("%c", ch);
#endif
if ((ch == ' ') || (ch == 0)) {
isSep = true;
} else {
for (i=0; i<numSeps; i++) {
if (ch == ZPEEK(seps + i)) {
isSep = true;
break;
}
}
} // isSep
if (isSep) {
encoded[0] = 0;
encoded[1] = 0;
encoded[2] = 0;
tokLen = ptr - strStart;
if (tokLen == 0) break; // Ran out of string.
zChIdx = 0;
for (i=0; i<tokLen; i++) {
ch = ZPEEK(strStart + i);
if ((ch >= 'a') && (ch <= 'z')) {
zChars[zChIdx++] = ((ch - 'a') + 6);
} else {
ptr2 = (byte *)libStrChr(tableA2, ch);
if (ptr2) {
// Command char to shift to table A2 for just the next char.
zChars[zChIdx++] = 3;
// +1 because the first table entry is a different piece of magic.
zChars[zChIdx++] = ((ptr2 - (byte *)tableA2) + 1) + 6;
}
}
if (zChIdx >= (sizeof(zChars) / sizeof(zChars[0]))) break;
} // for
pos = 0;
encoded[0] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 10;
encoded[0] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 5;
encoded[0] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 0;
encoded[1] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 10;
encoded[1] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 5;
encoded[1] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 0;
dictPtr = dict;
if (storyVersion() <= 3) {
encoded[1] |= 0x8000;
for (i=0; i<numEntries; i++) {
zscii1 = ZPEEKW(dictPtr); dictPtr += 2;
zscii2 = ZPEEKW(dictPtr); dictPtr += 2;
if ((encoded[0] == zscii1) && (encoded[1] == zscii2)) {
dictPtr -= 4;
break;
}
dictPtr += (entryLen - 4);
}
} else { // V3
encoded[2] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 10;
encoded[2] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 5;
encoded[2] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 0;
encoded[2] |= 0x8000;
for (i=0; i<numEntries; i++) {
zscii1 = ZPEEKW(dictPtr); dictPtr += 2;
zscii2 = ZPEEKW(dictPtr); dictPtr += 2;
zscii3 = ZPEEKW(dictPtr); dictPtr += 2;
if ((encoded[0] == zscii1) && (encoded[1] == zscii2) && (encoded[2] == zscii3)) {
dictPtr -= 6;
break;
}
dictPtr += (entryLen - 6);
}
} // V3
if (i == numEntries) dictPtr = 0; // Not Found.
ZPOKEW(parse, dictPtr); parse += 2;
ZPOKE(parse++, tokLen);
ZPOKE(parse++, (strStart - input) + 1);
numToks++;
if (numToks >= parseLen) break; // Ran out of space.
strStart = ptr + 1;
} // if isSep
if (ch == 0) break; // End of string.
ptr++;
} // while
#ifdef DEBUGGING
printf("]\n");
printf("Tokenized %u tokens\n", (unsigned int)numToks);
fflush(stdout);
#endif
ZPOKE(__state.operands[1] + 1, numToks);
}