mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
1323 lines
46 KiB
C++
1323 lines
46 KiB
C++
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "common/stream.h"
|
|
#include "common/util.h"
|
|
#include "./ast.h"
|
|
#include "./codewritervisitor.h"
|
|
#include "./handler.h"
|
|
#include "./names.h"
|
|
#include "./script.h"
|
|
|
|
namespace LingoDec {
|
|
|
|
/* Handler */
|
|
|
|
void Handler::readRecord(Common::SeekableReadStream &stream) {
|
|
nameID = stream.readSint16BE();
|
|
vectorPos = stream.readUint16BE();
|
|
compiledLen = stream.readUint32BE();
|
|
compiledOffset = stream.readUint32BE();
|
|
argumentCount = stream.readUint16BE();
|
|
argumentOffset = stream.readUint32BE();
|
|
localsCount = stream.readUint16BE();
|
|
localsOffset = stream.readUint32BE();
|
|
globalsCount = stream.readUint16BE();
|
|
globalsOffset = stream.readUint32BE();
|
|
unknown1 = stream.readUint32BE();
|
|
unknown2 = stream.readUint16BE();
|
|
lineCount = stream.readUint16BE();
|
|
lineOffset = stream.readUint32BE();
|
|
// yet to implement
|
|
if (script->version >= 850)
|
|
stackHeight = stream.readUint32BE();
|
|
}
|
|
|
|
void Handler::readData(Common::SeekableReadStream &stream) {
|
|
stream.seek(compiledOffset);
|
|
while (stream.pos() < compiledOffset + compiledLen) {
|
|
uint32 pos = stream.pos() - compiledOffset;
|
|
byte op = stream.readByte();
|
|
OpCode opcode = static_cast<OpCode>(op >= 0x40 ? 0x40 + op % 0x40 : op);
|
|
// argument can be one, two or four bytes
|
|
int32 obj = 0;
|
|
if (op >= 0xc0) {
|
|
// four bytes
|
|
obj = stream.readSint32BE();
|
|
} else if (op >= 0x80) {
|
|
// two bytes
|
|
if (opcode == kOpPushInt16 || opcode == kOpPushInt8) {
|
|
// treat pushint's arg as signed
|
|
// pushint8 may be used to push a 16-bit int in older Lingo
|
|
obj = stream.readSint16BE();
|
|
} else {
|
|
obj = stream.readUint16BE();
|
|
}
|
|
} else if (op >= 0x40) {
|
|
// one byte
|
|
if (opcode == kOpPushInt8) {
|
|
// treat pushint's arg as signed
|
|
obj = stream.readSByte();
|
|
} else {
|
|
obj = stream.readByte();
|
|
}
|
|
}
|
|
Bytecode bytecode(op, obj, pos);
|
|
bytecodeArray.push_back(bytecode);
|
|
bytecodePosMap[pos] = bytecodeArray.size() - 1;
|
|
}
|
|
|
|
argumentNameIDs = readVarnamesTable(stream, argumentCount, argumentOffset);
|
|
localNameIDs = readVarnamesTable(stream, localsCount, localsOffset);
|
|
globalNameIDs = readVarnamesTable(stream, globalsCount, globalsOffset);
|
|
}
|
|
|
|
Common::Array<int16> Handler::readVarnamesTable(Common::SeekableReadStream &stream, uint16 count, uint32 offset) {
|
|
stream.seek(offset);
|
|
Common::Array<int16> nameIDs;
|
|
nameIDs.resize(count);
|
|
for (size_t i = 0; i < count; i++) {
|
|
nameIDs[i] = stream.readUint16BE();
|
|
}
|
|
return nameIDs;
|
|
}
|
|
|
|
void Handler::readNames() {
|
|
if (!isGenericEvent) {
|
|
name = getName(nameID);
|
|
}
|
|
for (size_t i = 0; i < argumentNameIDs.size(); i++) {
|
|
if (i == 0 && script->isFactory())
|
|
continue;
|
|
argumentNames.push_back(getName(argumentNameIDs[i]));
|
|
}
|
|
for (auto nameID_ : localNameIDs) {
|
|
if (validName(nameID_)) {
|
|
localNames.push_back(getName(nameID_));
|
|
}
|
|
}
|
|
for (auto nameID_ : globalNameIDs) {
|
|
if (validName(nameID_)) {
|
|
globalNames.push_back(getName(nameID_));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Handler::validName(int id) const {
|
|
return script->validName(id);
|
|
}
|
|
|
|
Common::String Handler::getName(int id) const {
|
|
return script->getName(id);
|
|
}
|
|
|
|
Common::String Handler::getArgumentName(int id) const {
|
|
if (-1 < id && (unsigned)id < argumentNameIDs.size())
|
|
return getName(argumentNameIDs[id]);
|
|
return Common::String::format("UNKNOWN_ARG_%d", id);
|
|
}
|
|
|
|
Common::String Handler::getLocalName(int id) const {
|
|
if (-1 < id && (unsigned)id < localNameIDs.size())
|
|
return getName(localNameIDs[id]);
|
|
return Common::String::format("UNKNOWN_LOCAL_%d", id);
|
|
}
|
|
|
|
Common::SharedPtr<Node> Handler::pop() {
|
|
if (stack.empty())
|
|
return Common::SharedPtr<Node>(new ErrorNode(0));
|
|
|
|
auto res = stack.back();
|
|
stack.pop_back();
|
|
return res;
|
|
}
|
|
|
|
int Handler::variableMultiplier() {
|
|
if (script->version >= 850)
|
|
return 1;
|
|
if (script->version >= 500)
|
|
return 8;
|
|
return 6;
|
|
}
|
|
|
|
Common::SharedPtr<Node> Handler::readVar(int varType) {
|
|
Common::SharedPtr<Node> castID;
|
|
if (varType == 0x6 && script->version >= 500) // field cast ID
|
|
castID = pop();
|
|
Common::SharedPtr<Node> id = pop();
|
|
|
|
switch (varType) {
|
|
case 0x1: // global
|
|
case 0x2: // global
|
|
case 0x3: // property/instance
|
|
return id;
|
|
case 0x4: // arg
|
|
{
|
|
Common::String name_ = getArgumentName(id->getValue()->i / variableMultiplier());
|
|
auto ref = Common::SharedPtr<Datum>(new Datum(kDatumVarRef, name_));
|
|
return Common::SharedPtr<Node>(new LiteralNode(id->_startOffset, Common::move(ref)));
|
|
}
|
|
case 0x5: // local
|
|
{
|
|
Common::String name_ = getLocalName(id->getValue()->i / variableMultiplier());
|
|
auto ref = Common::SharedPtr<Datum>(new Datum(kDatumVarRef, name_));
|
|
return Common::SharedPtr<Node>(new LiteralNode(id->_startOffset, Common::move(ref)));
|
|
}
|
|
case 0x6: // field
|
|
return Common::SharedPtr<Node>(new MemberExprNode(id->_startOffset, "field", Common::move(id), Common::move(castID)));
|
|
default:
|
|
warning("findVar: unhandled var type %d", varType);
|
|
break;
|
|
}
|
|
return Common::SharedPtr<Node>(new ErrorNode(id->_startOffset));
|
|
}
|
|
|
|
Common::String Handler::getVarNameFromSet(const Bytecode &bytecode) {
|
|
Common::String varName;
|
|
switch (bytecode.opcode) {
|
|
case kOpSetGlobal:
|
|
case kOpSetGlobal2:
|
|
varName = getName(bytecode.obj);
|
|
break;
|
|
case kOpSetProp:
|
|
varName = getName(bytecode.obj);
|
|
break;
|
|
case kOpSetParam:
|
|
varName = getArgumentName(bytecode.obj / variableMultiplier());
|
|
break;
|
|
case kOpSetLocal:
|
|
varName = getLocalName(bytecode.obj / variableMultiplier());
|
|
break;
|
|
default:
|
|
varName = "ERROR";
|
|
break;
|
|
}
|
|
return varName;
|
|
}
|
|
|
|
Common::SharedPtr<Node> Handler::readV4Property(uint32 offset, int propertyType, int propertyID) {
|
|
switch (propertyType) {
|
|
case 0x00:
|
|
{
|
|
if (propertyID <= 0x0b) { // movie property
|
|
Common::String propName(StandardNames::moviePropertyNames[propertyID]);
|
|
return Common::SharedPtr<Node>(new TheExprNode(offset, propName));
|
|
} else { // last chunk
|
|
auto string = pop();
|
|
auto chunkType = static_cast<ChunkExprType>(propertyID - 0x0b);
|
|
return Common::SharedPtr<Node>(new LastStringChunkExprNode(offset, chunkType, Common::move(string)));
|
|
}
|
|
}
|
|
break;
|
|
case 0x01: // number of chunks
|
|
{
|
|
auto string = pop();
|
|
return Common::SharedPtr<Node>(new StringChunkCountExprNode(offset, static_cast<ChunkExprType>(propertyID), Common::move(string)));
|
|
}
|
|
break;
|
|
case 0x02: // menu property
|
|
{
|
|
auto menuID = pop();
|
|
return Common::SharedPtr<Node>(new MenuPropExprNode(offset, Common::move(menuID), propertyID));
|
|
}
|
|
break;
|
|
case 0x03: // menu item property
|
|
{
|
|
auto menuID = pop();
|
|
auto itemID = pop();
|
|
return Common::SharedPtr<Node>(new MenuItemPropExprNode(offset, Common::move(menuID), Common::move(itemID), propertyID));
|
|
}
|
|
break;
|
|
case 0x04: // sound property
|
|
{
|
|
auto soundID = pop();
|
|
return Common::SharedPtr<Node>(new SoundPropExprNode(offset, Common::move(soundID), propertyID));
|
|
}
|
|
break;
|
|
case 0x05: // resource property - unused?
|
|
return Common::SharedPtr<Node>(new CommentNode(offset, "ERROR: Resource property"));
|
|
case 0x06: // sprite property
|
|
{
|
|
auto spriteID = pop();
|
|
return Common::SharedPtr<Node>(new SpritePropExprNode(offset, Common::move(spriteID), propertyID));
|
|
}
|
|
break;
|
|
case 0x07: // animation property
|
|
return Common::SharedPtr<Node>(new TheExprNode(offset, StandardNames::animationPropertyNames[propertyID]));
|
|
case 0x08: // animation 2 property
|
|
if (propertyID == 0x02 && script->version >= 500) { // the number of castMembers supports castLib selection from Director 5.0
|
|
auto castLib = pop();
|
|
if (!(castLib->type == kLiteralNode && castLib->getValue()->type == kDatumInt && castLib->getValue()->toInt() == 0)) {
|
|
auto castLibNode = Common::SharedPtr<Node>(new MemberExprNode(offset, "castLib", castLib, nullptr));
|
|
return Common::SharedPtr<Node>(new ThePropExprNode(offset, castLibNode, StandardNames::animation2PropertyNames[propertyID]));
|
|
}
|
|
}
|
|
return Common::SharedPtr<Node>(new TheExprNode(offset, StandardNames::animation2PropertyNames[propertyID]));
|
|
case 0x09: // generic cast member
|
|
case 0x0a: // chunk of cast member
|
|
case 0x0b: // field
|
|
case 0x0c: // chunk of field
|
|
case 0x0d: // digital video
|
|
case 0x0e: // bitmap
|
|
case 0x0f: // sound
|
|
case 0x10: // button
|
|
case 0x11: // shape
|
|
case 0x12: // movie
|
|
case 0x13: // script
|
|
case 0x14: // scriptText
|
|
case 0x15: // chunk of scriptText
|
|
{
|
|
auto propName = StandardNames::memberPropertyNames[propertyID];
|
|
Common::SharedPtr<Node> castID;
|
|
if (script->version >= 500) {
|
|
castID = pop();
|
|
}
|
|
auto memberID = pop();
|
|
Common::String prefix;
|
|
if (propertyType == 0x0b || propertyType == 0x0c) {
|
|
prefix = "field";
|
|
} else if (propertyType == 0x14 || propertyType == 0x15) {
|
|
prefix = "script";
|
|
} else {
|
|
prefix = (script->version >= 500) ? "member" : "cast";
|
|
}
|
|
auto member = Common::SharedPtr<Node>(new MemberExprNode(offset, prefix, Common::move(memberID), Common::move(castID)));
|
|
Common::SharedPtr<Node> entity;
|
|
if (propertyType == 0x0a || propertyType == 0x0c || propertyType == 0x15) {
|
|
entity = readChunkRef(offset, Common::move(member));
|
|
} else {
|
|
entity = member;
|
|
}
|
|
return Common::SharedPtr<Node>(new ThePropExprNode(offset, Common::move(entity), propName));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return Common::SharedPtr<Node>(new CommentNode(offset, Common::String::format("ERROR: Unknown property type %d", propertyType)));
|
|
}
|
|
|
|
Common::SharedPtr<Node> Handler::readChunkRef(uint32 offset, Common::SharedPtr<Node> string) {
|
|
auto lastLine = pop();
|
|
auto firstLine = pop();
|
|
auto lastItem = pop();
|
|
auto firstItem = pop();
|
|
auto lastWord = pop();
|
|
auto firstWord = pop();
|
|
auto lastChar = pop();
|
|
auto firstChar = pop();
|
|
|
|
if (!(firstLine->type == kLiteralNode && firstLine->getValue()->type == kDatumInt && firstLine->getValue()->toInt() == 0))
|
|
string = Common::SharedPtr<Node>(new ChunkExprNode(offset, kChunkLine, Common::move(firstLine), Common::move(lastLine), Common::move(string)));
|
|
if (!(firstItem->type == kLiteralNode && firstItem->getValue()->type == kDatumInt && firstItem->getValue()->toInt() == 0))
|
|
string = Common::SharedPtr<Node>(new ChunkExprNode(offset, kChunkItem, Common::move(firstItem), Common::move(lastItem), Common::move(string)));
|
|
if (!(firstWord->type == kLiteralNode && firstWord->getValue()->type == kDatumInt && firstWord->getValue()->toInt() == 0))
|
|
string = Common::SharedPtr<Node>(new ChunkExprNode(offset, kChunkWord, Common::move(firstWord), Common::move(lastWord), Common::move(string)));
|
|
if (!(firstChar->type == kLiteralNode && firstChar->getValue()->type == kDatumInt && firstChar->getValue()->toInt() == 0))
|
|
string = Common::SharedPtr<Node>(new ChunkExprNode(offset, kChunkChar, Common::move(firstChar), Common::move(lastChar), Common::move(string)));
|
|
|
|
return string;
|
|
}
|
|
|
|
void Handler::tagLoops() {
|
|
// Tag any jmpifz which is a loop with the loop type
|
|
// (kTagRepeatWhile, kTagRepeatWithIn, kTagRepeatWithTo, kTagRepeatWithDownTo).
|
|
// Tag the instruction which `next repeat` jumps to with kTagNextRepeatTarget.
|
|
// Tag any instructions which are internal loop logic with kTagSkip, so that
|
|
// they will be skipped during translation.
|
|
|
|
for (uint32 startIndex = 0; startIndex < bytecodeArray.size(); startIndex++) {
|
|
// All loops begin with jmpifz...
|
|
auto &jmpifz = bytecodeArray[startIndex];
|
|
if (jmpifz.opcode != kOpJmpIfZ)
|
|
continue;
|
|
|
|
// ...and end with endrepeat.
|
|
uint32 jmpPos = jmpifz.pos + jmpifz.obj;
|
|
uint32 endIndex = bytecodePosMap[jmpPos];
|
|
auto &endRepeat = bytecodeArray[endIndex - 1];
|
|
if (endRepeat.opcode != kOpEndRepeat || (endRepeat.pos - endRepeat.obj) > jmpifz.pos)
|
|
continue;
|
|
|
|
BytecodeTag loopType = identifyLoop(startIndex, endIndex);
|
|
bytecodeArray[startIndex].tag = loopType;
|
|
|
|
if (loopType == kTagRepeatWithIn) {
|
|
for (uint32 i = startIndex - 7, end = startIndex - 1; i <= end; i++)
|
|
bytecodeArray[i].tag = kTagSkip;
|
|
for (uint32 i = startIndex + 1, end = startIndex + 5; i <= end; i++)
|
|
bytecodeArray[i].tag = kTagSkip;
|
|
bytecodeArray[endIndex - 3].tag = kTagNextRepeatTarget; // pushint8 1
|
|
bytecodeArray[endIndex - 3].ownerLoop = startIndex;
|
|
bytecodeArray[endIndex - 2].tag = kTagSkip; // add
|
|
bytecodeArray[endIndex - 1].tag = kTagSkip; // endrepeat
|
|
bytecodeArray[endIndex - 1].ownerLoop = startIndex;
|
|
bytecodeArray[endIndex].tag = kTagSkip; // pop 3
|
|
} else if (loopType == kTagRepeatWithTo || loopType == kTagRepeatWithDownTo) {
|
|
uint32 conditionStartIndex = bytecodePosMap[endRepeat.pos - endRepeat.obj];
|
|
bytecodeArray[conditionStartIndex - 1].tag = kTagSkip; // set
|
|
bytecodeArray[conditionStartIndex].tag = kTagSkip; // get
|
|
bytecodeArray[startIndex - 1].tag = kTagSkip; // lteq / gteq
|
|
bytecodeArray[endIndex - 5].tag = kTagNextRepeatTarget; // pushint8 1 / pushint8 -1
|
|
bytecodeArray[endIndex - 5].ownerLoop = startIndex;
|
|
bytecodeArray[endIndex - 4].tag = kTagSkip; // get
|
|
bytecodeArray[endIndex - 3].tag = kTagSkip; // add
|
|
bytecodeArray[endIndex - 2].tag = kTagSkip; // set
|
|
bytecodeArray[endIndex - 1].tag = kTagSkip; // endrepeat
|
|
bytecodeArray[endIndex - 1].ownerLoop = startIndex;
|
|
} else if (loopType == kTagRepeatWhile) {
|
|
bytecodeArray[endIndex - 1].tag = kTagNextRepeatTarget; // endrepeat
|
|
bytecodeArray[endIndex - 1].ownerLoop = startIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Handler::isRepeatWithIn(uint32 startIndex, uint32 endIndex) {
|
|
if (startIndex < 7 || startIndex > bytecodeArray.size() - 6)
|
|
return false;
|
|
if (!(bytecodeArray[startIndex - 7].opcode == kOpPeek && bytecodeArray[startIndex - 7].obj == 0))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex - 6].opcode == kOpPushArgList && bytecodeArray[startIndex - 6].obj == 1))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex - 5].opcode == kOpExtCall && getName(bytecodeArray[startIndex - 5].obj) == "count"))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex - 4].opcode == kOpPushInt8 && bytecodeArray[startIndex - 4].obj == 1))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex - 3].opcode == kOpPeek && bytecodeArray[startIndex - 3].obj == 0))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex - 2].opcode == kOpPeek && bytecodeArray[startIndex - 2].obj == 2))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex - 1].opcode == kOpLtEq))
|
|
return false;
|
|
// if (!(bytecodeArray[startIndex].opcode == kOpJmpIfZ))
|
|
// return false;
|
|
if (!(bytecodeArray[startIndex + 1].opcode == kOpPeek && bytecodeArray[startIndex + 1].obj == 2))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex + 2].opcode == kOpPeek && bytecodeArray[startIndex + 2].obj == 1))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex + 3].opcode == kOpPushArgList && bytecodeArray[startIndex + 3].obj == 2))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex + 4].opcode == kOpExtCall && getName(bytecodeArray[startIndex + 4].obj) == "getAt"))
|
|
return false;
|
|
if (!(bytecodeArray[startIndex + 5].opcode == kOpSetGlobal || bytecodeArray[startIndex + 5].opcode == kOpSetProp
|
|
|| bytecodeArray[startIndex + 5].opcode == kOpSetParam || bytecodeArray[startIndex + 5].opcode == kOpSetLocal))
|
|
return false;
|
|
|
|
if (endIndex < 3)
|
|
return false;
|
|
if (!(bytecodeArray[endIndex - 3].opcode == kOpPushInt8 && bytecodeArray[endIndex - 3].obj == 1))
|
|
return false;
|
|
if (!(bytecodeArray[endIndex - 2].opcode == kOpAdd))
|
|
return false;
|
|
// if (!(bytecodeArray[startIndex - 1].opcode == kOpEndRepeat))
|
|
// return false;
|
|
if (!(bytecodeArray[endIndex].opcode == kOpPop && bytecodeArray[endIndex].obj == 3))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
BytecodeTag Handler::identifyLoop(uint32 startIndex, uint32 endIndex) {
|
|
if (isRepeatWithIn(startIndex, endIndex))
|
|
return kTagRepeatWithIn;
|
|
|
|
if (startIndex < 1)
|
|
return kTagRepeatWhile;
|
|
|
|
bool up;
|
|
switch (bytecodeArray[startIndex - 1].opcode) {
|
|
case kOpLtEq:
|
|
up = true;
|
|
break;
|
|
case kOpGtEq:
|
|
up = false;
|
|
break;
|
|
default:
|
|
return kTagRepeatWhile;
|
|
}
|
|
|
|
auto &endRepeat = bytecodeArray[endIndex - 1];
|
|
uint32 conditionStartIndex = bytecodePosMap[endRepeat.pos - endRepeat.obj];
|
|
|
|
if (conditionStartIndex < 1)
|
|
return kTagRepeatWhile;
|
|
|
|
OpCode getOp;
|
|
switch (bytecodeArray[conditionStartIndex - 1].opcode) {
|
|
case kOpSetGlobal:
|
|
getOp = kOpGetGlobal;
|
|
break;
|
|
case kOpSetGlobal2:
|
|
getOp = kOpGetGlobal2;
|
|
break;
|
|
case kOpSetProp:
|
|
getOp = kOpGetProp;
|
|
break;
|
|
case kOpSetParam:
|
|
getOp = kOpGetParam;
|
|
break;
|
|
case kOpSetLocal:
|
|
getOp = kOpGetLocal;
|
|
break;
|
|
default:
|
|
return kTagRepeatWhile;
|
|
}
|
|
OpCode setOp = bytecodeArray[conditionStartIndex - 1].opcode;
|
|
int32 varID = bytecodeArray[conditionStartIndex - 1].obj;
|
|
|
|
if (!(bytecodeArray[conditionStartIndex].opcode == getOp && bytecodeArray[conditionStartIndex].obj == varID))
|
|
return kTagRepeatWhile;
|
|
|
|
if (endIndex < 5)
|
|
return kTagRepeatWhile;
|
|
if (up) {
|
|
if (!(bytecodeArray[endIndex - 5].opcode == kOpPushInt8 && bytecodeArray[endIndex - 5].obj == 1))
|
|
return kTagRepeatWhile;
|
|
} else {
|
|
if (!(bytecodeArray[endIndex - 5].opcode == kOpPushInt8 && bytecodeArray[endIndex - 5].obj == -1))
|
|
return kTagRepeatWhile;
|
|
}
|
|
if (!(bytecodeArray[endIndex - 4].opcode == getOp && bytecodeArray[endIndex - 4].obj == varID))
|
|
return kTagRepeatWhile;
|
|
if (!(bytecodeArray[endIndex - 3].opcode == kOpAdd))
|
|
return kTagRepeatWhile;
|
|
if (!(bytecodeArray[endIndex - 2].opcode == setOp && bytecodeArray[endIndex - 2].obj == varID))
|
|
return kTagRepeatWhile;
|
|
|
|
return up ? kTagRepeatWithTo : kTagRepeatWithDownTo;
|
|
}
|
|
|
|
void Handler::parse() {
|
|
tagLoops();
|
|
stack.clear();
|
|
uint32 i = 0;
|
|
while (i < bytecodeArray.size()) {
|
|
auto &bytecode = bytecodeArray[i];
|
|
uint32 pos = bytecode.pos;
|
|
// exit last block if at end
|
|
while (pos == ast.currentBlock->endPos) {
|
|
auto exitedBlock = ast.currentBlock;
|
|
auto ancestorStmt = ast.currentBlock->ancestorStatement();
|
|
ast.exitBlock();
|
|
if (ancestorStmt) {
|
|
if (ancestorStmt->type == kIfStmtNode) {
|
|
auto ifStatement = static_cast<IfStmtNode *>(ancestorStmt);
|
|
if (ifStatement->hasElse && exitedBlock == ifStatement->block1.get()) {
|
|
ast.enterBlock(ifStatement->block2.get());
|
|
}
|
|
} else if (ancestorStmt->type == kCaseStmtNode) {
|
|
auto caseStmt = static_cast<CaseStmtNode *>(ancestorStmt);
|
|
auto caseLabel = ast.currentBlock->currentCaseLabel;
|
|
if (caseLabel) {
|
|
if (caseLabel->expect == kCaseExpectOtherwise) {
|
|
ast.currentBlock->currentCaseLabel = nullptr;
|
|
caseStmt->addOtherwise(i);
|
|
size_t otherwiseIndex = bytecodePosMap[caseStmt->potentialOtherwisePos];
|
|
bytecodeArray[otherwiseIndex].translation = Common::SharedPtr<Node>(caseStmt->otherwise);
|
|
ast.enterBlock(caseStmt->otherwise->block.get());
|
|
} else if (caseLabel->expect == kCaseExpectEnd) {
|
|
ast.currentBlock->currentCaseLabel = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
auto translateSize = translateBytecode(bytecode, i);
|
|
i += translateSize;
|
|
}
|
|
}
|
|
|
|
uint32 Handler::translateBytecode(Bytecode &bytecode, uint32 index) {
|
|
if (bytecode.tag == kTagSkip || bytecode.tag == kTagNextRepeatTarget) {
|
|
// This is internal loop logic. Skip it.
|
|
return 1;
|
|
}
|
|
|
|
Common::SharedPtr<Node> translation = nullptr;
|
|
BlockNode *nextBlock = nullptr;
|
|
|
|
switch (bytecode.opcode) {
|
|
case kOpRet:
|
|
case kOpRetFactory:
|
|
if (index == bytecodeArray.size() - 1) {
|
|
ast.root->_endOffset = bytecode.pos;
|
|
ast.currentBlock->_endOffset = bytecode.pos;
|
|
return 1; // end of handler
|
|
}
|
|
translation = Common::SharedPtr<Node>(new ExitStmtNode(bytecode.pos));
|
|
break;
|
|
case kOpPushZero:
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, Common::SharedPtr<Datum>(new Datum(0))));
|
|
break;
|
|
case kOpMul:
|
|
case kOpAdd:
|
|
case kOpSub:
|
|
case kOpDiv:
|
|
case kOpMod:
|
|
case kOpJoinStr:
|
|
case kOpJoinPadStr:
|
|
case kOpLt:
|
|
case kOpLtEq:
|
|
case kOpNtEq:
|
|
case kOpEq:
|
|
case kOpGt:
|
|
case kOpGtEq:
|
|
case kOpAnd:
|
|
case kOpOr:
|
|
case kOpContainsStr:
|
|
case kOpContains0Str:
|
|
{
|
|
auto b = pop();
|
|
auto a = pop();
|
|
translation = Common::SharedPtr<Node>(new BinaryOpNode(bytecode.pos, bytecode.opcode, Common::move(a), Common::move(b)));
|
|
}
|
|
break;
|
|
case kOpInv:
|
|
{
|
|
auto x = pop();
|
|
translation = Common::SharedPtr<Node>(new InverseOpNode(bytecode.pos, Common::move(x)));
|
|
}
|
|
break;
|
|
case kOpNot:
|
|
{
|
|
auto x = pop();
|
|
translation = Common::SharedPtr<Node>(new NotOpNode(bytecode.pos, Common::move(x)));
|
|
}
|
|
break;
|
|
case kOpGetChunk:
|
|
{
|
|
auto string = pop();
|
|
translation = readChunkRef(bytecode.pos, Common::move(string));
|
|
}
|
|
break;
|
|
case kOpHiliteChunk:
|
|
{
|
|
Common::SharedPtr<Node> castID;
|
|
if (script->version >= 500)
|
|
castID = pop();
|
|
auto fieldID = pop();
|
|
auto field = Common::SharedPtr<Node>(new MemberExprNode(bytecode.pos, "field", Common::move(fieldID), Common::move(castID)));
|
|
auto chunk = readChunkRef(bytecode.pos, Common::move(field));
|
|
if (chunk->type == kCommentNode) { // error comment
|
|
translation = chunk;
|
|
} else {
|
|
translation = Common::SharedPtr<Node>(new ChunkHiliteStmtNode(bytecode.pos, Common::move(chunk)));
|
|
}
|
|
}
|
|
break;
|
|
case kOpOntoSpr:
|
|
{
|
|
auto secondSprite = pop();
|
|
auto firstSprite = pop();
|
|
translation = Common::SharedPtr<Node>(new SpriteIntersectsExprNode(bytecode.pos, Common::move(firstSprite), Common::move(secondSprite)));
|
|
}
|
|
break;
|
|
case kOpIntoSpr:
|
|
{
|
|
auto secondSprite = pop();
|
|
auto firstSprite = pop();
|
|
translation = Common::SharedPtr<Node>(new SpriteWithinExprNode(bytecode.pos, Common::move(firstSprite), Common::move(secondSprite)));
|
|
}
|
|
break;
|
|
case kOpGetField:
|
|
{
|
|
Common::SharedPtr<Node> castID;
|
|
if (script->version >= 500)
|
|
castID = pop();
|
|
auto fieldID = pop();
|
|
translation = Common::SharedPtr<Node>(new MemberExprNode(bytecode.pos, "field", Common::move(fieldID), Common::move(castID)));
|
|
}
|
|
break;
|
|
case kOpStartTell:
|
|
{
|
|
auto window = pop();
|
|
auto tellStmt = Common::SharedPtr<TellStmtNode>(new TellStmtNode(bytecode.pos, Common::move(window)));
|
|
translation = tellStmt;
|
|
nextBlock = tellStmt->block.get();
|
|
}
|
|
break;
|
|
case kOpEndTell:
|
|
{
|
|
ast.currentBlock->_endOffset = bytecode.pos;
|
|
ast.exitBlock();
|
|
return 1;
|
|
}
|
|
break;
|
|
case kOpPushList:
|
|
{
|
|
auto list = pop();
|
|
list->getValue()->type = kDatumList;
|
|
translation = list;
|
|
}
|
|
break;
|
|
case kOpPushPropList:
|
|
{
|
|
auto list = pop();
|
|
list->getValue()->type = kDatumPropList;
|
|
translation = list;
|
|
}
|
|
break;
|
|
case kOpSwap:
|
|
if (stack.size() >= 2) {
|
|
SWAP(stack[stack.size() - 1], stack[stack.size() - 2]);
|
|
} else {
|
|
warning("kOpSwap: Stack too small!");
|
|
}
|
|
return 1;
|
|
case kOpPushInt8:
|
|
case kOpPushInt16:
|
|
case kOpPushInt32:
|
|
{
|
|
auto i = Common::SharedPtr<Datum>(new Datum((int)bytecode.obj));
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, Common::move(i)));
|
|
}
|
|
break;
|
|
case kOpPushFloat32:
|
|
{
|
|
auto f = Common::SharedPtr<Datum>(new Datum(*(float *)(&bytecode.obj)));
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, Common::move(f)));
|
|
}
|
|
break;
|
|
case kOpPushArgListNoRet:
|
|
{
|
|
auto argCount = bytecode.obj;
|
|
Common::Array<Common::SharedPtr<Node>> args;
|
|
args.resize(argCount);
|
|
while (argCount) {
|
|
argCount--;
|
|
args[argCount] = pop();
|
|
}
|
|
auto argList = Common::SharedPtr<Datum>(new Datum(kDatumArgListNoRet, args));
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, Common::move(argList)));
|
|
}
|
|
break;
|
|
case kOpPushArgList:
|
|
{
|
|
auto argCount = bytecode.obj;
|
|
Common::Array<Common::SharedPtr<Node>> args;
|
|
args.resize(argCount);
|
|
while (argCount) {
|
|
argCount--;
|
|
args[argCount] = pop();
|
|
}
|
|
auto argList = Common::SharedPtr<Datum>(new Datum(kDatumArgList, args));
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, Common::move(argList)));
|
|
}
|
|
break;
|
|
case kOpPushCons:
|
|
{
|
|
int literalID = bytecode.obj / variableMultiplier();
|
|
if (-1 < literalID && (unsigned)literalID < script->literals.size()) {
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, script->literals[literalID].value));
|
|
} else {
|
|
translation = Common::SharedPtr<Node>(new ErrorNode(bytecode.pos));
|
|
}
|
|
break;
|
|
}
|
|
case kOpPushSymb:
|
|
{
|
|
auto sym = Common::SharedPtr<Datum>(new Datum(kDatumSymbol, getName(bytecode.obj)));
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, Common::move(sym)));
|
|
}
|
|
break;
|
|
case kOpPushVarRef:
|
|
{
|
|
auto ref = Common::SharedPtr<Datum>(new Datum(kDatumVarRef, getName(bytecode.obj)));
|
|
translation = Common::SharedPtr<Node>(new LiteralNode(bytecode.pos, Common::move(ref)));
|
|
}
|
|
break;
|
|
case kOpGetGlobal:
|
|
case kOpGetGlobal2:
|
|
{
|
|
auto name_ = getName(bytecode.obj);
|
|
translation = Common::SharedPtr<Node>(new VarNode(bytecode.pos, name_));
|
|
}
|
|
break;
|
|
case kOpGetProp:
|
|
translation = Common::SharedPtr<Node>(new VarNode(bytecode.pos, getName(bytecode.obj)));
|
|
break;
|
|
case kOpGetParam:
|
|
translation = Common::SharedPtr<Node>(new VarNode(bytecode.pos, getArgumentName(bytecode.obj / variableMultiplier())));
|
|
break;
|
|
case kOpGetLocal:
|
|
translation = Common::SharedPtr<Node>(new VarNode(bytecode.pos, getLocalName(bytecode.obj / variableMultiplier())));
|
|
break;
|
|
case kOpSetGlobal:
|
|
case kOpSetGlobal2:
|
|
{
|
|
auto varName = getName(bytecode.obj);
|
|
auto var = Common::SharedPtr<Node>(new VarNode(bytecode.pos, varName));
|
|
auto value = pop();
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(var), Common::move(value)));
|
|
}
|
|
break;
|
|
case kOpSetProp:
|
|
{
|
|
auto var = Common::SharedPtr<Node>(new VarNode(bytecode.pos, getName(bytecode.obj)));
|
|
auto value = pop();
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(var), Common::move(value)));
|
|
}
|
|
break;
|
|
case kOpSetParam:
|
|
{
|
|
auto var = Common::SharedPtr<Node>(new VarNode(bytecode.pos, getArgumentName(bytecode.obj / variableMultiplier())));
|
|
auto value = pop();
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(var), Common::move(value)));
|
|
}
|
|
break;
|
|
case kOpSetLocal:
|
|
{
|
|
auto var = Common::SharedPtr<Node>(new VarNode(bytecode.pos, getLocalName(bytecode.obj / variableMultiplier())));
|
|
auto value = pop();
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(var), Common::move(value)));
|
|
}
|
|
break;
|
|
case kOpJmp:
|
|
{
|
|
uint32 targetPos = bytecode.pos + bytecode.obj;
|
|
size_t targetIndex = bytecodePosMap[targetPos];
|
|
auto &targetBytecode = bytecodeArray[targetIndex];
|
|
auto ancestorLoop = ast.currentBlock->ancestorLoop();
|
|
if (ancestorLoop) {
|
|
if (bytecodeArray[targetIndex - 1].opcode == kOpEndRepeat && bytecodeArray[targetIndex - 1].ownerLoop == ancestorLoop->startIndex) {
|
|
translation = Common::SharedPtr<Node>(new ExitRepeatStmtNode(bytecode.pos));
|
|
break;
|
|
} else if (bytecodeArray[targetIndex].tag == kTagNextRepeatTarget && bytecodeArray[targetIndex].ownerLoop == ancestorLoop->startIndex) {
|
|
translation = Common::SharedPtr<Node>(new NextRepeatStmtNode(bytecode.pos));
|
|
break;
|
|
}
|
|
}
|
|
auto &nextBytecode = bytecodeArray[index + 1];
|
|
auto ancestorStatement = ast.currentBlock->ancestorStatement();
|
|
if (ancestorStatement && nextBytecode.pos == ast.currentBlock->endPos) {
|
|
if (ancestorStatement->type == kIfStmtNode) {
|
|
auto ifStmt = static_cast<IfStmtNode *>(ancestorStatement);
|
|
if (ast.currentBlock == ifStmt->block1.get()) {
|
|
ifStmt->hasElse = true;
|
|
ifStmt->block2->_startOffset = ifStmt->block1->_endOffset;
|
|
ifStmt->block2->endPos = targetPos;
|
|
ifStmt->block2->_endOffset = targetPos;
|
|
ifStmt->_endOffset = targetPos;
|
|
return 1; // if statement amended, nothing to push
|
|
}
|
|
} else if (ancestorStatement->type == kCaseStmtNode) {
|
|
auto caseStmt = static_cast<CaseStmtNode *>(ancestorStatement);
|
|
caseStmt->potentialOtherwisePos = bytecode.pos;
|
|
caseStmt->endPos = targetPos;
|
|
caseStmt->_endOffset = targetPos;
|
|
targetBytecode.tag = kTagEndCase;
|
|
return 1;
|
|
}
|
|
}
|
|
if (targetBytecode.opcode == kOpPop && targetBytecode.obj == 1) {
|
|
// This is a case statement starting with 'otherwise'
|
|
auto value = pop();
|
|
auto caseStmt = Common::SharedPtr<CaseStmtNode>(new CaseStmtNode(bytecode.pos, Common::move(value)));
|
|
caseStmt->endPos = targetPos;
|
|
caseStmt->_endOffset = targetPos;
|
|
targetBytecode.tag = kTagEndCase;
|
|
caseStmt->addOtherwise(bytecode.pos);
|
|
translation = caseStmt;
|
|
nextBlock = caseStmt->otherwise->block.get();
|
|
break;
|
|
}
|
|
translation = Common::SharedPtr<Node>(new CommentNode(bytecode.pos, "ERROR: Could not identify jmp"));
|
|
}
|
|
break;
|
|
case kOpEndRepeat:
|
|
// This should normally be tagged kTagSkip or kTagNextRepeatTarget and skipped.
|
|
translation = Common::SharedPtr<Node>(new CommentNode(bytecode.pos, "ERROR: Stray endrepeat"));
|
|
break;
|
|
case kOpJmpIfZ:
|
|
{
|
|
uint32 endPos = bytecode.pos + bytecode.obj;
|
|
uint32 endIndex = bytecodePosMap[endPos];
|
|
switch (bytecode.tag) {
|
|
case kTagRepeatWhile:
|
|
{
|
|
auto condition = pop();
|
|
auto loop = Common::SharedPtr<RepeatWhileStmtNode>(new RepeatWhileStmtNode(bytecode.pos, Common::move(condition), bytecode.pos));
|
|
loop->block->endPos = endPos;
|
|
loop->block->_endOffset = endPos;
|
|
loop->_endOffset = endPos;
|
|
translation = loop;
|
|
nextBlock = loop->block.get();
|
|
}
|
|
break;
|
|
case kTagRepeatWithIn:
|
|
{
|
|
auto list = pop();
|
|
Common::String varName = getVarNameFromSet(bytecodeArray[index + 5]);
|
|
auto loop = Common::SharedPtr<RepeatWithInStmtNode>(new RepeatWithInStmtNode(bytecode.pos, varName, Common::move(list), bytecode.pos));
|
|
loop->block->endPos = endPos;
|
|
loop->block->_endOffset = endPos;
|
|
loop->_endOffset = endPos;
|
|
translation = loop;
|
|
nextBlock = loop->block.get();
|
|
}
|
|
break;
|
|
case kTagRepeatWithTo:
|
|
case kTagRepeatWithDownTo:
|
|
{
|
|
bool up = (bytecode.tag == kTagRepeatWithTo);
|
|
auto end = pop();
|
|
auto start = pop();
|
|
auto endRepeat = bytecodeArray[endIndex - 1];
|
|
uint32 conditionStartIndex = bytecodePosMap[endRepeat.pos - endRepeat.obj];
|
|
Common::String varName = getVarNameFromSet(bytecodeArray[conditionStartIndex - 1]);
|
|
auto loop = Common::SharedPtr<RepeatWithToStmtNode>(new RepeatWithToStmtNode(bytecode.pos, varName, Common::move(start), up, Common::move(end), bytecode.pos));
|
|
loop->block->endPos = endPos;
|
|
loop->block->_endOffset = endPos;
|
|
loop->_endOffset = endPos;
|
|
translation = loop;
|
|
nextBlock = loop->block.get();
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
auto condition = pop();
|
|
auto ifStmt = Common::SharedPtr<IfStmtNode>(new IfStmtNode(bytecode.pos, Common::move(condition)));
|
|
ifStmt->block1->endPos = endPos;
|
|
ifStmt->block1->_endOffset = endPos;
|
|
ifStmt->_endOffset = endPos;
|
|
translation = ifStmt;
|
|
nextBlock = ifStmt->block1.get();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case kOpLocalCall:
|
|
{
|
|
auto argList = pop();
|
|
translation = Common::SharedPtr<Node>(new CallNode(bytecode.pos, script->handlers[bytecode.obj].name, Common::move(argList)));
|
|
}
|
|
break;
|
|
case kOpExtCall:
|
|
case kOpTellCall:
|
|
{
|
|
Common::String name_ = getName(bytecode.obj);
|
|
auto argList = pop();
|
|
bool isStatement = (argList->getValue()->type == kDatumArgListNoRet);
|
|
auto &rawArgList = argList->getValue()->l;
|
|
size_t nargs = rawArgList.size();
|
|
if (isStatement && name_ == "sound" && nargs > 0 && rawArgList[0]->type == kLiteralNode && rawArgList[0]->getValue()->type == kDatumSymbol) {
|
|
Common::String cmd = rawArgList[0]->getValue()->s;
|
|
rawArgList.erase(rawArgList.begin());
|
|
translation = Common::SharedPtr<Node>(new SoundCmdStmtNode(bytecode.pos, cmd, Common::move(argList)));
|
|
} else if (isStatement && name_ == "play" && nargs <= 2) {
|
|
translation = Common::SharedPtr<Node>(new PlayCmdStmtNode(bytecode.pos, Common::move(argList)));
|
|
} else {
|
|
translation = Common::SharedPtr<Node>(new CallNode(bytecode.pos, name_, Common::move(argList)));
|
|
}
|
|
}
|
|
break;
|
|
case kOpObjCallV4:
|
|
{
|
|
auto object = readVar(bytecode.obj);
|
|
auto argList = pop();
|
|
auto &rawArgList = argList->getValue()->l;
|
|
if (rawArgList.size() > 0) {
|
|
// first arg is a symbol
|
|
// replace it with a variable
|
|
rawArgList[0] = Common::SharedPtr<Node>(new VarNode(bytecode.pos, rawArgList[0]->getValue()->s));
|
|
}
|
|
translation = Common::SharedPtr<Node>(new ObjCallV4Node(bytecode.pos, Common::move(object), Common::move(argList)));
|
|
}
|
|
break;
|
|
case kOpPut:
|
|
{
|
|
PutType putType = static_cast<PutType>((bytecode.obj >> 4) & 0xF);
|
|
uint32 varType = bytecode.obj & 0xF;
|
|
auto var = readVar(varType);
|
|
auto val = pop();
|
|
translation = Common::SharedPtr<Node>(new PutStmtNode(bytecode.pos, putType, Common::move(var), Common::move(val)));
|
|
}
|
|
break;
|
|
case kOpPutChunk:
|
|
{
|
|
PutType putType = static_cast<PutType>((bytecode.obj >> 4) & 0xF);
|
|
uint32 varType = bytecode.obj & 0xF;
|
|
auto var = readVar(varType);
|
|
auto chunk = readChunkRef(bytecode.pos, Common::move(var));
|
|
auto val = pop();
|
|
if (chunk->type == kCommentNode) { // error comment
|
|
translation = chunk;
|
|
} else {
|
|
translation = Common::SharedPtr<Node>(new PutStmtNode(bytecode.pos, putType, Common::move(chunk), Common::move(val)));
|
|
}
|
|
}
|
|
break;
|
|
case kOpDeleteChunk:
|
|
{
|
|
auto var = readVar(bytecode.obj);
|
|
auto chunk = readChunkRef(bytecode.pos, Common::move(var));
|
|
if (chunk->type == kCommentNode) { // error comment
|
|
translation = chunk;
|
|
} else {
|
|
translation = Common::SharedPtr<Node>(new ChunkDeleteStmtNode(bytecode.pos, Common::move(chunk)));
|
|
}
|
|
}
|
|
break;
|
|
case kOpGet:
|
|
{
|
|
int propertyID = pop()->getValue()->toInt();
|
|
translation = readV4Property(bytecode.pos, bytecode.obj, propertyID);
|
|
}
|
|
break;
|
|
case kOpSet:
|
|
{
|
|
int propertyID = pop()->getValue()->toInt();
|
|
auto value = pop();
|
|
if (bytecode.obj == 0x00 && 0x01 <= propertyID && propertyID <= 0x05 && value->getValue()->type == kDatumString) {
|
|
// This is either a `set eventScript to "script"` or `when event then script` statement.
|
|
// If the script starts with a space, it's probably a when statement.
|
|
// If the script contains a line break, it's definitely a when statement.
|
|
Common::String script_ = value->getValue()->s;
|
|
if (script_.size() > 0 && (script_[0] == ' ' || script_.find('\r') != Common::String::npos)) {
|
|
translation = Common::SharedPtr<Node>(new WhenStmtNode(bytecode.pos, propertyID, script_));
|
|
}
|
|
}
|
|
if (!translation) {
|
|
auto prop = readV4Property(bytecode.pos, bytecode.obj, propertyID);
|
|
if (prop->type == kCommentNode) { // error comment
|
|
translation = prop;
|
|
} else {
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(prop), Common::move(value), true));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case kOpGetMovieProp:
|
|
translation = Common::SharedPtr<Node>(new TheExprNode(bytecode.pos, getName(bytecode.obj)));
|
|
break;
|
|
case kOpSetMovieProp:
|
|
{
|
|
auto value = pop();
|
|
auto prop = Common::SharedPtr<TheExprNode>(new TheExprNode(bytecode.pos, getName(bytecode.obj)));
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(prop), Common::move(value)));
|
|
}
|
|
break;
|
|
case kOpGetObjProp:
|
|
case kOpGetChainedProp:
|
|
{
|
|
auto object = pop();
|
|
translation = Common::SharedPtr<Node>(new ObjPropExprNode(bytecode.pos, Common::move(object), getName(bytecode.obj)));
|
|
}
|
|
break;
|
|
case kOpSetObjProp:
|
|
{
|
|
auto value = pop();
|
|
auto object = pop();
|
|
auto prop = Common::SharedPtr<ObjPropExprNode>(new ObjPropExprNode(bytecode.pos, Common::move(object), getName(bytecode.obj)));
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(prop), Common::move(value)));
|
|
}
|
|
break;
|
|
case kOpPeek:
|
|
{
|
|
// This op denotes the beginning of a 'repeat with ... in list' statement or a case in a cases statement.
|
|
|
|
// In a 'repeat with ... in list' statement, this peeked value is the list.
|
|
// In a cases statement, this is the switch expression.
|
|
|
|
auto prevLabel = ast.currentBlock->currentCaseLabel;
|
|
|
|
// This must be a case. Find the comparison against the switch expression.
|
|
auto originalStackSize = stack.size();
|
|
uint32 currIndex = index + 1;
|
|
Bytecode *currBytecode = &bytecodeArray[currIndex];
|
|
do {
|
|
translateBytecode(*currBytecode, currIndex);
|
|
currIndex += 1;
|
|
currBytecode = &bytecodeArray[currIndex];
|
|
} while (
|
|
currIndex < bytecodeArray.size()
|
|
&& !(stack.size() == originalStackSize + 1 && (currBytecode->opcode == kOpEq || currBytecode->opcode == kOpNtEq))
|
|
);
|
|
if (currIndex >= bytecodeArray.size()) {
|
|
bytecode.translation = Common::SharedPtr<Node>(new CommentNode(bytecode.pos, "ERROR: Expected eq or nteq!"));
|
|
ast.addStatement(bytecode.translation);
|
|
return currIndex - index + 1;
|
|
}
|
|
|
|
// If the comparison is <>, this is followed by another, equivalent case.
|
|
// (e.g. this could be case1 in `case1, case2: statement`)
|
|
bool notEq = (currBytecode->opcode == kOpNtEq);
|
|
Common::SharedPtr<Node> caseValue = pop(); // This is the value the switch expression is compared against.
|
|
|
|
currIndex += 1;
|
|
currBytecode = &bytecodeArray[currIndex];
|
|
if (currIndex >= bytecodeArray.size() || currBytecode->opcode != kOpJmpIfZ) {
|
|
bytecode.translation = Common::SharedPtr<Node>(new CommentNode(bytecode.pos, "ERROR: Expected jmpifz!"));
|
|
ast.addStatement(bytecode.translation);
|
|
return currIndex - index + 1;
|
|
}
|
|
|
|
auto &jmpifz = *currBytecode;
|
|
auto jmpPos = jmpifz.pos + jmpifz.obj;
|
|
size_t targetIndex = bytecodePosMap[jmpPos];
|
|
auto &targetBytecode = bytecodeArray[targetIndex];
|
|
auto &prevFromTarget = bytecodeArray[targetIndex - 1];
|
|
CaseExpect expect;
|
|
if (notEq) {
|
|
expect = kCaseExpectOr; // Expect an equivalent case after this one.
|
|
} else if (targetBytecode.opcode == kOpPeek) {
|
|
expect = kCaseExpectNext; // Expect a different case after this one.
|
|
} else if (targetBytecode.opcode == kOpPop
|
|
&& targetBytecode.obj == 1
|
|
&& (prevFromTarget.opcode != kOpJmp || prevFromTarget.pos + prevFromTarget.obj == targetBytecode.pos)) {
|
|
expect = kCaseExpectEnd; // Expect the end of the switch statement.
|
|
} else {
|
|
expect = kCaseExpectOtherwise; // Expect an 'otherwise' block.
|
|
}
|
|
|
|
auto currLabel = Common::SharedPtr<CaseLabelNode>(new CaseLabelNode(bytecode.pos, Common::move(caseValue), expect));
|
|
jmpifz.translation = currLabel;
|
|
ast.currentBlock->currentCaseLabel = currLabel.get();
|
|
|
|
if (!prevLabel) {
|
|
auto peekedValue = pop();
|
|
auto caseStmt = Common::SharedPtr<CaseStmtNode>(new CaseStmtNode(bytecode.pos, Common::move(peekedValue)));
|
|
caseStmt->firstLabel = currLabel;
|
|
currLabel->parent = caseStmt.get();
|
|
bytecode.translation = caseStmt;
|
|
ast.addStatement(caseStmt);
|
|
} else if (prevLabel->expect == kCaseExpectOr) {
|
|
prevLabel->nextOr = currLabel;
|
|
currLabel->parent = prevLabel;
|
|
} else if (prevLabel->expect == kCaseExpectNext) {
|
|
prevLabel->nextLabel = currLabel;
|
|
currLabel->parent = prevLabel;
|
|
}
|
|
|
|
// The block doesn't start until the after last equivalent case,
|
|
// so don't create a block yet if we're expecting an equivalent case.
|
|
if (currLabel->expect != kCaseExpectOr) {
|
|
currLabel->block = Common::SharedPtr<BlockNode>(new BlockNode(bytecode.pos));
|
|
currLabel->block->parent = currLabel.get();
|
|
currLabel->block->endPos = jmpPos;
|
|
currLabel->block->_endOffset = jmpPos;
|
|
currLabel->_endOffset = jmpPos;
|
|
ast.enterBlock(currLabel->block.get());
|
|
}
|
|
|
|
return currIndex - index + 1;
|
|
}
|
|
break;
|
|
case kOpPop:
|
|
{
|
|
// Pop instructions in 'repeat with in' loops are tagged kTagSkip and skipped.
|
|
if (bytecode.tag == kTagEndCase) {
|
|
// We've already recognized this as the end of a case statement.
|
|
// Attach an 'end case' node for the summary only.
|
|
bytecode.translation = Common::SharedPtr<EndCaseNode>();
|
|
return 1;
|
|
}
|
|
if (bytecode.obj == 1 && stack.size() == 1) {
|
|
// We have an unused value on the stack, so this must be the end
|
|
// of a case statement with no labels.
|
|
auto value = pop();
|
|
translation = Common::SharedPtr<Node>(new CaseStmtNode(bytecode.pos, Common::move(value)));
|
|
break;
|
|
}
|
|
// Otherwise, this pop instruction occurs before a 'return' within
|
|
// a case statement. No translation needed.
|
|
return 1;
|
|
}
|
|
break;
|
|
case kOpTheBuiltin:
|
|
{
|
|
pop(); // empty arglist
|
|
translation = Common::SharedPtr<Node>(new TheExprNode(bytecode.pos, getName(bytecode.obj)));
|
|
}
|
|
break;
|
|
case kOpObjCall:
|
|
{
|
|
Common::String method = getName(bytecode.obj);
|
|
auto argList = pop();
|
|
auto &rawArgList = argList->getValue()->l;
|
|
size_t nargs = rawArgList.size();
|
|
if (method == "getAt" && nargs == 2) {
|
|
// obj.getAt(i) => obj[i]
|
|
auto obj = rawArgList[0];
|
|
auto prop = rawArgList[1];
|
|
translation = Common::SharedPtr<Node>(new ObjBracketExprNode(bytecode.pos, Common::move(obj), Common::move(prop)));
|
|
} else if (method == "setAt" && nargs == 3) {
|
|
// obj.setAt(i) => obj[i] = val
|
|
auto obj = rawArgList[0];
|
|
auto prop = rawArgList[1];
|
|
auto val = rawArgList[2];
|
|
Common::SharedPtr<Node> propExpr = Common::SharedPtr<Node>(new ObjBracketExprNode(bytecode.pos, Common::move(obj), Common::move(prop)));
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(propExpr), Common::move(val)));
|
|
} else if ((method == "getProp" || method == "getPropRef") && (nargs == 3 || nargs == 4) && rawArgList[1]->getValue()->type == kDatumSymbol) {
|
|
// obj.getProp(#prop, i) => obj.prop[i]
|
|
// obj.getProp(#prop, i, i2) => obj.prop[i..i2]
|
|
auto obj = rawArgList[0];
|
|
Common::String propName = rawArgList[1]->getValue()->s;
|
|
auto i = rawArgList[2];
|
|
auto i2 = (nargs == 4) ? rawArgList[3] : nullptr;
|
|
translation = Common::SharedPtr<Node>(new ObjPropIndexExprNode(bytecode.pos, Common::move(obj), propName, Common::move(i), Common::move(i2)));
|
|
} else if (method == "setProp" && (nargs == 4 || nargs == 5) && rawArgList[1]->getValue()->type == kDatumSymbol) {
|
|
// obj.setProp(#prop, i, val) => obj.prop[i] = val
|
|
// obj.setProp(#prop, i, i2, val) => obj.prop[i..i2] = val
|
|
auto obj = rawArgList[0];
|
|
Common::String propName = rawArgList[1]->getValue()->s;
|
|
auto i = rawArgList[2];
|
|
auto i2 = (nargs == 5) ? rawArgList[3] : nullptr;
|
|
auto propExpr = Common::SharedPtr<ObjPropIndexExprNode>(new ObjPropIndexExprNode(bytecode.pos, Common::move(obj), propName, Common::move(i), Common::move(i2)));
|
|
auto val = rawArgList[nargs - 1];
|
|
translation = Common::SharedPtr<Node>(new AssignmentStmtNode(bytecode.pos, Common::move(propExpr), Common::move(val)));
|
|
} else if (method == "count" && nargs == 2 && rawArgList[1]->getValue()->type == kDatumSymbol) {
|
|
// obj.count(#prop) => obj.prop.count
|
|
auto obj = rawArgList[0];
|
|
Common::String propName = rawArgList[1]->getValue()->s;
|
|
auto propExpr = Common::SharedPtr<ObjPropExprNode>(new ObjPropExprNode(bytecode.pos, Common::move(obj), propName));
|
|
translation = Common::SharedPtr<Node>(new ObjPropExprNode(bytecode.pos, Common::move(propExpr), "count"));
|
|
} else if ((method == "setContents" || method == "setContentsAfter" || method == "setContentsBefore") && nargs == 2) {
|
|
// var.setContents(val) => put val into var
|
|
// var.setContentsAfter(val) => put val after var
|
|
// var.setContentsBefore(val) => put val before var
|
|
PutType putType;
|
|
if (method == "setContents") {
|
|
putType = kPutInto;
|
|
} else if (method == "setContentsAfter") {
|
|
putType = kPutAfter;
|
|
} else {
|
|
putType = kPutBefore;
|
|
}
|
|
auto var = rawArgList[0];
|
|
auto val = rawArgList[1];
|
|
translation = Common::SharedPtr<Node>(new PutStmtNode(bytecode.pos, putType, Common::move(var), Common::move(val)));
|
|
} else if (method == "hilite" && nargs == 1) {
|
|
// chunk.hilite() => hilite chunk
|
|
auto chunk = rawArgList[0];
|
|
translation = Common::SharedPtr<Node>(new ChunkHiliteStmtNode(bytecode.pos, chunk));
|
|
} else if (method == "delete" && nargs == 1) {
|
|
// chunk.delete() => delete chunk
|
|
auto chunk = rawArgList[0];
|
|
translation = Common::SharedPtr<Node>(new ChunkDeleteStmtNode(bytecode.pos, chunk));
|
|
} else {
|
|
translation = Common::SharedPtr<Node>(new ObjCallNode(bytecode.pos, method, Common::move(argList)));
|
|
}
|
|
}
|
|
break;
|
|
case kOpPushChunkVarRef:
|
|
translation = readVar(bytecode.obj);
|
|
break;
|
|
case kOpGetTopLevelProp:
|
|
{
|
|
auto name_ = getName(bytecode.obj);
|
|
translation = Common::SharedPtr<VarNode>(new VarNode(bytecode.pos, name_));
|
|
}
|
|
break;
|
|
case kOpNewObj:
|
|
{
|
|
auto objType = getName(bytecode.obj);
|
|
auto objArgs = pop();
|
|
translation = Common::SharedPtr<NewObjNode>(new NewObjNode(bytecode.pos, objType, Common::move(objArgs)));
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
auto commentText = StandardNames::getOpcodeName(bytecode.opID);
|
|
if (bytecode.opcode >= 0x40)
|
|
commentText += Common::String::format(" %d", bytecode.obj);
|
|
translation = Common::SharedPtr<CommentNode>(new CommentNode(bytecode.pos, commentText));
|
|
stack.clear(); // Clear stack so later bytecode won't be too screwed up
|
|
}
|
|
}
|
|
|
|
if (!translation)
|
|
translation = Common::SharedPtr<ErrorNode>(new ErrorNode(bytecode.pos));
|
|
|
|
bytecode.translation = translation;
|
|
if (translation->isExpression) {
|
|
stack.push_back(Common::move(translation));
|
|
} else {
|
|
ast.addStatement(Common::move(translation));
|
|
}
|
|
|
|
if (nextBlock)
|
|
ast.enterBlock(nextBlock);
|
|
|
|
return 1;
|
|
}
|
|
|
|
Common::String posToString(int32 pos) {
|
|
return Common::String::format("[%3d]", pos);
|
|
}
|
|
|
|
void Handler::writeBytecodeText(CodeWriterVisitor &code) const {
|
|
bool isMethod = script->isFactory();
|
|
|
|
if (!isGenericEvent) {
|
|
if (isMethod) {
|
|
code.write("method ");
|
|
} else {
|
|
code.write("on ");
|
|
}
|
|
code.write(name);
|
|
if (argumentNames.size() > 0) {
|
|
code.write(" ");
|
|
for (size_t i = 0; i < argumentNames.size(); i++) {
|
|
if (i > 0)
|
|
code.write(", ");
|
|
code.write(argumentNames[i]);
|
|
}
|
|
}
|
|
code.writeLine();
|
|
code.indent();
|
|
}
|
|
for (auto &bytecode : bytecodeArray) {
|
|
code.write(posToString(bytecode.pos));
|
|
code.write(" ");
|
|
code.write(StandardNames::getOpcodeName(bytecode.opID));
|
|
switch (bytecode.opcode) {
|
|
case kOpJmp:
|
|
case kOpJmpIfZ:
|
|
code.write(" ");
|
|
code.write(posToString(bytecode.pos + bytecode.obj));
|
|
break;
|
|
case kOpEndRepeat:
|
|
code.write(" ");
|
|
code.write(posToString(bytecode.pos - bytecode.obj));
|
|
break;
|
|
case kOpPushFloat32:
|
|
code.write(" ");
|
|
code.write(Common::String::format("%g", (*(const float *)(&bytecode.obj))));
|
|
break;
|
|
default:
|
|
if (bytecode.opID > 0x40) {
|
|
code.write(" ");
|
|
code.write(Common::String::format("%d", bytecode.obj));
|
|
}
|
|
break;
|
|
}
|
|
if (bytecode.translation) {
|
|
code.write(" ...");
|
|
while (code.lineWidth() < 49) {
|
|
code.write(".");
|
|
}
|
|
code.write(" ");
|
|
if (bytecode.translation->isExpression) {
|
|
code.write("<");
|
|
}
|
|
bytecode.translation->accept(code);
|
|
if (bytecode.translation->isExpression) {
|
|
code.write(">");
|
|
}
|
|
}
|
|
code.writeLine();
|
|
}
|
|
if (!isGenericEvent) {
|
|
code.unindent();
|
|
if (!isMethod) {
|
|
code.writeLine("end");
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace LingoDec
|