/* * 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= '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= 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); }