373 lines
9.8 KiB
C
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);
|
|
}
|