Initial commit
This commit is contained in:
309
engines/stark/tools/abstractsyntaxtree.cpp
Normal file
309
engines/stark/tools/abstractsyntaxtree.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "engines/stark/tools/abstractsyntaxtree.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Tools {
|
||||
|
||||
ASTNode::ASTNode(ASTNode *parent) :
|
||||
_parent(parent) {
|
||||
|
||||
}
|
||||
|
||||
ASTNode::~ASTNode() {
|
||||
|
||||
}
|
||||
|
||||
void ASTNode::printWithDepth(uint depth, const Common::String &string) const {
|
||||
Common::String prefix;
|
||||
for (uint i = 0; i < depth; i++) {
|
||||
prefix += "\t";
|
||||
}
|
||||
|
||||
debug("%s%s", prefix.c_str(), string.c_str());
|
||||
}
|
||||
|
||||
void ASTNode::findSuccessors(ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
|
||||
findSuccessorsIntern(this, follower, trueBranch, falseBranch);
|
||||
}
|
||||
|
||||
ASTBlock::ASTBlock(ASTNode *parent) :
|
||||
ASTNode(parent) {
|
||||
|
||||
}
|
||||
|
||||
ASTBlock::~ASTBlock() {
|
||||
for (uint i = 0; i < _children.size(); i++) {
|
||||
delete _children[i];
|
||||
}
|
||||
}
|
||||
|
||||
void ASTBlock::addNode(ASTNode *node) {
|
||||
_children.push_back(node);
|
||||
}
|
||||
|
||||
void ASTBlock::print(uint depth, DefinitionRegistry *definitions) {
|
||||
for (uint i = 0; i < _children.size(); i++) {
|
||||
_children[i]->print(depth, definitions);
|
||||
}
|
||||
}
|
||||
|
||||
Common::Array<const ASTCommand *> ASTBlock::listCommands(uint16 index) const {
|
||||
Common::Array<const ASTCommand *> list;
|
||||
|
||||
for (uint i = 0; i < _children.size(); i++) {
|
||||
list.push_back(_children[i]->listCommands(index));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void ASTBlock::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
|
||||
if (node == this) {
|
||||
if (_parent) {
|
||||
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _children.size() - 1; i++) {
|
||||
if (node == _children[i]) {
|
||||
*follower = _children[i+1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node == _children.back()) {
|
||||
if (_parent) {
|
||||
_parent->findSuccessorsIntern(this, follower, trueBranch, falseBranch);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
error("Unknown node");
|
||||
}
|
||||
|
||||
const ASTCommand *ASTBlock::getFirstCommand() const {
|
||||
if (!_children.empty()) {
|
||||
return _children[0]->getFirstCommand();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ASTCommand::ASTCommand(ASTNode *parent, Command *command, DefinitionRegistry *definitions) :
|
||||
ASTNode(parent),
|
||||
Command(command) {
|
||||
_arguments = command->getEffectiveArguments();
|
||||
|
||||
for (uint i = 0; i < _arguments.size(); i++) {
|
||||
if (_arguments[i].type == Resources::Command::Argument::kTypeResourceReference) {
|
||||
definitions->registerReference(_arguments[i].referenceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASTCommand::print(uint depth, DefinitionRegistry *definitions) {
|
||||
printWithDepth(depth, callString(definitions));
|
||||
}
|
||||
|
||||
Common::String ASTCommand::callString(DefinitionRegistry *definitions) {
|
||||
return Common::String::format("%s(%s)", _subTypeDesc->name, describeArguments(definitions).c_str());
|
||||
}
|
||||
|
||||
Common::Array<const ASTCommand *> ASTCommand::listCommands(uint16 index) const {
|
||||
Common::Array<const ASTCommand *> list;
|
||||
|
||||
if (_index == index) {
|
||||
list.push_back(this);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void ASTCommand::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
|
||||
assert(node == this);
|
||||
|
||||
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
|
||||
}
|
||||
|
||||
const ASTCommand *ASTCommand::getFirstCommand() const {
|
||||
return this;
|
||||
}
|
||||
|
||||
ASTCondition::ASTCondition(ASTNode *parent) :
|
||||
ASTNode(parent),
|
||||
condition(nullptr),
|
||||
invertedCondition(false),
|
||||
thenBlock(nullptr),
|
||||
elseBlock(nullptr) {
|
||||
|
||||
}
|
||||
|
||||
ASTCondition::~ASTCondition() {
|
||||
delete condition;
|
||||
delete thenBlock;
|
||||
delete elseBlock;
|
||||
}
|
||||
|
||||
void ASTCondition::print(uint depth, DefinitionRegistry *definitions) {
|
||||
Common::String ifHeader = Common::String::format("if (%s%s) {", invertedCondition ? "!" : "",
|
||||
condition->callString(definitions).c_str());
|
||||
printWithDepth(depth, ifHeader);
|
||||
|
||||
thenBlock->print(depth + 1, definitions);
|
||||
|
||||
if (elseBlock) {
|
||||
printWithDepth(depth, "} else {");
|
||||
elseBlock->print(depth + 1, definitions);
|
||||
}
|
||||
printWithDepth(depth, "}");
|
||||
}
|
||||
|
||||
Common::Array<const ASTCommand *> ASTCondition::listCommands(uint16 index) const {
|
||||
Common::Array<const ASTCommand *> list;
|
||||
|
||||
list.push_back(condition->listCommands(index));
|
||||
list.push_back(thenBlock->listCommands(index));
|
||||
if (elseBlock) {
|
||||
list.push_back(elseBlock->listCommands(index));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void ASTCondition::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
|
||||
if (node == this) {
|
||||
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node == condition) {
|
||||
ASTNode *nextNode = nullptr;
|
||||
if (!elseBlock) {
|
||||
_parent->findSuccessorsIntern(this, &nextNode, nullptr, nullptr);
|
||||
}
|
||||
|
||||
if (!invertedCondition) {
|
||||
*trueBranch = thenBlock;
|
||||
*falseBranch = elseBlock ? elseBlock : nextNode;
|
||||
} else {
|
||||
*trueBranch = elseBlock ? elseBlock : nextNode;
|
||||
*falseBranch = thenBlock;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (node == thenBlock) {
|
||||
_parent->findSuccessorsIntern(this, follower, trueBranch, falseBranch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node == elseBlock) {
|
||||
_parent->findSuccessorsIntern(this, follower, trueBranch, falseBranch);
|
||||
return;
|
||||
}
|
||||
|
||||
error("Unknown node");
|
||||
}
|
||||
|
||||
const ASTCommand *ASTCondition::getFirstCommand() const {
|
||||
return condition->getFirstCommand();
|
||||
}
|
||||
|
||||
ASTLoop::ASTLoop(ASTNode *parent) :
|
||||
ASTNode(parent),
|
||||
condition(nullptr),
|
||||
invertedCondition(false),
|
||||
loopBlock(nullptr) {
|
||||
|
||||
}
|
||||
|
||||
ASTLoop::~ASTLoop() {
|
||||
delete condition;
|
||||
delete loopBlock;
|
||||
}
|
||||
|
||||
void ASTLoop::print(uint depth, DefinitionRegistry *definitions) {
|
||||
Common::String loopHeader;
|
||||
if (condition) {
|
||||
loopHeader = Common::String::format("while (%s%s) {", invertedCondition ? "!" : "",
|
||||
condition->callString(definitions).c_str());
|
||||
} else {
|
||||
loopHeader = "loop {";
|
||||
}
|
||||
printWithDepth(depth, loopHeader);
|
||||
|
||||
loopBlock->print(depth + 1, definitions);
|
||||
|
||||
printWithDepth(depth, "}");
|
||||
}
|
||||
|
||||
Common::Array<const ASTCommand *> ASTLoop::listCommands(uint16 index) const {
|
||||
Common::Array<const ASTCommand *> list;
|
||||
|
||||
if (condition) {
|
||||
list.push_back(condition->listCommands(index));
|
||||
}
|
||||
list.push_back(loopBlock->listCommands(index));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void ASTLoop::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
|
||||
if (node == this) {
|
||||
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node == condition) {
|
||||
ASTNode *nextNode = nullptr;
|
||||
_parent->findSuccessorsIntern(this, &nextNode, nullptr, nullptr);
|
||||
|
||||
if (!invertedCondition) {
|
||||
*trueBranch = loopBlock;
|
||||
*falseBranch = nextNode;
|
||||
} else {
|
||||
*trueBranch = nextNode;
|
||||
*falseBranch = loopBlock;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (node == loopBlock) {
|
||||
*follower = condition ? (ASTNode *)condition : (ASTNode *)loopBlock;
|
||||
return;
|
||||
}
|
||||
|
||||
error("Unknown node");
|
||||
}
|
||||
|
||||
const ASTCommand *ASTLoop::getFirstCommand() const {
|
||||
return condition ? condition->getFirstCommand() : loopBlock->getFirstCommand();
|
||||
}
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
146
engines/stark/tools/abstractsyntaxtree.h
Normal file
146
engines/stark/tools/abstractsyntaxtree.h
Normal file
@@ -0,0 +1,146 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STARK_TOOLS_ABSTRACT_SYNTAX_TREE_H
|
||||
#define STARK_TOOLS_ABSTRACT_SYNTAX_TREE_H
|
||||
|
||||
#include "common/array.h"
|
||||
|
||||
#include "engines/stark/tools/command.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Tools {
|
||||
|
||||
struct ASTCommand;
|
||||
|
||||
/**
|
||||
* Base Abstract Syntax Tree node
|
||||
*
|
||||
* The abstract syntax tree directly maps the script source code.
|
||||
*/
|
||||
struct ASTNode {
|
||||
ASTNode(ASTNode *parent);
|
||||
virtual ~ASTNode();
|
||||
|
||||
/** Print the script source code for this node and its children */
|
||||
virtual void print(uint depth, DefinitionRegistry *definitions) = 0;
|
||||
|
||||
/** Recursively list all the commands in the tree with the requested index */
|
||||
virtual Common::Array<const ASTCommand *> listCommands(uint16 index) const = 0;
|
||||
|
||||
/** Find the successors of a node, either the direct follower or the condition branches */
|
||||
void findSuccessors(ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const;
|
||||
virtual void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const = 0;
|
||||
|
||||
/** Find the first command to be executed when running this job */
|
||||
virtual const ASTCommand *getFirstCommand() const = 0;
|
||||
|
||||
protected:
|
||||
void printWithDepth(uint depth, const Common::String &string) const;
|
||||
|
||||
ASTNode *_parent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Command AST Node
|
||||
*
|
||||
* Commands are leaf AST nodes. They represent an engine command call.
|
||||
*/
|
||||
struct ASTCommand : public ASTNode, public Command {
|
||||
ASTCommand(ASTNode *parent, Command *command, DefinitionRegistry *definitions);
|
||||
|
||||
// ASTNode API
|
||||
void print(uint depth, DefinitionRegistry *definitions) override;
|
||||
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
|
||||
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
|
||||
const ASTCommand *getFirstCommand() const override;
|
||||
|
||||
/** Build a script source code call for this command */
|
||||
Common::String callString(DefinitionRegistry *definitions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Block AST Node
|
||||
*
|
||||
* A list of consecutive script expressions.
|
||||
*/
|
||||
struct ASTBlock : public ASTNode {
|
||||
ASTBlock(ASTNode *parent);
|
||||
~ASTBlock() override;
|
||||
|
||||
// ASTNode API
|
||||
void print(uint depth, DefinitionRegistry *definitions) override;
|
||||
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
|
||||
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
|
||||
const ASTCommand *getFirstCommand() const override;
|
||||
|
||||
/** Append a child expression to this block */
|
||||
void addNode(ASTNode *node);
|
||||
|
||||
private:
|
||||
Common::Array<ASTNode *> _children;
|
||||
};
|
||||
|
||||
/**
|
||||
* Condition AST Node
|
||||
*
|
||||
* An if / then / else branching condition.
|
||||
*/
|
||||
struct ASTCondition : public ASTNode {
|
||||
ASTCondition(ASTNode *parent);
|
||||
~ASTCondition() override;
|
||||
|
||||
// ASTNode API
|
||||
void print(uint depth, DefinitionRegistry *definitions) override;
|
||||
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
|
||||
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
|
||||
const ASTCommand *getFirstCommand() const override;
|
||||
|
||||
ASTCommand *condition;
|
||||
bool invertedCondition;
|
||||
ASTBlock *thenBlock;
|
||||
ASTBlock *elseBlock;
|
||||
};
|
||||
|
||||
/**
|
||||
* Loop AST Node
|
||||
*
|
||||
* A while loop.
|
||||
*/
|
||||
struct ASTLoop : public ASTNode {
|
||||
ASTLoop(ASTNode *parent);
|
||||
~ASTLoop() override;
|
||||
|
||||
// ASTNode API
|
||||
void print(uint depth, DefinitionRegistry *definitions) override;
|
||||
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
|
||||
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
|
||||
const ASTCommand *getFirstCommand() const override;
|
||||
|
||||
ASTCommand *condition;
|
||||
bool invertedCondition;
|
||||
ASTBlock *loopBlock;
|
||||
};
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
|
||||
#endif // STARK_TOOLS_ABSTRACT_SYNTAX_TREE_H
|
||||
328
engines/stark/tools/block.cpp
Normal file
328
engines/stark/tools/block.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "engines/stark/tools/block.h"
|
||||
|
||||
#include "engines/stark/tools/command.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Tools {
|
||||
|
||||
Block::Block() :
|
||||
_follower(nullptr),
|
||||
_trueBranch(nullptr),
|
||||
_falseBranch(nullptr),
|
||||
_controlStructure(nullptr),
|
||||
_infiniteLoopStart(false) {
|
||||
|
||||
}
|
||||
|
||||
void Block::appendCommand(CFGCommand *command) {
|
||||
_commands.push_back(command);
|
||||
command->setBlock(this);
|
||||
}
|
||||
|
||||
bool Block::isEmpty() const {
|
||||
return _commands.empty();
|
||||
}
|
||||
|
||||
void Block::print() const {
|
||||
for (uint i = 0; i < _commands.size(); i++) {
|
||||
_commands[i]->printCall();
|
||||
}
|
||||
|
||||
if (_controlStructure) {
|
||||
switch (_controlStructure->type) {
|
||||
case ControlStructure::kTypeIf:
|
||||
debug("if%s: %d else: %d next: %d",
|
||||
_controlStructure->invertedCondition ? " not" : "",
|
||||
_controlStructure->thenHead->getFirstCommandIndex(),
|
||||
_controlStructure->elseHead ? _controlStructure->elseHead->getFirstCommandIndex() : -1,
|
||||
_controlStructure->next ? _controlStructure->next->getFirstCommandIndex() : -1);
|
||||
break;
|
||||
case ControlStructure::kTypeWhile:
|
||||
debug("while%s: %d next: %d",
|
||||
_controlStructure->invertedCondition ? " not" : "",
|
||||
_controlStructure->loopHead->getFirstCommandIndex(),
|
||||
_controlStructure->next->getFirstCommandIndex());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_infiniteLoopStart) {
|
||||
debug("infinite loop start: %d", getFirstCommandIndex());
|
||||
}
|
||||
|
||||
if (isCondition() && !_controlStructure) {
|
||||
debug("unrecognized control flow");
|
||||
}
|
||||
}
|
||||
|
||||
void Block::setBranches(Block *trueBranch, Block *falseBranch) {
|
||||
_trueBranch = trueBranch;
|
||||
_falseBranch = falseBranch;
|
||||
|
||||
trueBranch->addPredecessor(this);
|
||||
falseBranch->addPredecessor(this);
|
||||
}
|
||||
|
||||
void Block::setFollower(Block *follower) {
|
||||
_follower = follower;
|
||||
follower->addPredecessor(this);
|
||||
}
|
||||
|
||||
void Block::addPredecessor(Block *predecessor) {
|
||||
_predecessors.push_back(predecessor);
|
||||
}
|
||||
|
||||
bool Block::isCondition() const {
|
||||
return _trueBranch != nullptr && _falseBranch != nullptr;
|
||||
}
|
||||
|
||||
bool Block::hasPredecessor(Block *predecessor) const {
|
||||
Common::Array<const Block *> visited;
|
||||
return hasPredecessorIntern(visited, predecessor);
|
||||
}
|
||||
|
||||
bool Block::hasPredecessorIntern(Common::Array<const Block *> &visited, Block *predecessor) const {
|
||||
visited.push_back(this);
|
||||
|
||||
if (isInfiniteLoopStart()) {
|
||||
// Don't follow infinite loops
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _predecessors.size(); i++) {
|
||||
if (_predecessors[i] == predecessor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool alreadyVisited = Common::find(visited.begin(), visited.end(), _predecessors[i]) != visited.end();
|
||||
if (!alreadyVisited && _predecessors[i]->hasPredecessorIntern(visited, predecessor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Block::hasSuccessor(Block *successor) const {
|
||||
Common::Array<const Block *> visited;
|
||||
return hasSuccessorIntern(visited, successor);
|
||||
}
|
||||
|
||||
bool Block::hasSuccessorIntern(Common::Array<const Block *> &visited, Block *successor) const {
|
||||
visited.push_back(this);
|
||||
|
||||
if (this == successor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool followerHasSuccessor = hasChildSuccessorIntern(visited, _follower, successor);
|
||||
bool trueBranchHasSuccessor = hasChildSuccessorIntern(visited, _trueBranch, successor);
|
||||
bool falseBranchHasSuccessor = hasChildSuccessorIntern(visited, _falseBranch, successor);
|
||||
|
||||
return followerHasSuccessor || trueBranchHasSuccessor || falseBranchHasSuccessor;
|
||||
}
|
||||
|
||||
bool Block::hasChildSuccessorIntern(Common::Array<const Block *> &visited, Block *child, Block *successor) const {
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool alreadyVisited = Common::find(visited.begin(), visited.end(), child) != visited.end();
|
||||
return !alreadyVisited && child->hasSuccessorIntern(visited, successor);
|
||||
}
|
||||
|
||||
Block *Block::findMergePoint(Block *other) {
|
||||
// TODO: Find the closest merge point? How to define that notion?
|
||||
Common::Array<const Block *> visited;
|
||||
return findMergePointIntern(visited, other);
|
||||
}
|
||||
|
||||
Block *Block::findMergePointIntern(Common::Array<const Block *> &visited, Block *other) {
|
||||
visited.push_back(this);
|
||||
|
||||
if (other == this) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (hasPredecessor(other)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Block *mergePoint = findChildMergePoint(visited, _follower, other);
|
||||
if (mergePoint) {
|
||||
return mergePoint;
|
||||
}
|
||||
|
||||
mergePoint = findChildMergePoint(visited, _trueBranch, other);
|
||||
if (mergePoint) {
|
||||
return mergePoint;
|
||||
}
|
||||
|
||||
mergePoint = findChildMergePoint(visited, _falseBranch, other);
|
||||
if (mergePoint) {
|
||||
return mergePoint;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Block *Block::findChildMergePoint(Common::Array<const Block *> &visited, Block *child, Block *other) const {
|
||||
if (child) {
|
||||
bool alreadyVisited = Common::find(visited.begin(), visited.end(), child) != visited.end();
|
||||
if (!alreadyVisited) {
|
||||
return child->findMergePointIntern(visited, other);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Block::checkAllBranchesConverge(Block *junction) const {
|
||||
// Check if there is at least one path to the junction node
|
||||
if (!hasSuccessor(junction)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check all the successors go to the junction point or loop infinitely
|
||||
Common::Array<const Block *> visited;
|
||||
return checkAllBranchesConvergeIntern(visited, junction);
|
||||
}
|
||||
|
||||
bool Block::checkAllBranchesConvergeIntern(Common::Array<const Block *> &visited, Block *junction) const {
|
||||
visited.push_back(this);
|
||||
|
||||
if (this == junction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_follower && !_trueBranch && !_falseBranch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isInfiniteLoopStart()) {
|
||||
// Don't follow infinite loops
|
||||
return false;
|
||||
}
|
||||
|
||||
bool followerConverges = checkChildConvergeIntern(visited, _follower, junction);
|
||||
bool trueBranchConverges = checkChildConvergeIntern(visited, _trueBranch, junction);
|
||||
bool falseBranchConverges = checkChildConvergeIntern(visited, _falseBranch, junction);
|
||||
|
||||
return followerConverges && trueBranchConverges && falseBranchConverges;
|
||||
}
|
||||
|
||||
bool Block::checkChildConvergeIntern(Common::Array<const Block *> &visited, Block *child, Block *junction) const {
|
||||
if (child) {
|
||||
bool alreadyVisited = Common::find(visited.begin(), visited.end(), child) != visited.end();
|
||||
if (!alreadyVisited) {
|
||||
return child->checkAllBranchesConvergeIntern(visited, junction);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Block *Block::getFollower() const {
|
||||
return _follower;
|
||||
}
|
||||
|
||||
Block *Block::getTrueBranch() const {
|
||||
return _trueBranch;
|
||||
}
|
||||
|
||||
Block *Block::getFalseBranch() const {
|
||||
return _falseBranch;
|
||||
}
|
||||
|
||||
uint16 Block::getFirstCommandIndex() const {
|
||||
return _commands[0]->getIndex();
|
||||
}
|
||||
|
||||
bool Block::hasControlStructure() const {
|
||||
return _controlStructure != nullptr;
|
||||
}
|
||||
|
||||
void Block::setControlStructure(ControlStructure *controlStructure) {
|
||||
_controlStructure = controlStructure;
|
||||
}
|
||||
|
||||
ControlStructure *Block::getControlStructure() const {
|
||||
return _controlStructure;
|
||||
}
|
||||
|
||||
void Block::setInfiniteLoopStart(bool infiniteLoopStart) {
|
||||
_infiniteLoopStart = infiniteLoopStart;
|
||||
}
|
||||
|
||||
bool Block::isInfiniteLoopStart() const {
|
||||
return _infiniteLoopStart;
|
||||
}
|
||||
|
||||
Common::Array<CFGCommand *> Block::getLinearCommands() const {
|
||||
if (!hasControlStructure()) {
|
||||
return _commands;
|
||||
} else {
|
||||
Common::Array<CFGCommand *> commands;
|
||||
|
||||
// The last command in the block is the condition.
|
||||
// Exclude it from the linear commands.
|
||||
for (uint i = 0; i < _commands.size() - 1; i ++) {
|
||||
commands.push_back(_commands[i]);
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
CFGCommand *Block::getConditionCommand() const {
|
||||
if (hasControlStructure()) {
|
||||
// The condition command is always the last one
|
||||
return _commands.back();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Block::allowDuplication() const {
|
||||
// Allow simple termination blocks to be duplicated in the decompiled output
|
||||
bool isScriptEnd = !_follower && !_trueBranch && !_falseBranch;
|
||||
bool isShort = _commands.size() < 5;
|
||||
|
||||
return isScriptEnd && isShort;
|
||||
}
|
||||
|
||||
ControlStructure::ControlStructure(ControlStructureType t) :
|
||||
type(t),
|
||||
condition(nullptr),
|
||||
invertedCondition(false),
|
||||
loopHead(nullptr),
|
||||
thenHead(nullptr),
|
||||
elseHead(nullptr),
|
||||
next(nullptr) {
|
||||
}
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
140
engines/stark/tools/block.h
Normal file
140
engines/stark/tools/block.h
Normal file
@@ -0,0 +1,140 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STARK_TOOLS_BLOCK_H
|
||||
#define STARK_TOOLS_BLOCK_H
|
||||
|
||||
#include "common/array.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Tools {
|
||||
|
||||
class CFGCommand;
|
||||
struct ControlStructure;
|
||||
|
||||
/**
|
||||
* An aggregate of script commands
|
||||
*
|
||||
* Commands in the same group are always executed in sequence.
|
||||
*
|
||||
* This class is a node in the disassembly node graph.
|
||||
*/
|
||||
class Block {
|
||||
public:
|
||||
Block();
|
||||
|
||||
/** Add a command at the end of the block, and set it as the command's block */
|
||||
void appendCommand(CFGCommand *command);
|
||||
|
||||
/** Get the commands from the block that are not part of a condition */
|
||||
Common::Array<CFGCommand *> getLinearCommands() const;
|
||||
|
||||
/** Get the command from the block that plays as a condition in a control structure */
|
||||
CFGCommand *getConditionCommand() const;
|
||||
|
||||
/** Does the block contain commands? */
|
||||
bool isEmpty() const;
|
||||
|
||||
/** Can the block branch? */
|
||||
bool isCondition() const;
|
||||
|
||||
/**
|
||||
* Blocks are linked together in the block graph with these relationships:
|
||||
* - follower: The natural follower of the block. Used when the block is not a branch, nor an end point.
|
||||
* - true branch: The next block when the block's condition evaluates to true.
|
||||
* - false branch: The next block when the block's condition evaluates to false.
|
||||
* - predecessors: A list of blocks whose execution can lead to this block.
|
||||
*/
|
||||
void setBranches(Block *trueBranch, Block *falseBranch);
|
||||
void setFollower(Block *follower);
|
||||
void addPredecessor(Block *predecessor);
|
||||
Block *getTrueBranch() const;
|
||||
Block *getFalseBranch() const;
|
||||
Block *getFollower() const;
|
||||
|
||||
/**
|
||||
* Print a list of this block's commands to the debug output
|
||||
*/
|
||||
void print() const;
|
||||
|
||||
/**
|
||||
* The high level control structure this block has the main role in
|
||||
*/
|
||||
bool hasControlStructure() const;
|
||||
ControlStructure *getControlStructure() const;
|
||||
void setControlStructure(ControlStructure *controlStructure);
|
||||
|
||||
/** Flag to indicate this block is the first in an unconditional infinite loop */
|
||||
bool isInfiniteLoopStart() const;
|
||||
void setInfiniteLoopStart(bool infiniteLoopStart);
|
||||
|
||||
/** Can this block appear multiple times in the decompiled output? */
|
||||
bool allowDuplication() const;
|
||||
|
||||
// Graph query methods
|
||||
bool hasPredecessor(Block *predecessor) const;
|
||||
bool hasSuccessor(Block *successor) const;
|
||||
Block *findMergePoint(Block *other);
|
||||
bool checkAllBranchesConverge(Block *junction) const;
|
||||
|
||||
private:
|
||||
bool hasPredecessorIntern(Common::Array<const Block *> &visited, Block *predecessor) const;
|
||||
bool hasSuccessorIntern(Common::Array<const Block *> &visited, Block *successor) const;
|
||||
bool hasChildSuccessorIntern(Common::Array<const Block *> &visited, Block *child, Block *successor) const;
|
||||
Block *findMergePointIntern(Common::Array<const Block *> &visited, Block *other);
|
||||
Block *findChildMergePoint(Common::Array<const Block *> &visited, Block *child, Block *other) const;
|
||||
bool checkAllBranchesConvergeIntern(Common::Array<const Block *> &visited, Block *junction) const;
|
||||
bool checkChildConvergeIntern(Common::Array<const Block *> &visited, Block *child, Block *junction) const;
|
||||
|
||||
uint16 getFirstCommandIndex() const;
|
||||
|
||||
Common::Array<CFGCommand *> _commands;
|
||||
|
||||
Block *_follower;
|
||||
Block *_trueBranch;
|
||||
Block *_falseBranch;
|
||||
Common::Array<Block *> _predecessors;
|
||||
|
||||
ControlStructure *_controlStructure;
|
||||
bool _infiniteLoopStart;
|
||||
};
|
||||
|
||||
struct ControlStructure {
|
||||
enum ControlStructureType {
|
||||
kTypeIf,
|
||||
kTypeWhile
|
||||
};
|
||||
|
||||
ControlStructureType type;
|
||||
Block *condition;
|
||||
bool invertedCondition;
|
||||
Block *loopHead;
|
||||
Block *thenHead;
|
||||
Block *elseHead;
|
||||
Block *next;
|
||||
|
||||
ControlStructure(ControlStructureType t);
|
||||
};
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
|
||||
#endif // STARK_TOOLS_BLOCK_H
|
||||
376
engines/stark/tools/command.cpp
Normal file
376
engines/stark/tools/command.cpp
Normal file
@@ -0,0 +1,376 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "engines/stark/tools/command.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/tokenizer.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Tools {
|
||||
|
||||
Command::Command(Command *command) {
|
||||
_index = command->_index;
|
||||
_subType = command->_subType;
|
||||
_subTypeDesc = command->_subTypeDesc;
|
||||
_arguments = command->_arguments;
|
||||
}
|
||||
|
||||
Command::Command(Resources::Command *resource) {
|
||||
_index = resource->getIndex();
|
||||
_subType = (Resources::Command::SubType) resource->getSubType();
|
||||
_subTypeDesc = searchSubTypeDesc(_subType);
|
||||
_arguments = resource->getArguments();
|
||||
}
|
||||
|
||||
const Command::SubTypeDesc *Command::searchSubTypeDesc(Resources::Command::SubType subType) {
|
||||
static const SubTypeDesc typeNames[] = {
|
||||
{ Resources::Command::kCommandBegin, "begin", kFlowNormal },
|
||||
{ Resources::Command::kCommandEnd, "end", kFlowEnd },
|
||||
{ Resources::Command::kScriptCall, "scriptCall", kFlowNormal },
|
||||
{ Resources::Command::kDialogCall, "dialogCall", kFlowNormal },
|
||||
{ Resources::Command::kSetInteractiveMode, "setInteractiveMode", kFlowNormal },
|
||||
{ Resources::Command::kLocationGoTo, "locationGoTo", kFlowEnd },
|
||||
{ Resources::Command::kWalkTo, "walkTo", kFlowBranch },
|
||||
{ Resources::Command::kGameLoop, "gameLoop", kFlowNormal },
|
||||
{ Resources::Command::kScriptPause, "scriptPause", kFlowNormal },
|
||||
{ Resources::Command::kScriptPauseRandom, "scriptPauseRandom", kFlowNormal },
|
||||
{ Resources::Command::kScriptPauseSkippable, "scriptPauseSkippable", kFlowNormal },
|
||||
{ Resources::Command::kScriptAbort, "scriptAbort", kFlowNormal },
|
||||
{ Resources::Command::kExit2DLocation, "exit2DLocation", kFlowEnd },
|
||||
{ Resources::Command::kGoto2DLocation, "goto2DLocation", kFlowEnd },
|
||||
{ Resources::Command::kRumbleScene, "rumbleScene", kFlowNormal },
|
||||
{ Resources::Command::kFadeScene, "fadeScene", kFlowNormal },
|
||||
{ Resources::Command::kSwayScene, "swayScene", kFlowNormal },
|
||||
{ Resources::Command::kLocationGoToNewCD, "locationGoToNewCD", kFlowEnd },
|
||||
{ Resources::Command::kGameEnd, "gameEnd", kFlowNormal },
|
||||
{ Resources::Command::kInventoryOpen, "inventoryOpen", kFlowNormal },
|
||||
{ Resources::Command::kFloatScene, "floatScene", kFlowNormal },
|
||||
{ Resources::Command::kBookOfSecretsOpen, "bookOfSecretsOpen", kFlowNormal },
|
||||
{ Resources::Command::kDoNothing, "doNothing", kFlowNormal },
|
||||
{ Resources::Command::kItem3DPlaceOn, "item3DPlaceOn", kFlowNormal },
|
||||
{ Resources::Command::kItem3DWalkTo, "item3DWalkTo", kFlowNormal },
|
||||
{ Resources::Command::kItem3DFollowPath, "item3DFollowPath", kFlowNormal },
|
||||
{ Resources::Command::kItemLookAt, "itemLookAt", kFlowNormal },
|
||||
{ Resources::Command::kItem2DFollowPath, "item2DFollowPath", kFlowNormal },
|
||||
{ Resources::Command::kItemEnable, "itemEnable", kFlowNormal },
|
||||
{ Resources::Command::kItemSetActivity, "itemSetActivity", kFlowNormal },
|
||||
{ Resources::Command::kItemSelectInInventory, "itemSelectInInventory", kFlowNormal },
|
||||
{ Resources::Command::kUseAnimHierarchy, "useAnimHierarchy", kFlowNormal },
|
||||
{ Resources::Command::kPlayAnimation, "playAnimation", kFlowNormal },
|
||||
{ Resources::Command::kScriptEnable, "scriptEnable", kFlowNormal },
|
||||
{ Resources::Command::kShowPlay, "showPlay", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeSetBoolean, "knowledgeSetBoolean", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeSetInteger, "knowledgeSetInteger", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeAddInteger, "knowledgeAddInteger", kFlowNormal },
|
||||
{ Resources::Command::kEnableFloorField, "enableFloorField", kFlowNormal },
|
||||
{ Resources::Command::kPlayAnimScriptItem, "playAnimScriptItem", kFlowNormal },
|
||||
{ Resources::Command::kItemAnimFollowPath, "itemAnimFollowPath", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeAssignBool, "knowledgeAssignBool", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeAssignInteger, "knowledgeAssignInteger", kFlowNormal },
|
||||
{ Resources::Command::kLocationScrollTo, "locationScrollTo", kFlowNormal },
|
||||
{ Resources::Command::kSoundPlay, "soundPlay", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeSetIntRandom, "knowledgeSetIntRandom", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeSubValue, "knowledgeSubValue", kFlowNormal },
|
||||
{ Resources::Command::kItemLookDirection, "itemLookDirection", kFlowNormal },
|
||||
{ Resources::Command::kStopPlayingSound, "stopPlayingSound", kFlowNormal },
|
||||
{ Resources::Command::kLayerGoTo, "layerGoTo", kFlowNormal },
|
||||
{ Resources::Command::kLayerEnable, "layerEnable", kFlowNormal },
|
||||
{ Resources::Command::kLocationScrollSet, "locationScrollSet", kFlowNormal },
|
||||
{ Resources::Command::kFullMotionVideoPlay, "fullMotionVideoPlay", kFlowNormal },
|
||||
{ Resources::Command::kAnimSetFrame, "animSetFrame", kFlowNormal },
|
||||
{ Resources::Command::kKnowledgeAssignNegatedBool, "knowledgeAssignNegatedBool", kFlowNormal },
|
||||
{ Resources::Command::kDiaryEnableEntry, "diaryEnableEntry", kFlowNormal },
|
||||
{ Resources::Command::kPATChangeTooltip, "pATChangeTooltip", kFlowNormal },
|
||||
{ Resources::Command::kSoundChange, "soundChange", kFlowNormal },
|
||||
{ Resources::Command::kLightSetColor, "lightSetColor", kFlowNormal },
|
||||
{ Resources::Command::kLightFollowPath, "lightFollowPath", kFlowNormal },
|
||||
{ Resources::Command::kItem3DRunTo, "item3DRunTo", kFlowNormal },
|
||||
{ Resources::Command::kItemPlaceDirection, "itemPlaceDirection", kFlowNormal },
|
||||
{ Resources::Command::kItemRotateDirection, "itemRotateDirection", kFlowNormal },
|
||||
{ Resources::Command::kActivateTexture, "activateTexture", kFlowNormal },
|
||||
{ Resources::Command::kActivateMesh, "activateMesh", kFlowNormal },
|
||||
{ Resources::Command::kItem3DSetWalkTarget, "item3DSetWalkTarget", kFlowNormal },
|
||||
{ Resources::Command::kSpeakWithoutTalking, "speakWithoutTalking", kFlowNormal },
|
||||
{ Resources::Command::kIsOnFloorField, "isOnFloorField", kFlowBranch },
|
||||
{ Resources::Command::kIsItemEnabled, "isItemEnabled", kFlowBranch },
|
||||
{ Resources::Command::kIsScriptEnabled, "isScriptEnabled", kFlowBranch },
|
||||
{ Resources::Command::kIsKnowledgeBooleanSet, "isKnowledgeBooleanSet", kFlowBranch },
|
||||
{ Resources::Command::kIsKnowledgeIntegerInRange, "isKnowledgeIntegerInRange", kFlowBranch },
|
||||
{ Resources::Command::kIsKnowledgeIntegerAbove, "isKnowledgeIntegerAbove", kFlowBranch },
|
||||
{ Resources::Command::kIsKnowledgeIntegerEqual, "isKnowledgeIntegerEqual", kFlowBranch },
|
||||
{ Resources::Command::kIsKnowledgeIntegerLower, "isKnowledgeIntegerLower", kFlowBranch },
|
||||
{ Resources::Command::kIsScriptActive, "isScriptActive", kFlowBranch },
|
||||
{ Resources::Command::kIsRandom, "isRandom", kFlowBranch },
|
||||
{ Resources::Command::kIsAnimScriptItemReached, "isAnimScriptItemReached", kFlowBranch },
|
||||
{ Resources::Command::kIsItemOnPlace, "isItemOnPlace", kFlowBranch },
|
||||
{ Resources::Command::kIsAnimPlaying, "isAnimPlaying", kFlowBranch },
|
||||
{ Resources::Command::kIsItemActivity, "isItemActivity", kFlowBranch },
|
||||
{ Resources::Command::kIsItemNearPlace, "isItemNearPlace", kFlowBranch },
|
||||
{ Resources::Command::kIsAnimAtTime, "isAnimAtTime", kFlowBranch },
|
||||
{ Resources::Command::kIsLocation2D, "isLocation2D", kFlowBranch },
|
||||
{ Resources::Command::kIsInventoryOpen, "isInventoryOpen", kFlowBranch }
|
||||
};
|
||||
|
||||
for (uint i = 0; i < ARRAYSIZE(typeNames); i++) {
|
||||
if (typeNames[i].subType == subType) {
|
||||
return &typeNames[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Command::ArgumentArray Command::getEffectiveArguments() const {
|
||||
uint effectiveArgumentsStart;
|
||||
switch (_subTypeDesc->controlFlowType) {
|
||||
case kFlowEnd:
|
||||
effectiveArgumentsStart = 0;
|
||||
break;
|
||||
case kFlowNormal:
|
||||
effectiveArgumentsStart = 1;
|
||||
break;
|
||||
case kFlowBranch:
|
||||
effectiveArgumentsStart = 2;
|
||||
break;
|
||||
default:
|
||||
error("Unhandled control flow type '%d'", _subTypeDesc->controlFlowType);
|
||||
}
|
||||
|
||||
ArgumentArray effectiveArguments;
|
||||
for (uint i = effectiveArgumentsStart; i < _arguments.size(); i++) {
|
||||
effectiveArguments.push_back(_arguments[i]);
|
||||
}
|
||||
|
||||
return effectiveArguments;
|
||||
}
|
||||
|
||||
Common::String Command::describeArguments(DefinitionRegistry *definitions) const {
|
||||
Common::String desc;
|
||||
|
||||
for (uint i = 0; i < _arguments.size(); i++) {
|
||||
switch (_arguments[i].type) {
|
||||
case Resources::Command::Argument::kTypeInteger1:
|
||||
case Resources::Command::Argument::kTypeInteger2:
|
||||
desc += Common::String::format("%d", _arguments[i].intValue);
|
||||
break;
|
||||
|
||||
case Resources::Command::Argument::kTypeResourceReference: {
|
||||
if (definitions) {
|
||||
desc += definitions->getFromReference(_arguments[i].referenceValue);
|
||||
} else {
|
||||
desc += _arguments[i].referenceValue.describe();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Resources::Command::Argument::kTypeString:
|
||||
desc += _arguments[i].stringValue;
|
||||
break;
|
||||
default:
|
||||
error("Unknown argument type %d", _arguments[i].type);
|
||||
}
|
||||
|
||||
if (i != _arguments.size() - 1) {
|
||||
desc += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
void Command::printCall() const {
|
||||
debug("%d: %s(%s)", _index, _subTypeDesc->name, describeArguments(nullptr).c_str());
|
||||
}
|
||||
|
||||
uint16 Command::getIndex() const {
|
||||
return _index;
|
||||
}
|
||||
|
||||
bool Command::hasSubtypeDescription() const {
|
||||
return _subTypeDesc != nullptr;
|
||||
}
|
||||
|
||||
Resources::Command::SubType Command::getSubType() const {
|
||||
return _subType;
|
||||
}
|
||||
|
||||
CFGCommand::CFGCommand(Resources::Command *resource) :
|
||||
Command(resource),
|
||||
_followerIndex(-1),
|
||||
_trueBranchIndex(-1),
|
||||
_falseBranchIndex(-1),
|
||||
_follower(nullptr),
|
||||
_trueBranch(nullptr),
|
||||
_falseBranch(nullptr),
|
||||
_block(nullptr) {
|
||||
if (_subTypeDesc) {
|
||||
initBranches();
|
||||
}
|
||||
}
|
||||
|
||||
void CFGCommand::initBranches() {
|
||||
switch (_subTypeDesc->controlFlowType) {
|
||||
case kFlowNormal:
|
||||
_followerIndex = _arguments[0].intValue;
|
||||
break;
|
||||
case kFlowBranch:
|
||||
if (_arguments[0].intValue == _arguments[1].intValue) {
|
||||
// Degenerate conditions are handled here so that blocks are not split after them
|
||||
_followerIndex = _arguments[0].intValue;
|
||||
} else {
|
||||
_falseBranchIndex = _arguments[0].intValue;
|
||||
_trueBranchIndex = _arguments[1].intValue;
|
||||
}
|
||||
break;
|
||||
case kFlowEnd:
|
||||
// No followers
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CFGCommand::isEntryPoint() const {
|
||||
return _subType == Resources::Command::kCommandBegin;
|
||||
}
|
||||
|
||||
bool CFGCommand::isBranch() const {
|
||||
return _trueBranchIndex >= 0 && _falseBranchIndex >= 0;
|
||||
}
|
||||
|
||||
bool CFGCommand::isBranchTarget() const {
|
||||
return _predecessors.size() > 1;
|
||||
}
|
||||
|
||||
Block *CFGCommand::getBlock() const {
|
||||
return _block;
|
||||
}
|
||||
|
||||
void CFGCommand::setBlock(Block *block) {
|
||||
_block = block;
|
||||
}
|
||||
|
||||
CFGCommand *CFGCommand::getFollower() const {
|
||||
return _follower;
|
||||
}
|
||||
|
||||
CFGCommand *CFGCommand::getTrueBranch() const {
|
||||
return _trueBranch;
|
||||
}
|
||||
|
||||
CFGCommand *CFGCommand::getFalseBranch() const {
|
||||
return _falseBranch;
|
||||
}
|
||||
|
||||
void CFGCommand::linkBranches(const Common::Array<CFGCommand *> &commands) {
|
||||
if (_followerIndex >= 0) {
|
||||
_follower = findCommandWithIndex(commands, _followerIndex);
|
||||
_follower->_predecessors.push_back(this);
|
||||
}
|
||||
|
||||
if (_falseBranchIndex >= 0) {
|
||||
_falseBranch = findCommandWithIndex(commands, _falseBranchIndex);
|
||||
_falseBranch->_predecessors.push_back(this);
|
||||
}
|
||||
|
||||
if (_trueBranchIndex >= 0) {
|
||||
_trueBranch = findCommandWithIndex(commands, _trueBranchIndex);
|
||||
_trueBranch->_predecessors.push_back(this);
|
||||
}
|
||||
}
|
||||
|
||||
CFGCommand *CFGCommand::findCommandWithIndex(const Common::Array<CFGCommand *> &commands, int32 index) {
|
||||
for (uint i = 0; i < commands.size(); i++) {
|
||||
CFGCommand *command = commands[i];
|
||||
|
||||
if (command->_index == index) {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
error("Unable to find command with index %d", index);
|
||||
}
|
||||
|
||||
void DefinitionRegistry::registerReference(const ResourceReference &reference) {
|
||||
if (!reference.canResolve()) {
|
||||
// The reference uses archives that are not currently loaded
|
||||
return;
|
||||
}
|
||||
|
||||
Resources::Object *object = reference.resolve<Resources::Object>();
|
||||
if (!_definitions.contains(object)) {
|
||||
// TODO: There is no guarantee the definition is unique
|
||||
_definitions[object] = object->getType().getName() + stringToCamelCase(object->getName());
|
||||
}
|
||||
}
|
||||
|
||||
Common::String DefinitionRegistry::getFromReference(const ResourceReference &reference) const {
|
||||
if (!reference.canResolve()) {
|
||||
// The reference uses archives that are not currently loaded
|
||||
return reference.describe();
|
||||
}
|
||||
|
||||
Resources::Object *object = reference.resolve<Resources::Object>();
|
||||
|
||||
if (_definitions.contains(object)) {
|
||||
return _definitions.getVal(object);
|
||||
} else {
|
||||
return reference.describe();
|
||||
}
|
||||
}
|
||||
|
||||
Common::String DefinitionRegistry::stringToCamelCase(const Common::String &input) {
|
||||
Common::String clean = input;
|
||||
|
||||
// First replace all non alphanumerical characters with spaces
|
||||
for (uint i = 0; i < clean.size(); i++) {
|
||||
if (!Common::isAlnum(clean[i])) {
|
||||
clean.setChar(' ', i);
|
||||
}
|
||||
}
|
||||
|
||||
// Then turn the string into camel case
|
||||
Common::String output;
|
||||
Common::StringTokenizer tokens = Common::StringTokenizer(clean);
|
||||
while (!tokens.empty()) {
|
||||
Common::String token = tokens.nextToken();
|
||||
|
||||
char upperFirstLetter = toupper(token[0]);
|
||||
token.setChar(upperFirstLetter, 0);
|
||||
|
||||
output += token;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void DefinitionRegistry::printAll() const {
|
||||
DefinitionMap::const_iterator it = _definitions.begin();
|
||||
while (it != _definitions.end()) {
|
||||
ResourceReference reference;
|
||||
reference.buildFromResource(it->_key);
|
||||
|
||||
debug("let %s = %s", it->_value.c_str(), reference.describe().c_str());
|
||||
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
179
engines/stark/tools/command.h
Normal file
179
engines/stark/tools/command.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STARK_TOOLS_COMMAND_H
|
||||
#define STARK_TOOLS_COMMAND_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/hash-ptr.h"
|
||||
|
||||
#include "engines/stark/resources/command.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Tools {
|
||||
|
||||
class Block;
|
||||
class DefinitionRegistry;
|
||||
|
||||
/**
|
||||
* A base script command for disassembly use
|
||||
*
|
||||
* As opposed to the Command class in the Resources namespace, this class
|
||||
* is not meant to be executed. It is meant to be used for script disassembly,
|
||||
* to store analysis results.
|
||||
*/
|
||||
class Command {
|
||||
public:
|
||||
enum ControlFlowType {
|
||||
kFlowNormal,
|
||||
kFlowBranch,
|
||||
kFlowEnd
|
||||
};
|
||||
typedef Common::Array<Resources::Command::Argument> ArgumentArray;
|
||||
|
||||
Command(Command *command);
|
||||
Command(Resources::Command *resource);
|
||||
|
||||
/**
|
||||
* Print a call to this command to the debug output
|
||||
*/
|
||||
void printCall() const;
|
||||
|
||||
/** This command's resource tree index */
|
||||
uint16 getIndex() const;
|
||||
Resources::Command::SubType getSubType() const;
|
||||
bool hasSubtypeDescription() const;
|
||||
|
||||
/** List the command's arguments ignoring the control flow related ones */
|
||||
ArgumentArray getEffectiveArguments() const;
|
||||
|
||||
protected:
|
||||
struct SubTypeDesc {
|
||||
Resources::Command::SubType subType;
|
||||
const char *name;
|
||||
ControlFlowType controlFlowType;
|
||||
};
|
||||
|
||||
/** Get a description for a command subtype from an internal database */
|
||||
static const SubTypeDesc *searchSubTypeDesc(Resources::Command::SubType subType);
|
||||
|
||||
/** List the arguments values as a coma separated string */
|
||||
Common::String describeArguments(DefinitionRegistry *definitions) const;
|
||||
|
||||
uint16 _index;
|
||||
Resources::Command::SubType _subType;
|
||||
const SubTypeDesc *_subTypeDesc;
|
||||
ArgumentArray _arguments;
|
||||
};
|
||||
|
||||
/**
|
||||
* A script command with control flow information
|
||||
*
|
||||
* This class is a node in the disassembly command control flow graph.
|
||||
* It is referenced by the blocks in the block control flow graph.
|
||||
*/
|
||||
class CFGCommand : public Command {
|
||||
public:
|
||||
CFGCommand(Resources::Command *resource);
|
||||
|
||||
/** Is this command an entry point for the whole script? */
|
||||
bool isEntryPoint() const;
|
||||
|
||||
/** Can this command influence the control flow? */
|
||||
bool isBranch() const;
|
||||
|
||||
/** Is this command a jump target? */
|
||||
bool isBranchTarget() const;
|
||||
|
||||
/**
|
||||
* Commands are linked together in the command graph with these relationships:
|
||||
* - follower: The natural follower of the command. Used when the command is not a branch, nor an end point.
|
||||
* - true branch: The next command when the command's condition evaluates to true.
|
||||
* - false branch: The next command when the command's condition evaluates to false.
|
||||
* - predecessors: A list of commands whose execution can lead to this command.
|
||||
*/
|
||||
CFGCommand *getFollower() const;
|
||||
CFGCommand *getTrueBranch() const;
|
||||
CFGCommand *getFalseBranch() const;
|
||||
|
||||
/**
|
||||
* Commands are aggregated into blocks
|
||||
*/
|
||||
Block *getBlock() const;
|
||||
void setBlock(Block *block);
|
||||
|
||||
/**
|
||||
* Add the command to the command graph
|
||||
*
|
||||
* This sets the graph edges concerning this command.
|
||||
*/
|
||||
void linkBranches(const Common::Array<CFGCommand *> &commands);
|
||||
|
||||
protected:
|
||||
/** Set the link indices from the argument values */
|
||||
void initBranches();
|
||||
|
||||
/** Gets the command with the specifed index */
|
||||
static CFGCommand *findCommandWithIndex(const Common::Array<CFGCommand *> &commands, int32 index);
|
||||
|
||||
int32 _followerIndex;
|
||||
int32 _trueBranchIndex;
|
||||
int32 _falseBranchIndex;
|
||||
|
||||
CFGCommand *_follower;
|
||||
CFGCommand *_trueBranch;
|
||||
CFGCommand *_falseBranch;
|
||||
Common::Array<CFGCommand *> _predecessors;
|
||||
|
||||
Block *_block;
|
||||
};
|
||||
|
||||
/**
|
||||
* Storage for aliases between world resources and names
|
||||
*/
|
||||
class DefinitionRegistry {
|
||||
public:
|
||||
/**
|
||||
* Add a definition from a reference
|
||||
*
|
||||
* The name is computed from the object's name
|
||||
*/
|
||||
void registerReference(const ResourceReference &reference);
|
||||
|
||||
/** Get a previously registered definition from a reference */
|
||||
Common::String getFromReference(const ResourceReference &reference) const;
|
||||
|
||||
/** Print all the registered definitions */
|
||||
void printAll() const;
|
||||
|
||||
private:
|
||||
typedef Common::HashMap<Resources::Object *, Common::String> DefinitionMap;
|
||||
|
||||
Common::String stringToCamelCase(const Common::String &input);
|
||||
|
||||
DefinitionMap _definitions;
|
||||
};
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
|
||||
#endif // STARK_TOOLS_COMMAND_H
|
||||
448
engines/stark/tools/decompiler.cpp
Normal file
448
engines/stark/tools/decompiler.cpp
Normal file
@@ -0,0 +1,448 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "engines/stark/tools/decompiler.h"
|
||||
|
||||
#include "engines/stark/resources/command.h"
|
||||
|
||||
#include "engines/stark/tools/abstractsyntaxtree.h"
|
||||
#include "engines/stark/tools/block.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Tools {
|
||||
|
||||
Decompiler::Decompiler(Resources::Script *script) :
|
||||
_entryPoint(nullptr),
|
||||
_astHead(nullptr),
|
||||
_definitionRegistry(nullptr) {
|
||||
// Convert the script commands to decompiler commands
|
||||
Common::Array<Resources::Command *> resourceCommands = script->listChildren<Resources::Command>();
|
||||
for (uint i = 0; i < resourceCommands.size(); i++) {
|
||||
_commands.push_back(new CFGCommand(resourceCommands[i]));
|
||||
}
|
||||
if (_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!checkCommands()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_entryPoint = findEntryPoint();
|
||||
|
||||
linkCommandBranches();
|
||||
buildBlocks();
|
||||
analyseControlFlow();
|
||||
|
||||
_definitionRegistry = new DefinitionRegistry();
|
||||
_astHead = buildAST();
|
||||
verifyAST();
|
||||
}
|
||||
|
||||
void Decompiler::printCommands() const {
|
||||
for (uint i = 0; i < _commands.size(); i++) {
|
||||
_commands[i]->printCall();
|
||||
}
|
||||
}
|
||||
|
||||
void Decompiler::printBlocks() const {
|
||||
for (uint i = 0; i < _blocks.size(); i++) {
|
||||
_blocks[i]->print();
|
||||
debug("- - - -");
|
||||
}
|
||||
}
|
||||
|
||||
void Decompiler::printDecompiled() {
|
||||
if (_astHead) {
|
||||
_definitionRegistry->printAll();
|
||||
debug(" "); // Empty line
|
||||
_astHead->print(0, _definitionRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
Decompiler::~Decompiler() {
|
||||
for (uint i = 0; i < _commands.size(); i++) {
|
||||
delete _commands[i];
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _blocks.size(); i++) {
|
||||
delete _blocks[i];
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _controlStructures.size(); i++) {
|
||||
delete _controlStructures[i];
|
||||
}
|
||||
|
||||
delete _astHead;
|
||||
delete _definitionRegistry;
|
||||
}
|
||||
|
||||
void Decompiler::linkCommandBranches() {
|
||||
for (uint i = 0; i < _commands.size(); i++) {
|
||||
_commands[i]->linkBranches(_commands);
|
||||
}
|
||||
}
|
||||
|
||||
CFGCommand *Decompiler::findEntryPoint() {
|
||||
for (uint i = 0; i < _commands.size(); i++) {
|
||||
if (_commands[i]->isEntryPoint()) {
|
||||
return _commands[i];
|
||||
}
|
||||
}
|
||||
|
||||
error("Unable to find an entry point");
|
||||
}
|
||||
|
||||
void Decompiler::buildBlocks() {
|
||||
Block *entryPointBlock = new Block();
|
||||
_blocks.push_back(entryPointBlock);
|
||||
|
||||
buildBlocks(entryPointBlock, _entryPoint);
|
||||
}
|
||||
|
||||
void Decompiler::buildBlocks(Block *block, CFGCommand *command) {
|
||||
CFGCommand *blockCommand = command;
|
||||
while (blockCommand) {
|
||||
if (blockCommand->getBlock()) {
|
||||
block->setFollower(blockCommand->getBlock());
|
||||
break;
|
||||
}
|
||||
|
||||
if (blockCommand->isBranchTarget() && !block->isEmpty()) {
|
||||
Block *follower = buildBranchBlocks(blockCommand);
|
||||
|
||||
block->setFollower(follower);
|
||||
break;
|
||||
}
|
||||
|
||||
block->appendCommand(blockCommand);
|
||||
|
||||
if (blockCommand->isBranch()) {
|
||||
Block *falseBranch = buildBranchBlocks(blockCommand->getFalseBranch());
|
||||
Block *trueBranch = buildBranchBlocks(blockCommand->getTrueBranch());
|
||||
|
||||
block->setBranches(trueBranch, falseBranch);
|
||||
break;
|
||||
}
|
||||
|
||||
blockCommand = blockCommand->getFollower();
|
||||
}
|
||||
}
|
||||
|
||||
Block *Decompiler::buildBranchBlocks(CFGCommand *command) {
|
||||
if (command->getBlock()) {
|
||||
// The command already has a block. No need to go through this path again.
|
||||
return command->getBlock();
|
||||
}
|
||||
|
||||
Block *branchBlock = new Block();
|
||||
_blocks.push_back(branchBlock);
|
||||
|
||||
buildBlocks(branchBlock, command);
|
||||
|
||||
return branchBlock;
|
||||
}
|
||||
|
||||
void Decompiler::analyseControlFlow() {
|
||||
detectInfiniteLoop();
|
||||
detectWhile();
|
||||
detectIf();
|
||||
}
|
||||
|
||||
void Decompiler::detectInfiniteLoop() {
|
||||
for (uint i = 0; i < _blocks.size(); i++) {
|
||||
Block *block = _blocks[i];
|
||||
|
||||
// Check all paths from the block go back to itself
|
||||
if (block->getFollower()) {
|
||||
bool followerConvergesBack = block->getFollower()->checkAllBranchesConverge(block);
|
||||
|
||||
if (followerConvergesBack) {
|
||||
block->setInfiniteLoopStart(true);
|
||||
}
|
||||
} else if (block->isCondition()) {
|
||||
bool trueBranchConvergesBack = block->getTrueBranch()->checkAllBranchesConverge(block);
|
||||
bool falseBranchConvergesBack = block->getFalseBranch()->checkAllBranchesConverge(block);
|
||||
|
||||
if (trueBranchConvergesBack && falseBranchConvergesBack) {
|
||||
block->setInfiniteLoopStart(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Decompiler::detectWhile() {
|
||||
for (uint i = 0; i < _blocks.size(); i++) {
|
||||
Block *block = _blocks[i];
|
||||
|
||||
if (block->hasControlStructure()) continue;
|
||||
if (!block->isCondition()) continue;
|
||||
if (block->isInfiniteLoopStart()) continue; // TODO: This should be handled by checkAllBranchesConverge already
|
||||
|
||||
// Check all paths from the body branch go back to the condition
|
||||
// TODO: If the original had "break" statement, this will not work
|
||||
bool trueBranchConvergesToCondition = block->getTrueBranch()->checkAllBranchesConverge(block);
|
||||
bool falseBranchConvergesToCondition = block->getFalseBranch()->checkAllBranchesConverge(block);
|
||||
|
||||
if (!trueBranchConvergesToCondition && !falseBranchConvergesToCondition) continue;
|
||||
if (trueBranchConvergesToCondition && falseBranchConvergesToCondition) {
|
||||
warning("Both branches of a condition converge back to the condition");
|
||||
}
|
||||
|
||||
ControlStructure *controlStructure = new ControlStructure(ControlStructure::kTypeWhile);
|
||||
if (trueBranchConvergesToCondition) {
|
||||
controlStructure->invertedCondition = false;
|
||||
controlStructure->loopHead = block->getTrueBranch();
|
||||
controlStructure->next = block->getFalseBranch();
|
||||
} else {
|
||||
controlStructure->invertedCondition = true;
|
||||
controlStructure->loopHead = block->getFalseBranch();
|
||||
controlStructure->next = block->getTrueBranch();
|
||||
}
|
||||
|
||||
block->setControlStructure(controlStructure);
|
||||
_controlStructures.push_back(controlStructure);
|
||||
}
|
||||
}
|
||||
|
||||
void Decompiler::detectIf() {
|
||||
for (uint i = 0; i < _blocks.size(); i++) {
|
||||
Block *block = _blocks[i];
|
||||
|
||||
if (block->hasControlStructure()) continue;
|
||||
if (!block->isCondition()) continue;
|
||||
|
||||
ControlStructure *controlStructure = new ControlStructure(ControlStructure::kTypeIf);
|
||||
controlStructure->next = block->getTrueBranch()->findMergePoint(block->getFalseBranch());
|
||||
|
||||
if (!controlStructure->next) {
|
||||
// When one (or both) of the branches return, there is no merge point
|
||||
controlStructure->invertedCondition = false;
|
||||
controlStructure->thenHead = block->getTrueBranch();
|
||||
controlStructure->elseHead = block->getFalseBranch();
|
||||
} else if (block->getTrueBranch() != controlStructure->next) {
|
||||
// Use the "true" branch as the "then" block ...
|
||||
controlStructure->invertedCondition = false;
|
||||
controlStructure->thenHead = block->getTrueBranch();
|
||||
controlStructure->elseHead = controlStructure->next != block->getFalseBranch() ? block->getFalseBranch() : nullptr;
|
||||
} else {
|
||||
// ... unless the true branch is empty.
|
||||
// In which case use the false branch and invert the condition.
|
||||
controlStructure->invertedCondition = true;
|
||||
controlStructure->thenHead = block->getFalseBranch();
|
||||
controlStructure->elseHead = nullptr;
|
||||
}
|
||||
|
||||
block->setControlStructure(controlStructure);
|
||||
_controlStructures.push_back(controlStructure);
|
||||
}
|
||||
}
|
||||
|
||||
bool Decompiler::checkCommands() {
|
||||
for (uint i = 0; i < _commands.size(); i++) {
|
||||
Command *command = _commands[i];
|
||||
if (!command->hasSubtypeDescription()) {
|
||||
_error = Common::String::format("Command subtype %d is not described", command->getSubType());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::String Decompiler::getError() const {
|
||||
return _error;
|
||||
}
|
||||
|
||||
ASTNode *Decompiler::buildAST() {
|
||||
Block *entryPoint = _entryPoint->getBlock();
|
||||
|
||||
ASTBlock *head = new ASTBlock(nullptr);
|
||||
buildASTFromBlock(head, entryPoint, nullptr);
|
||||
return head;
|
||||
}
|
||||
|
||||
void Decompiler::buildASTFromBlock(ASTBlock *parent, Block *block, Block *stopBlock) {
|
||||
if (block->isInfiniteLoopStart()) {
|
||||
bool alreadyVisited = Common::find(_visitedInfiniteLoopStarts.begin(), _visitedInfiniteLoopStarts.end(), block)
|
||||
!= _visitedInfiniteLoopStarts.end();
|
||||
if (alreadyVisited) {
|
||||
// Don't add the same loop multiple times in the AST
|
||||
return;
|
||||
}
|
||||
|
||||
_visitedInfiniteLoopStarts.push_back(block);
|
||||
|
||||
ASTLoop *loop = new ASTLoop(parent);
|
||||
loop->loopBlock = new ASTBlock(loop);
|
||||
parent->addNode(loop);
|
||||
|
||||
// Hijack the parameters to add the current block to the loop body and continue
|
||||
parent = loop->loopBlock;
|
||||
stopBlock = block;
|
||||
}
|
||||
|
||||
{
|
||||
bool alreadyVisited = Common::find(_visitedBlocks.begin(), _visitedBlocks.end(), block) != _visitedBlocks.end();
|
||||
if (alreadyVisited && !block->allowDuplication()) {
|
||||
// FIXME: We just return for now when an already visited block is visited again.
|
||||
// Obviously, this leads to invalid decompiled code, which is caught by the verification step.
|
||||
// To fix, either handle the cases leading to multiple visits, or generate gotos.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_visitedBlocks.push_back(block);
|
||||
|
||||
Common::Array<CFGCommand *> commands = block->getLinearCommands();
|
||||
for (uint i = 0; i < commands.size(); i++) {
|
||||
parent->addNode(new ASTCommand(parent, commands[i], _definitionRegistry));
|
||||
}
|
||||
|
||||
if (block->hasControlStructure()) {
|
||||
ControlStructure *cfgControlStructure = block->getControlStructure();
|
||||
ASTNode *astControlStructure;
|
||||
switch (cfgControlStructure->type) {
|
||||
case ControlStructure::kTypeIf:
|
||||
astControlStructure = buildASTConditionFromBlock(parent, block);
|
||||
break;
|
||||
case ControlStructure::kTypeWhile:
|
||||
astControlStructure = buildASTLoopFromBlock(parent, block);
|
||||
break;
|
||||
default:
|
||||
error("Unknown control structure type %d", cfgControlStructure->type);
|
||||
}
|
||||
|
||||
parent->addNode(astControlStructure);
|
||||
|
||||
if (cfgControlStructure->next && cfgControlStructure->next != stopBlock) {
|
||||
buildASTFromBlock(parent, cfgControlStructure->next, stopBlock);
|
||||
}
|
||||
} else {
|
||||
Block *follower = block->getFollower();
|
||||
if (follower && follower != stopBlock) {
|
||||
buildASTFromBlock(parent, follower, stopBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASTCondition *Decompiler::buildASTConditionFromBlock(ASTNode *parent, Block *block) {
|
||||
ControlStructure *controlStructure = block->getControlStructure();
|
||||
|
||||
ASTCondition *condition = new ASTCondition(parent);
|
||||
condition->condition = new ASTCommand(condition, block->getConditionCommand(), _definitionRegistry);
|
||||
condition->invertedCondition = controlStructure->invertedCondition;
|
||||
|
||||
condition->thenBlock = new ASTBlock(condition);
|
||||
buildASTFromBlock(condition->thenBlock, controlStructure->thenHead, controlStructure->next);
|
||||
|
||||
if (controlStructure->elseHead) {
|
||||
condition->elseBlock = new ASTBlock(condition);
|
||||
buildASTFromBlock(condition->elseBlock, controlStructure->elseHead, controlStructure->next);
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
ASTLoop *Decompiler::buildASTLoopFromBlock(ASTNode *parent, Block *block) {
|
||||
ControlStructure *controlStructure = block->getControlStructure();
|
||||
|
||||
ASTLoop *loop = new ASTLoop(parent);
|
||||
loop->condition = new ASTCommand(loop, block->getConditionCommand(), _definitionRegistry);
|
||||
loop->invertedCondition = controlStructure->invertedCondition;
|
||||
|
||||
loop->loopBlock = new ASTBlock(loop);
|
||||
buildASTFromBlock(loop->loopBlock, controlStructure->loopHead, block);
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
void Decompiler::verifyAST() {
|
||||
for (uint i = 0; i < _commands.size(); i++) {
|
||||
CFGCommand *command = _commands[i];
|
||||
if (!verifyCommandInAST(command)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Decompiler::verifyCommandInAST(CFGCommand *cfgCommand) {
|
||||
Common::Array<const ASTCommand *> list = _astHead->listCommands(cfgCommand->getIndex());
|
||||
|
||||
if (list.empty()) {
|
||||
_error = Common::String::format("Command %d not found in the AST", cfgCommand->getIndex());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (list.size() > 1 && !cfgCommand->getBlock()->allowDuplication()) {
|
||||
_error = Common::String::format("Command %d found %d times in the AST", cfgCommand->getIndex(), list.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
const ASTCommand *astCommand = list[0];
|
||||
|
||||
ASTNode *follower = nullptr, *trueBranch = nullptr, *falseBranch = nullptr;
|
||||
astCommand->findSuccessors(&follower, &trueBranch, &falseBranch);
|
||||
|
||||
if (!verifyCommandSuccessorInAST(cfgCommand, cfgCommand->getFollower(), follower, "follower")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!verifyCommandSuccessorInAST(cfgCommand, cfgCommand->getTrueBranch(), trueBranch, "trueBranch")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!verifyCommandSuccessorInAST(cfgCommand, cfgCommand->getFalseBranch(), falseBranch, "falseBranch")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Decompiler::verifyCommandSuccessorInAST(CFGCommand *cfgCommand, CFGCommand *cfgSuccessor, ASTNode *astSuccessor, const char *successorType) {
|
||||
if (cfgSuccessor) {
|
||||
if (!astSuccessor) {
|
||||
_error = Common::String::format("Command %d does not have a %s in the AST",
|
||||
cfgCommand->getIndex(), successorType);
|
||||
return false;
|
||||
}
|
||||
|
||||
const ASTCommand *astSuccessorCommand = astSuccessor->getFirstCommand();
|
||||
if (!astSuccessorCommand) {
|
||||
_error = Common::String::format("Command %d has an empty %s in the AST",
|
||||
cfgCommand->getIndex(), successorType);
|
||||
return false;
|
||||
}
|
||||
|
||||
int16 expectedSuccessorIndex = cfgSuccessor->getIndex();
|
||||
if (astSuccessorCommand->getIndex() != expectedSuccessorIndex) {
|
||||
_error = Common::String::format("Command %d has an unexpected %s %d in the AST, should be %d",
|
||||
cfgCommand->getIndex(), successorType, astSuccessorCommand->getIndex(), expectedSuccessorIndex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
103
engines/stark/tools/decompiler.h
Normal file
103
engines/stark/tools/decompiler.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STARK_TOOLS_SCRIPT_H
|
||||
#define STARK_TOOLS_SCRIPT_H
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
#include "engines/stark/resources/script.h"
|
||||
|
||||
namespace Stark {
|
||||
|
||||
namespace Resources {
|
||||
class Script;
|
||||
}
|
||||
|
||||
namespace Tools {
|
||||
|
||||
class Block;
|
||||
class CFGCommand;
|
||||
class DefinitionRegistry;
|
||||
struct ControlStructure;
|
||||
struct ASTBlock;
|
||||
struct ASTNode;
|
||||
struct ASTCondition;
|
||||
struct ASTLoop;
|
||||
|
||||
class Decompiler {
|
||||
public:
|
||||
Decompiler(Resources::Script *script);
|
||||
~Decompiler();
|
||||
|
||||
void printCommands() const;
|
||||
void printBlocks() const;
|
||||
void printDecompiled();
|
||||
|
||||
Common::String getError() const;
|
||||
|
||||
private:
|
||||
// Command control flow graph construction
|
||||
bool checkCommands();
|
||||
void linkCommandBranches();
|
||||
CFGCommand *findEntryPoint();
|
||||
|
||||
// Block control flow graph construction
|
||||
void buildBlocks();
|
||||
void buildBlocks(Block *block, CFGCommand *command);
|
||||
Block *buildBranchBlocks(CFGCommand *command);
|
||||
|
||||
// Control flow analysis
|
||||
void analyseControlFlow();
|
||||
void detectInfiniteLoop();
|
||||
void detectWhile();
|
||||
void detectIf();
|
||||
|
||||
// AST generation
|
||||
ASTNode *buildAST();
|
||||
void buildASTFromBlock(ASTBlock *parent, Block *block, Block *stopBlock);
|
||||
ASTCondition *buildASTConditionFromBlock(ASTNode *parent, Block *block);
|
||||
ASTLoop *buildASTLoopFromBlock(ASTNode *parent, Block *block);
|
||||
|
||||
// AST verification
|
||||
void verifyAST();
|
||||
bool verifyCommandInAST(CFGCommand *cfgCommand);
|
||||
bool verifyCommandSuccessorInAST(CFGCommand *cfgCommand, CFGCommand *cfgSuccessor, ASTNode *astSuccessor, const char *successorType);
|
||||
|
||||
Common::String _error;
|
||||
|
||||
Common::Array<CFGCommand *> _commands;
|
||||
CFGCommand *_entryPoint;
|
||||
|
||||
Common::Array<Block *> _blocks;
|
||||
Common::Array<ControlStructure *> _controlStructures;
|
||||
|
||||
ASTNode *_astHead;
|
||||
Common::Array<Block *> _visitedInfiniteLoopStarts;
|
||||
Common::Array<Block *> _visitedBlocks;
|
||||
|
||||
DefinitionRegistry *_definitionRegistry;
|
||||
};
|
||||
|
||||
} // End of namespace Tools
|
||||
} // End of namespace Stark
|
||||
|
||||
#endif // STARK_TOOLS_SCRIPT_H
|
||||
Reference in New Issue
Block a user