/* bcidefs.cpp XIDEK: Extensible Script Language Development Kit Bytecode Interpreter Definitions Copyright (c) 1996-2002 Parsifal Software. All Rights Reserved. This file implements a simple bytecode interpreter which uses an arithmetic stack for intermediate results. For further information about this program or the AnaGram parser generator, please contact: Parsifal Software http://www.parsifalsoft.com info@parsifalsoft.com +1-800-879-2577, Voice/Fax +1-508-358-2564 P.O. Box 219 Wayland, MA 01778 USA */ #include "comdefs.h" #include "bcidefs.h" #include // Entries in this table _must_ align precisely with the Opcode enumeration in // opcodes.h const char *opcodeText[] = { "HLT", // Stop execution "FETCH", // Fetch value addressed by top stack element "I_FETCH", // Increment, then fetch value adressed by top of stack "D_FETCH", // Decrement, then fetch value adressed by top of stack "FETCH_I", // Fetch, then increment value addressed by top of stack "FETCH_D", // Fetch, then decrement value addressed by top of stack "STORE", // Store second item on stack in location addressed by // top of stack. "POP", // Take off top stack element and discard it "SAP", "PRINT", // Pop top stack element and print it "RETURN", // Return to calling process // Binary operators: pop two values off stack, compute, push result "ADD", // Add "SUB", // Subtract "MUL", // Multiply "DIV", // Divide "IDIV", // Integer Divide "RDIV", // Real divide "MOD", // Remainder "POW", // Raise to power "AND", // And "IOR", // Inclusive or "XOR", // Exclusive or "LS", // Left shift "RS", // Right shift /* * Binary operators on memory: * pop top stack element, * pop pointer from stack * fetch value through pointer * compute, * then push result and also store it through pointer */ "ADDM", // Add to memory (variable) "SUBM", // Subtract from memory (variable) "MULM", // Multiply with memory (variable) "DIVM", // Divide by memory (variable) "MODM", // Remainder to memory (variable) "ANDM", // And to memory (variable) "IORM", // Inclusive or to memory (variable) "XORM", // Exclusive or to memory (variable) "LSM", // Left shift memory (variable) "RSM", // Right shift memory (variable) "POWM", // Raise memory to power // Comparison operators: pop two values off stack, push zero or nonzero "LT", // < "LE", // <= "GT", // > "GE", // >= "EQ", // == "NE", // != // Unary operators: pop value from stack, compute, push result "NEG", // Negate top element on stack "NOT", // Logical NOT of top element on stack "COM", // Bitwise complement // Cast operators "CAST_LONG", // Force type to int "CAST_DOUBLE", // Force type to double /* * Opcodes with one operand; two byte-code-sizes long. * In the descriptions, the value of the operand is represented with "K". */ "LOCATE", // Push address of variable no. K onto stack "PUSHC", // Push value of constant no. K onto stack "DUMP", // Print name and value of variable no. K. /* * Branch instructions: K is the offset in number of bytecodes * relative to the address of the instruction after the branch instruction. * That is, BR -2 is an infinite loop. */ "LAND", // branch on false; pop top stack element if not taken "LOR", // branch on true; pop top stack element if not taken "BR", // branch unconditionally "BRF", // branch on false, always pop top stack element "BRT", // branch on true, always pop top stack element "CALL", // call K'th function in function table "RPT", // Repeat block n times }; // ScriptMethod functions // Copy Constructor ScriptMethod::ScriptMethod(const ScriptMethod &m) : dictionary(m.dictionary), bytecode(m.bytecode), constantList(m.constantList) { // Nothing to do } // Write a listing of bytecode to a file. const ScriptMethod &ScriptMethod::list(ostream &f) const { const Bytecode *codeBase = (const Bytecode *) bytecode; const Bytecode *pc = codeBase; const Constant *constant = (const Constant *) constantList; Opcode op; do { op = (Opcode) *pc++; int offset; f << setfill('0') << setw(4) << (pc - codeBase - 1) << ": " << opcodeText[op] << " "; switch (op) { case LOCATE: case DUMP: f << (const char *)dictionary[*pc++]; break; case PUSHC: f << (const char *) constant[*pc++].asLiteral(); break; case LAND: case LOR: case BR: case BRF: case BRT: case RPT: offset = *pc++; f << (pc - codeBase + offset); break; case CALL: offset = *pc++; f << (const char *) functionTable[offset].name; break; } f << "\n"; } while (op != HLT); f << flush; return *this; } // Execute bytecode using a specified dataset Value ScriptMethod::apply(Dataset &dataset) const { // Dictionaries have to be the same. assert(&dataset.dictionary == &dictionary); // Get beginning of code const Bytecode *codeBase = (const Bytecode *) bytecode; const Constant *constant = (const Constant *) constantList; // Initialize program counter const Bytecode *pc = codeBase; // Stack for the arithmentic stack AgStack stack; //return value Value returnValue; // Temporaries int offset; // for branches const Bytecode *currentLocation; // Now, execute it. try { Opcode op; int running = 1; while (running) { currentLocation = pc; op = (Opcode) *pc++; switch(op) { case RETURN: // set return value from stack returnValue = stack.pop(); case HLT: // Note that on a HLT instruction, returnValue is uninitialized // force exit from execution loop running = 0; break; case LOCATE: { // Fetch address of variable and push on stack stack.push(Value(&dataset[*pc++])); break; } case FETCH: { stack.top().deref(); break; } case I_FETCH: { stack.push(++stack.pop()); break; } case D_FETCH: { stack.push(--stack.pop()); break; } case FETCH_I: { stack.push(stack.pop()++); break; } case FETCH_D: { stack.push(stack.pop()--); break; } case STORE: { Value temp = stack.pop(); stack.top() = temp; stack.top().deref(); break; } case SAP: { Value temp = stack.pop(); stack.pop() = temp; break; } case DUMP: { offset = *pc++; cout << (const char *) dictionary[offset] << " = " << (const char *) dataset[offset].asLiteral() << "\n"; break; } case PUSHC: { // Fetch value of constant and push on stack stack.push(constant[*pc++]); break; } case POP: { // Discard top of stack stack.pop(); break; } case PRINT: { // Pop top stack element and print it cout << (const char *) stack.pop().asString(); break; } case NEG: { // Negate top item on stack stack.top() = -stack.top(); break; } case COM: { // complement top item on stack stack.top() = ~stack.top(); break; } case ADD: { Value temp = stack.pop(); stack.top() += temp; break; } case ADDM: { Value temp = stack.pop(); stack.top() += temp; stack.top().deref(); break; } case SUB: { Value temp = stack.pop(); stack.top() -= temp; break; } case SUBM: { Value temp = stack.pop(); stack.top() -= temp; stack.top().deref(); break; } case MUL: { Value temp = stack.pop(); stack.top() *= temp; break; } case MULM: { Value temp = stack.pop(); stack.top() *= temp; stack.top().deref(); break; } case DIV: { Value temp = stack.pop(); stack.top() /= temp; break; } case DIVM: { Value temp = stack.pop(); stack.top() /= temp; stack.top().deref(); break; } case IDIV: { Value temp = stack.pop(); stack.top() = stack.top().idiv(temp); break; } case RDIV: { Value temp = stack.pop(); stack.top() = stack.top().rdiv(temp); break; } case MOD: { Value temp = stack.pop(); stack.top() %= temp; break; } case MODM: { Value temp = stack.pop(); stack.top() %= temp; stack.top().deref(); break; } case ANDM: { Value temp = stack.pop(); stack.top() &= temp; stack.top().deref(); break; } case IORM: { Value temp = stack.pop(); stack.top() |= temp; stack.top().deref(); break; } case XORM: { Value temp = stack.pop(); stack.top() ^= temp; stack.top().deref(); break; } case LSM: { Value temp = stack.pop(); stack.top() <<= temp; stack.top().deref(); break; } case RSM: { Value temp = stack.pop(); stack.top() >>= temp; stack.top().deref(); break; } case POW: { Value temp = stack.pop(); stack.top() = pow(stack.top(), temp); break; } case POWM: { Value power = stack.pop(); Value base = stack.top(); stack.top() = pow(base.deref(), power); stack.top().deref(); break; } case CAST_LONG: { stack.top().makeInteger(); break; } case CAST_DOUBLE: { stack.top().makeReal(); break; } case LT: { Value temp = stack.pop(); stack.top() = Value(stack.top() < temp); break; } case LE: { Value temp = stack.pop(); stack.top() = Value(stack.top() <= temp); break; } case GT: { Value temp = stack.pop(); stack.top() = Value(stack.top() > temp); break; } case GE: { Value temp = stack.pop(); stack.top() = Value(stack.top() >= temp); break; } case EQ: { Value temp = stack.pop(); stack.top() = Value(stack.top() == temp); break; } case NE: { Value temp = stack.pop(); stack.top() = Value(stack.top() != temp); break; } case LS: { Value temp = stack.pop(); stack.top() <<= temp; break; } case RS: { Value temp = stack.pop(); stack.top() >>= temp; break; } case AND: { Value temp = stack.pop(); stack.top() &= temp; break; } case IOR: { Value temp = stack.pop(); stack.top() |= temp; break; } case XOR: { Value temp = stack.pop(); stack.top() ^= temp; break; } case LAND: { offset = *pc++; if (stack.top().isTrue()) stack.pop(); else pc += offset; break; } case LOR: { offset = *pc++; if (stack.top().isFalse()) stack.pop(); else pc += offset; break; } case NOT: { stack.top() = !stack.top(); break; } case BR: { offset = *pc++; pc += offset; break; } case BRF: { offset = *pc++; if (stack.pop().isFalse()) pc += offset; break; } case BRT: { offset = *pc++; if (stack.pop().isTrue()) pc += offset; break; } case RPT: { offset = *pc++; if (stack.top() <= Value(0)) { pc += offset; stack.pop(); break; } stack.top() -= Value(1); break; } case CALL: { offset = *pc++; stack.push(callFunction(offset, stack)); break; } default: throw ErrorMessage("Undefined instruction"); } } } catch(ErrorMessage e) { char buf[100]; sprintf(buf, "Error at location %d: %s", (int)(currentLocation - codeBase), e.message()); throw ErrorDiagnostic(buf); } return returnValue; } /*** CodeFragment methods ***************************************************/ // Copy Constructor CodeFragment::CodeFragment(const CodeFragment &f) : bytecode(f.bytecode), branchList(f.branchList) { // Nothing to do } // Fetch array of bytecode AgArray CodeFragment::getBytecode() { return bytecode.contents(); } // Append opcode with parameter CodeFragment &CodeFragment::append(Opcode op, int offset) { // Make sure offset will fit in a Bytecode Bytecode trimmedOffset = (Bytecode) offset; if ((int) trimmedOffset != offset) throw ErrorMessage("Operand out of range"); // Actually append the code bytecode.push((Bytecode) op).push(trimmedOffset); return *this; } // Concatenate two code fragments CodeFragment &CodeFragment::concat(const CodeFragment &f) { int n = bytecode.size(); int i; // copy bytecodes for (i = 0; i < f.bytecode.size(); i++) bytecode.push(f.bytecode[i]); // copy and adjust continue and break locations for (i = 0; i < f.branchList.size(); i++) branchList.push(f.branchList[i] + n); return *this; } // append a continue instruction CodeFragment &CodeFragment::appendContinue(Opcode op) { bytecode.push((Bytecode) op); // append instruction branchList.push(bytecode.size()); // location of operand to branchList bytecode.push((Bytecode) 0); // 0 to indicate continue return *this; } // append a break instruction CodeFragment &CodeFragment::appendBreak(Opcode op) { bytecode.push((Bytecode) op); // append instruction branchList.push(bytecode.size()); // location of operand to branchList bytecode.push((Bytecode) 1); // 1 to indicate break return *this; } // patch offset fields for break branches CodeFragment &CodeFragment::patchBranches() { int n = size(); // scan all break and continue instructions in fragment for (int i = 0; i < branchList.size(); i++) { int k = branchList[i]; // bytecode[k] was set to 0 for continue, 1 for break int offset = n*bytecode[k] - k - 1; Bytecode bytecodeOffset = (Bytecode) offset; if ((int) bytecodeOffset != offset) { throw ErrorMessage("Branch offset out of range"); } bytecode[k] = bytecodeOffset; // set true offset } branchList.reset(); return *this; } // Determine whether the code fragment contains a break instruction int CodeFragment::containsBreak() { // scan all break and continue instructions in fragment for (int i = 0; i < branchList.size(); i++) { int k = branchList[i]; // bytecode[k] was set to 0 for continue, 1 for break if (bytecode[k]) return 1; } return 0; } // Code generation functions. CodeFragment CodeFragment::ifStatement ( CodeFragment &condition, CodeFragment &statement) { return condition // do the test .append(BRF, statement.size()) // if false, skip statement .concat(statement); // otherwise, execute code } CodeFragment CodeFragment::ifElse ( CodeFragment &condition, CodeFragment &onTrue, CodeFragment &onFalse) { onTrue.append(BR, onFalse.size()); // branch over false option return condition .append(BRF, onTrue.size()) // branch over true option .concat(onTrue) .concat(onFalse); } CodeFragment CodeFragment::whileLoop( CodeFragment &condition, CodeFragment &statement) { statement.appendContinue(BR); return condition // evaluate the condition .appendBreak(BRF) // if false, jump out .concat(statement) // run the loop body .patchBranches(); } CodeFragment CodeFragment::doLoop ( CodeFragment &statement, CodeFragment &condition) { CodeFragment header; condition.appendBreak(BRF); header.append(BR, condition.size()); // Branch over condition first time condition.concat(statement) .appendContinue(BR) .patchBranches(); return header.concat(condition); } CodeFragment CodeFragment::repeatLoop ( CodeFragment &statement, CodeFragment &condition) { CodeFragment header; condition.appendBreak(BRT); header.append(BR, condition.size()); // Branch over condition first time condition.concat(statement) .appendContinue(BR) .patchBranches(); return header.concat(condition); } CodeFragment CodeFragment::forLoop( CodeFragment &initializer, CodeFragment &condition, CodeFragment &increment, CodeFragment &statement) { if (initializer.size()) initializer.append(POP); if (increment.size()) { increment.append(POP); initializer.append(BR, increment.size()); } if (condition.size()) condition.appendBreak(BRF); increment.concat(condition) .concat(statement) // actual loop body .appendContinue(BR) // jump back to top of loop .patchBranches(); // fill in continue, break offsets return initializer.concat(increment); } CodeFragment CodeFragment::simpleForLoop( const CodeFragment &lValue, const CodeFragment &begin, const CodeFragment &inc, const CodeFragment &end, const CodeFragment &statement) { CodeFragment loopInit; CodeFragment loopBody; loopInit.concat(lValue) // Get pointer to loop value .concat(begin) // Calculate beginning value .append(SAP) // Store in loop Value .concat(end) // Get final value .concat(inc) .append(ADD) // add increment .concat(begin) // Initial value .append(SUB) // Subtract final from initial .concat(inc) // Get increment .append(DIV) // Get repeat count .append(CAST_LONG); // reduce to an integer loopBody.appendBreak(RPT) .concat(statement) // statement body .concat(lValue) // address for store .concat(lValue) // address for fetch .append(FETCH) .concat(inc) // get increment .append(ADD) .append(SAP) .appendContinue(BR) .patchBranches(); loopInit.concat(loopBody); return loopInit; }