Files
scummvm-cursorfix/engines/director/debugger/dt-script-d4.cpp
2026-02-02 04:50:13 +01:00

1262 lines
35 KiB
C++

/* 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 "director/director.h"
#include "director/movie.h"
#include "director/cast.h"
#include "director/debugger/dt-internal.h"
#include "director/debugger.h"
#include "director/lingo/lingo-object.h"
#include "director/lingo/lingodec/codewritervisitor.h"
#include "director/lingo/lingodec/names.h"
#include "director/lingo/lingodec/script.h"
namespace Director {
namespace DT {
class RenderScriptVisitor : public LingoDec::NodeVisitor {
public:
RenderScriptVisitor(ImGuiScript &script, bool showByteCode, bool scrollTo) : _script(script), _showByteCode(showByteCode), _scrollTo(scrollTo) {
Common::Array<CFrame *> &callstack = g_lingo->_state->callstack;
if (!callstack.empty()) {
CFrame *head = callstack[callstack.size() - 1];
_isScriptInDebug = (head->sp.ctx->_id == script.id.member) && (*head->sp.name == script.handlerId);
}
_script.startOffsets.clear();
}
virtual void visit(const LingoDec::HandlerNode &node) override {
if (_showByteCode) {
byteCode(node);
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
lingoCode(node);
ImGui::PopStyleVar();
}
virtual void visit(const LingoDec::CommentNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._comment_color), "-- %s", node.text.c_str());
ImGui::SameLine();
}
virtual void visit(const LingoDec::LiteralNode &node) override {
write(*node.value);
}
virtual void visit(const LingoDec::NewObjNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), "new");
ImGui::SameLine();
ImGui::TextColored(ImColor(_state->_colors._type_color), "%s", node.objType.c_str());
ImGui::SameLine();
ImGui::Text(" (");
ImGui::SameLine();
node.objArgs->accept(*this);
ImGui::Text(")");
ImGui::SameLine();
}
virtual void visit(const LingoDec::ObjCallV4Node &node) override {
if (node.isStatement) {
renderLine(node._startOffset);
renderIndentation();
}
node.obj->accept(*this);
ImGui::SameLine();
ImGui::Text(" (");
ImGui::SameLine();
node.argList->accept(*this);
ImGui::SameLine();
ImGui::Text(")");
if (!node.isStatement) {
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::CallNode &node) override {
// new line only if it's a statement
if (node.isStatement) {
renderLine(node._startOffset);
renderIndentation();
}
const ImVec4 color = (ImVec4)ImColor(g_lingo->_builtinCmds.contains(node.name) ? _state->_colors._builtin_color : _state->_colors._call_color);
ImGui::TextColored(color, "%s", node.name.c_str());
// TODO: we should test Director::builtins too (but inaccessible)
if (!g_lingo->_builtinFuncs.contains(node.name) && ImGui::IsItemHovered() && ImGui::BeginTooltip()) {
ImGui::Text("Go to definition");
ImGui::EndTooltip();
}
if (!g_lingo->_builtinFuncs.contains(node.name) && ImGui::IsItemClicked()) {
int32 obj = 0;
for (uint i = 0; i < _script.bytecodeArray.size(); i++) {
if (node._startOffset == _script.bytecodeArray[i].pos) {
// This obj represents the index of the handler being called
// The index may be local (lingo context-wide) or global (cast-wide)
obj = _script.bytecodeArray[i].obj;
break;
}
}
ScriptContext *context = getScriptContext(obj, _script.id, node.name);
if (context) {
ImGuiScript script = toImGuiScript(_script.type, CastMemberID(context->_id, _script.id.castLib), node.name);
const Director::Movie *movie = g_director->getCurrentMovie();
script.byteOffsets = context->_functionByteOffsets[script.handlerId];
script.moviePath = _script.moviePath;
int castId = context->_id;
bool childScript = false;
if (castId == -1) {
castId = movie->getCast()->getCastIdByScriptId(context->_parentNumber);
childScript = true;
}
script.handlerName = formatHandlerName(context->_scriptId, castId, script.handlerId, context->_scriptType, childScript);
setScriptToDisplay(script);
_state->_dbg._goToDefinition = true;
}
}
ImGui::SameLine();
if (node.noParens()) {
ImGui::Text(" ");
ImGui::SameLine();
node.argList->accept(*this);
} else {
ImGui::Text("(");
ImGui::SameLine();
node.argList->accept(*this);
ImGui::Text(")");
ImGui::SameLine();
}
if (node.isStatement) {
ImGui::NewLine();
}
}
virtual void visit(const LingoDec::BlockNode &node) override {
indent();
for (const auto &child : node.children) {
child->accept(*this);
}
unindent();
}
virtual void visit(const LingoDec::PutStmtNode &node) override {
write(node._startOffset, "put ", _state->_colors._keyword_color);
ImGui::SameLine();
node.value->accept(*this);
ImGui::Text(" ");
ImGui::SameLine();
ImGui::TextColored(ImColor(_state->_colors._keyword_color), LingoDec::StandardNames::putTypeNames[node.type]);
ImGui::SameLine();
ImGui::Text(" ");
ImGui::SameLine();
node.variable->accept(*this);
ImGui::NewLine();
}
virtual void visit(const LingoDec::TheExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s", node.prop.c_str());
ImGui::SameLine();
}
virtual void visit(const LingoDec::ExitStmtNode &node) override {
write(node._startOffset, "exit", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::WhenStmtNode &node) override {
write(node._startOffset, "when ", _state->_colors._keyword_color);
ImGui::SameLine();
ImGui::TextColored(ImColor(_state->_colors._keyword_color), LingoDec::StandardNames::whenEventNames[node.event]);
ImGui::SameLine();
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " then ");
ImGui::SameLine();
ImGui::Text("%s", node.script.c_str());
}
virtual void visit(const LingoDec::RepeatWhileStmtNode &node) override {
write(node._startOffset, "repeat while ", _state->_colors._keyword_color);
ImGui::SameLine();
node.condition->accept(*this);
ImGui::NewLine();
node.block->accept(*this);
write(node._endOffset, "end repeat", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::RepeatWithInStmtNode &node) override {
write(node._startOffset, "repeat with ", _state->_colors._keyword_color);
ImGui::SameLine();
renderVar(node.varName);
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " in ");
ImGui::SameLine();
node.list->accept(*this);
ImGui::NewLine();
node.block->accept(*this);
write(node._endOffset, "end repeat", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::RepeatWithToStmtNode &node) override {
write(node._startOffset, "repeat with ", _state->_colors._keyword_color);
ImGui::SameLine();
renderVar(node.varName);
ImGui::Text(" = ");
ImGui::SameLine();
node.start->accept(*this);
if (node.up) {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " to ");
ImGui::SameLine();
} else {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " down to ");
ImGui::SameLine();
}
node.end->accept(*this);
ImGui::NewLine();
node.block->accept(*this);
write(node._endOffset, "end repeat", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::IfStmtNode &node) override {
{
write(node._startOffset, "if ", _state->_colors._keyword_color);
ImGui::SameLine();
node.condition->accept(*this);
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " then ");
}
node.block1->accept(*this);
if (node.hasElse) {
write(node.block2->_startOffset, "else ", _state->_colors._keyword_color);
node.block2->accept(*this);
}
write(node._endOffset, "end if", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::TellStmtNode &node) override {
write(node._startOffset, "tell ", _state->_colors._keyword_color);
ImGui::SameLine();
node.window->accept(*this);
ImGui::NewLine();
node.block->accept(*this);
write(node._endOffset, "end tell", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::EndCaseNode &node) override {
write(node._endOffset, "end case", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::CaseLabelNode &node) override {
renderLine(node._startOffset);
renderIndentation();
bool parenValue = node.value->hasSpaces(_dot);
if (parenValue) {
ImGui::Text("(");
ImGui::SameLine();
}
node.value->accept(*this);
if (parenValue) {
ImGui::Text(")");
ImGui::SameLine();
}
if (node.nextOr) {
ImGui::Text(",");
ImGui::SameLine();
node.nextOr->accept(*this);
} else {
ImGui::Text(":");
node.block->accept(*this);
}
if (node.nextLabel) {
node.nextLabel->accept(*this);
}
}
virtual void visit(const LingoDec::ChunkExprNode &node) override {
ImGui::Text(LingoDec::StandardNames::chunkTypeNames[node.type]);
ImGui::SameLine();
ImGui::Text(" ");
ImGui::SameLine();
node.first->accept(*this);
if (!(node.last->type == LingoDec::kLiteralNode && node.last->getValue()->type == LingoDec::kDatumInt && node.last->getValue()->i == 0)) {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " to ");
ImGui::SameLine();
node.last->accept(*this);
}
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
ImGui::SameLine();
node.string->accept(*this);
}
virtual void visit(const LingoDec::InverseOpNode &node) override {
ImGui::Text("-");
ImGui::SameLine();
bool parenOperand = node.operand->hasSpaces(_dot);
if (parenOperand) {
ImGui::Text("(");
ImGui::SameLine();
}
node.operand->accept(*this);
if (parenOperand) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::CaseStmtNode &node) override {
write(node._startOffset, "case ", _state->_colors._keyword_color);
ImGui::SameLine();
node.value->accept(*this);
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
indent();
if (node.firstLabel) {
node.firstLabel->accept(*this);
}
if (node.otherwise) {
node.otherwise->accept(*this);
}
unindent();
write(node._endOffset, "end case", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::ObjCallNode &node) override {
auto &rawArgs = node.argList->getValue()->l;
auto &obj = rawArgs[0];
bool parenObj = obj->hasSpaces(_dot);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::Text(".");
ImGui::SameLine();
ImGui::Text(node.name.c_str());
ImGui::SameLine();
ImGui::Text("(");
ImGui::SameLine();
for (size_t i = 1; i < rawArgs.size(); i++) {
if (i > 1) {
ImGui::Text(",");
ImGui::SameLine();
}
rawArgs[i]->accept(*this);
}
ImGui::Text(")");
ImGui::SameLine();
}
virtual void visit(const LingoDec::ObjPropExprNode &node) override {
if (_dot) {
bool parenObj = node.obj->hasSpaces(_dot);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
node.obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::Text(".");
ImGui::SameLine();
ImGui::Text("%s", node.prop.c_str());
ImGui::SameLine();
} else {
ImGui::TextColored(_state->_colors._the_color, "the %s", node.prop.c_str());
ImGui::SameLine();
ImGui::TextColored(_state->_colors._keyword_color, " of ");
ImGui::SameLine();
bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
node.obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
}
}
virtual void visit(const LingoDec::BinaryOpNode &node) override {
unsigned int precedence = node.getPrecedence();
bool parenLeft = false;
bool parenRight = false;
if (precedence) {
if (node.left->type == LingoDec::kBinaryOpNode) {
auto leftBinaryOpNode = static_cast<LingoDec::BinaryOpNode *>(node.left.get());
parenLeft = (leftBinaryOpNode->getPrecedence() != precedence);
}
parenRight = (node.right->type == LingoDec::kBinaryOpNode);
}
if (parenLeft) {
ImGui::Text("(");
ImGui::SameLine();
}
node.left->accept(*this);
if (parenLeft) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::Text(" ");
ImGui::SameLine();
ImGui::Text(LingoDec::StandardNames::binaryOpNames[node.opcode]);
ImGui::SameLine();
ImGui::Text(" ");
ImGui::SameLine();
if (parenRight) {
ImGui::Text("(");
ImGui::SameLine();
}
node.right->accept(*this);
if (parenRight) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::OtherwiseNode &node) override {
write(node._startOffset, "otherwise:", _state->_colors._keyword_color);
node.block->accept(*this);
}
virtual void visit(const LingoDec::MemberExprNode &node) override {
bool hasCastID = node.castID && !(node.castID->type == LingoDec::kLiteralNode && node.castID->getValue()->type == LingoDec::kDatumInt && node.castID->getValue()->i == 0);
ImGui::Text(node.type.c_str());
ImGui::SameLine();
ImGui::Text(" ");
ImGui::SameLine();
if (_dot) {
ImGui::Text("(");
ImGui::SameLine();
node.memberID->accept(*this);
if (hasCastID) {
ImGui::Text(",");
ImGui::SameLine();
node.castID->accept(*this);
}
ImGui::Text(")");
ImGui::SameLine();
} else {
bool parenMemberID = (node.memberID->type == LingoDec::kBinaryOpNode);
if (parenMemberID) {
ImGui::Text("(");
ImGui::SameLine();
}
node.memberID->accept(*this);
if (parenMemberID) {
ImGui::Text(")");
ImGui::SameLine();
}
if (hasCastID) {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), "of castLib");
ImGui::SameLine();
bool parenCastID = (node.castID->type == LingoDec::kBinaryOpNode);
if (parenCastID) {
ImGui::Text("(");
ImGui::SameLine();
}
node.castID->accept(*this);
if (parenCastID) {
ImGui::Text(")");
ImGui::SameLine();
}
}
}
}
virtual void visit(const LingoDec::PlayCmdStmtNode &node) override {
auto &rawArgs = node.argList->getValue()->l;
write(node._startOffset, "play ", _state->_colors._keyword_color);
ImGui::SameLine();
if (rawArgs.size() == 0) {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " done");
ImGui::SameLine();
return;
}
auto &frame = rawArgs[0];
if (rawArgs.size() == 1) {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " frame ");
ImGui::SameLine();
frame->accept(*this);
return;
}
auto &movie = rawArgs[1];
if (!(frame->type == LingoDec::kLiteralNode && frame->getValue()->type == LingoDec::kDatumInt && frame->getValue()->i == 1)) {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " frame ");
ImGui::SameLine();
frame->accept(*this);
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
ImGui::SameLine();
}
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " movie ");
ImGui::SameLine();
movie->accept(*this);
}
virtual void visit(const LingoDec::ThePropExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), "the ");
ImGui::SameLine();
ImGui::Text(node.prop.c_str());
ImGui::SameLine();
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
ImGui::SameLine();
bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
node.obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::MenuPropExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of menu ", LingoDec::StandardNames::menuPropertyNames[node.prop]);
ImGui::SameLine();
bool parenMenuID = (node.menuID->type == LingoDec::kBinaryOpNode);
if (parenMenuID) {
ImGui::Text("(");
ImGui::SameLine();
}
node.menuID->accept(*this);
if (parenMenuID) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::SoundCmdStmtNode &node) override {
write(node._startOffset, "sound ", _state->_colors._keyword_color);
ImGui::SameLine();
ImGui::Text(node.cmd.c_str());
ImGui::SameLine();
ImGui::Text(" ");
ImGui::SameLine();
if (node.argList->getValue()->l.size() > 0) {
node.argList->accept(*this);
}
ImGui::NewLine();
}
virtual void visit(const LingoDec::SoundPropExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of sound ", LingoDec::StandardNames::soundPropertyNames[node.prop]);
ImGui::SameLine();
bool parenSoundID = (node.soundID->type == LingoDec::kBinaryOpNode);
if (parenSoundID) {
ImGui::Text("(");
ImGui::SameLine();
}
node.soundID->accept(*this);
if (parenSoundID) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::AssignmentStmtNode &node) override {
if (!_dot) {
write(node._startOffset, "set ", _state->_colors._keyword_color);
ImGui::SameLine();
node.variable->accept(*this);
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " to ");
ImGui::SameLine();
node.value->accept(*this);
} else {
node.variable->accept(*this);
ImGui::Text(" = ");
ImGui::SameLine();
node.value->accept(*this);
}
ImGui::NewLine();
}
virtual void visit(const LingoDec::ExitRepeatStmtNode &node) override {
write(node._startOffset, "exit repeat", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::NextRepeatStmtNode &node) override {
write(node._startOffset, "next repeat", _state->_colors._keyword_color);
}
virtual void visit(const LingoDec::ObjBracketExprNode &node) override {
bool parenObj = node.obj->hasSpaces(_dot);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
node.obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::Text("[");
ImGui::SameLine();
node.prop->accept(*this);
ImGui::Text("]");
ImGui::SameLine();
}
virtual void visit(const LingoDec::SpritePropExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of sprite ", LingoDec::StandardNames::spritePropertyNames[node.prop]);
ImGui::SameLine();
bool parenSpriteID = (node.spriteID->type == LingoDec::kBinaryOpNode);
if (parenSpriteID) {
ImGui::Text("(");
ImGui::SameLine();
}
node.spriteID->accept(*this);
if (parenSpriteID) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::ChunkDeleteStmtNode &node) override {
write(node._startOffset, "delete", _state->_colors._keyword_color);
ImGui::SameLine();
node.chunk->accept(*this);
}
virtual void visit(const LingoDec::ChunkHiliteStmtNode &node) override {
write(node._startOffset, "hilite", _state->_colors._keyword_color);
ImGui::SameLine();
node.chunk->accept(*this);
}
virtual void visit(const LingoDec::MenuItemPropExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of menuItem ", LingoDec::StandardNames::menuItemPropertyNames[node.prop]);
ImGui::SameLine();
bool parenItemID = (node.itemID->type == LingoDec::kBinaryOpNode);
if (parenItemID) {
ImGui::Text("(");
ImGui::SameLine();
}
node.itemID->accept(*this);
if (parenItemID) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of menu ");
ImGui::SameLine();
bool parenMenuID = (node.menuID->type == LingoDec::kBinaryOpNode);
if (parenMenuID) {
ImGui::Text("(");
ImGui::SameLine();
}
node.menuID->accept(*this);
if (parenMenuID) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::ObjPropIndexExprNode &node) override {
bool parenObj = node.obj->hasSpaces(_dot);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
node.obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::Text(".");
ImGui::SameLine();
ImGui::Text(node.prop.c_str());
ImGui::SameLine();
ImGui::Text("[");
ImGui::SameLine();
node.index->accept(*this);
if (node.index2) {
ImGui::Text("..");
ImGui::SameLine();
node.index2->accept(*this);
}
ImGui::Text("]");
ImGui::SameLine();
}
virtual void visit(const LingoDec::SpriteWithinExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), "sprite ");
ImGui::SameLine();
bool parenFirstSprite = (node.firstSprite->type == LingoDec::kBinaryOpNode);
if (parenFirstSprite) {
ImGui::Text("(");
ImGui::SameLine();
}
node.firstSprite->accept(*this);
if (parenFirstSprite) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " within ");
ImGui::SameLine();
bool parenSecondSprite = (node.secondSprite->type == LingoDec::kBinaryOpNode);
if (parenSecondSprite) {
ImGui::Text("(");
ImGui::SameLine();
}
node.secondSprite->accept(*this);
if (parenSecondSprite) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::LastStringChunkExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._the_color), "the last %s in ", LingoDec::StandardNames::chunkTypeNames[node.type]);
ImGui::SameLine();
bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
node.obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::SpriteIntersectsExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._keyword_color), "sprite ");
ImGui::SameLine();
bool parenFirstSprite = (node.firstSprite->type == LingoDec::kBinaryOpNode);
if (parenFirstSprite) {
ImGui::Text("(");
ImGui::SameLine();
}
node.firstSprite->accept(*this);
if (parenFirstSprite) {
ImGui::Text(")");
ImGui::SameLine();
}
ImGui::TextColored(ImColor(_state->_colors._keyword_color), " intersects ");
ImGui::SameLine();
bool parenSecondSprite = (node.secondSprite->type == LingoDec::kBinaryOpNode);
if (parenSecondSprite) {
ImGui::Text("(");
ImGui::SameLine();
}
node.secondSprite->accept(*this);
if (parenSecondSprite) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::StringChunkCountExprNode &node) override {
ImGui::TextColored(ImColor(_state->_colors._the_color), "the number of %ss in ", LingoDec::StandardNames::chunkTypeNames[node.type]);
ImGui::SameLine();
bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
if (parenObj) {
ImGui::Text("(");
ImGui::SameLine();
}
node.obj->accept(*this);
if (parenObj) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void visit(const LingoDec::VarNode &node) override {
renderVar(node.varName);
}
virtual void visit(const LingoDec::NotOpNode &node) override {
ImGui::Text("not ");
ImGui::SameLine();
bool parenOperand = node.operand->hasSpaces(_dot);
if (parenOperand) {
ImGui::Text("(");
ImGui::SameLine();
}
node.operand->accept(*this);
if (parenOperand) {
ImGui::Text(")");
ImGui::SameLine();
}
}
virtual void defaultVisit(const LingoDec::Node &node) override {
LingoDec::CodeWriterVisitor code(_dot, false);
node.accept(code);
if (node.isStatement) {
renderLine(node._startOffset);
renderIndentation();
}
ImGui::Text("%s", code._str.c_str());
}
private:
void write(LingoDec::Datum &datum) {
switch (datum.type) {
case LingoDec::kDatumVoid:
ImGui::TextColored(_state->_colors._keyword_color, "VOID");
ImGui::SameLine();
return;
case LingoDec::kDatumSymbol:
ImGui::Text("#%s", datum.s.c_str());
ImGui::SameLine();
return;
case LingoDec::kDatumVarRef:
ImGui::TextColored(_state->_colors._var_color, datum.s.c_str());
ImGui::SameLine();
return;
case LingoDec::kDatumString:
if (datum.s.empty()) {
ImGui::TextColored(_state->_colors._keyword_color, "EMPTY");
ImGui::SameLine();
return;
}
if (datum.s.size() == 1) {
switch (datum.s[0]) {
case '\x03':
ImGui::TextColored(_state->_colors._keyword_color, "ENTER");
ImGui::SameLine();
return;
case '\x08':
ImGui::TextColored(_state->_colors._keyword_color, "BACKSPACE");
ImGui::SameLine();
return;
case '\t':
ImGui::TextColored(_state->_colors._keyword_color, "TAB");
ImGui::SameLine();
return;
case '\r':
ImGui::TextColored(_state->_colors._keyword_color, "RETURN");
ImGui::SameLine();
return;
case '"':
ImGui::TextColored(_state->_colors._keyword_color, "QUOTE");
ImGui::SameLine();
return;
default:
break;
}
}
ImGui::Text("\"%s\"", datum.s.c_str());
ImGui::SameLine();
return;
case LingoDec::kDatumInt:
ImGui::TextColored(_state->_colors._literal_color, "%d", datum.i);
ImGui::SameLine();
return;
case LingoDec::kDatumFloat:
ImGui::TextColored(_state->_colors._literal_color, "%g", datum.f);
ImGui::SameLine();
return;
case LingoDec::kDatumList:
case LingoDec::kDatumArgList:
case LingoDec::kDatumArgListNoRet: {
if (datum.type == LingoDec::kDatumList) {
ImGui::Text("[");
ImGui::SameLine();
}
for (size_t ii = 0; ii < datum.l.size(); ii++) {
if (ii > 0) {
ImGui::Text(", ");
ImGui::SameLine();
}
datum.l[ii]->accept(*this);
}
if (datum.type == LingoDec::kDatumList) {
ImGui::Text("]");
ImGui::SameLine();
}
}
return;
case LingoDec::kDatumPropList: {
ImGui::Text("[");
ImGui::SameLine();
if (datum.l.size() == 0) {
ImGui::Text(":");
ImGui::SameLine();
} else {
for (size_t ii = 0; ii < datum.l.size(); ii += 2) {
if (ii > 0) {
ImGui::Text(", ");
ImGui::SameLine();
}
datum.l[ii]->accept(*this);
ImGui::Text(": ");
ImGui::SameLine();
datum.l[ii + 1]->accept(*this);
}
}
ImGui::Text("]");
ImGui::SameLine();
}
return;
}
}
void renderVar(const Common::String &varName) {
ImGui::TextColored(_state->_colors._var_color, "%s", varName.c_str());
if (ImGui::IsItemHovered() && g_lingo->_globalvars.contains(varName)) {
const Datum &val = g_lingo->_globalvars.getVal(varName);
ImGui::BeginTooltip();
ImGui::Text("%s", varName.c_str());
ImGui::Text("Click to add to watches.");
Common::String s = val.asString(true);
s.wordWrap(150);
if (s.size() > 4000) {
uint chop = s.size() - 4000;
s.chop(s.size() - 4000);
s += Common::String::format("... [chopped %d chars]", chop);
}
ImGui::Text("= %s", s.c_str());
ImGui::EndTooltip();
}
if (ImGui::IsItemClicked()) {
_state->_variables[varName] = true;
}
ImGui::SameLine();
}
void lingoCode(const LingoDec::HandlerNode &node) {
if (_script.isGenericEvent) {
node.block->accept(*this);
return;
}
bool isMethod = _script.isMethod;
write(node._startOffset, isMethod ? "method " : "on ", _state->_colors._keyword_color);
ImGui::SameLine();
ImGui::TextColored(_state->_colors._call_color, "%s", _script.handlerId.c_str());
ImGui::SameLine();
if (!_script.argumentNames.empty()) {
ImGui::Text(" ");
ImGui::SameLine();
for (size_t i = 0; i < _script.argumentNames.size(); i++) {
if (i > 0) {
ImGui::Text(", ");
ImGui::SameLine();
}
ImGui::Text("%s", _script.argumentNames[i].c_str());
ImGui::SameLine();
}
}
if (_state->_dbg._goToDefinition && _scrollTo) {
ImGui::SetScrollHereY(0.5f);
_state->_dbg._goToDefinition = false;
}
indent();
if (isMethod && !_script.propertyNames.empty() && node.handler == &node.handler->script->handlers[0]) {
ImGui::NewLine();
write(node._startOffset, "instance ");
ImGui::SameLine();
for (size_t i = 0; i < _script.propertyNames.size(); i++) {
if (i > 0)
ImGui::Text(",");
ImGui::SameLine();
ImGui::TextColored(_state->_colors._var_color, "%s", _script.propertyNames[i].c_str());
ImGui::SameLine();
}
}
if (!_script.globalNames.empty()) {
ImGui::NewLine();
write(node._startOffset, "global ");
ImGui::SameLine();
for (size_t i = 0; i < _script.globalNames.size(); i++) {
if (i > 0) {
ImGui::Text(",");
ImGui::SameLine();
}
renderVar(_script.globalNames[i]);
ImGui::SameLine();
}
}
ImGui::NewLine();
unindent();
node.block->accept(*this);
if (!isMethod) {
write(node._endOffset, "end", _state->_colors._keyword_color);
}
}
void byteCode(const LingoDec::HandlerNode &node) {
bool isMethod = _script.isMethod;
if (!_script.isGenericEvent) {
Common::String code;
if (isMethod) {
code += "method ";
} else {
code += "on ";
}
code += _script.handlerId;
if (_script.argumentNames.size() > 0) {
code += " ";
for (size_t i = 0; i < _script.argumentNames.size(); i++) {
if (i > 0)
code += ", ";
code += _script.argumentNames[i];
}
}
writeByteCode(0, code);
}
for (uint i = 0; i < _script.bytecodeArray.size(); i++) {
LingoDec::CodeWriterVisitor code(_dot, true);
code.indent();
auto &bytecode = _script.bytecodeArray[i];
code.write(LingoDec::StandardNames::getOpcodeName(bytecode.opID));
switch (bytecode.opcode) {
case LingoDec::kOpJmp:
case LingoDec::kOpJmpIfZ:
code.write(" ");
code.write(posToString(bytecode.pos + bytecode.obj));
break;
case LingoDec::kOpEndRepeat:
code.write(" ");
code.write(posToString(bytecode.pos - bytecode.obj));
break;
case LingoDec::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(">");
}
}
writeByteCode(bytecode.pos, code._str);
}
if (!_script.isGenericEvent) {
if (!isMethod) {
writeByteCode(node._endOffset, "end");
}
}
}
void write(uint32 offset, const Common::String &code, ImVec4 color = ImVec4(1, 1, 1, 1)) {
renderLine(offset);
renderIndentation();
ImGui::TextColored(color, "%s", code.c_str());
}
void writeByteCode(uint32 offset, const Common::String &code) {
renderLine(offset);
Common::String s;
for (int i = 0; i < _indent; i++) {
s += " ";
}
ImGui::Text("%s", (s + code).c_str());
}
void renderLine(uint p) {
bool showCurrentStatement = false;
p = MIN(p, _script.byteOffsets.size() - 1);
uint pc = _script.byteOffsets[p];
_script.startOffsets.push_back(pc);
if (_script.pc != 0 && pc >= _script.pc) {
if (!_currentStatementDisplayed) {
showCurrentStatement = true;
_currentStatementDisplayed = true;
}
} else if (_isScriptInDebug && g_lingo->_exec._state == kPause) {
// check current statement
if (!_currentStatementDisplayed) {
if (g_lingo->_state->pc <= pc) {
showCurrentStatement = true;
_currentStatementDisplayed = true;
}
}
}
ImDrawList *dl = ImGui::GetWindowDrawList();
const ImVec2 pos = ImGui::GetCursorScreenPos();
const float width = ImGui::GetContentRegionAvail().x;
const ImVec2 mid(pos.x + 7, pos.y + 7);
ImVec4 color = _state->_colors._bp_color_disabled;
const Director::Breakpoint *bp = getBreakpoint(_script.handlerId, _script.id.member, pc);
if (bp)
color = _state->_colors._bp_color_enabled;
// Need to give a new id for each button
Common::String id = _script.handlerId + _renderLineID;
ImGui::PushID(id.c_str());
ImGui::InvisibleButton("Line", ImVec2(16, ImGui::GetFontSize()));
ImGui::PopID();
_renderLineID++;
// click on breakpoint column?
if (ImGui::IsItemClicked(0)) {
if (color == _state->_colors._bp_color_enabled) {
g_lingo->delBreakpoint(bp->id);
color = _state->_colors._bp_color_disabled;
} else {
Director::Breakpoint newBp;
newBp.type = kBreakpointFunction;
newBp.scriptId = _script.id.member;
newBp.funcName = _script.handlerId;
newBp.funcOffset = pc;
g_lingo->addBreakpoint(newBp);
color = _state->_colors._bp_color_enabled;
}
}
if (color == _state->_colors._bp_color_disabled && ImGui::IsItemHovered()) {
color = _state->_colors._bp_color_hover;
}
// draw breakpoint
if (!bp || bp->enabled)
dl->AddCircleFilled(mid, 4.0f, ImColor(color));
else
dl->AddCircle(mid, 4.0f, ImColor(_state->_colors._line_color));
// draw current statement
if (showCurrentStatement) {
dl->AddQuadFilled(ImVec2(pos.x, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 10.f), ImVec2(pos.x, pos.y + 10.f), ImColor(_state->_colors._current_statement));
dl->AddTriangleFilled(ImVec2(pos.x + 8.f, pos.y), ImVec2(pos.x + 14.f, pos.y + 7.f), ImVec2(pos.x + 8.f, pos.y + 14.f), ImColor(_state->_colors._current_statement));
if (_state->_dbg._scrollToPC && _scrollTo && g_lingo->_state->callstack.size() != _state->_dbg._callstackSize) {
ImGui::SetScrollHereY(0.5f);
_state->_dbg._scrollToPC = false;
}
dl->AddRectFilled(ImVec2(pos.x + 16.f, pos.y), ImVec2(pos.x + width, pos.y + 16.f), ImColor(IM_COL32(0xFF, 0xFF, 0x00, 0x20)), 0.4f);
}
// draw separator
dl->AddLine(ImVec2(pos.x + 16.0f, pos.y), ImVec2(pos.x + 16.0f, pos.y + 17), ImColor(_state->_colors._line_color));
ImGui::SetItemTooltip("Click to add a breakpoint");
ImGui::SameLine();
// draw offset
ImGui::Text("[%5d] ", pc);
ImGui::SameLine();
}
void renderIndentation(int indent) const {
for (int i = 0; i < indent; i++) {
ImGui::Text(" ");
ImGui::SameLine();
}
}
void renderIndentation() const {
renderIndentation(_indent);
}
void indent() {
_indent++;
}
void unindent() {
if (_indent > 0)
_indent--;
}
static Common::String posToString(int32 pos) {
return Common::String::format("[%3d]", pos);
}
private:
ImGuiScript &_script;
bool _showByteCode = false;
bool _dot = false;
int _indent = 0;
bool _currentStatementDisplayed = false;
bool _isScriptInDebug = false;
int _renderLineID = 1;
bool _scrollTo = false;
};
void renderScriptAST(ImGuiScript &script, bool showByteCode, bool scrollTo) {
RenderScriptVisitor visitor(script, showByteCode, scrollTo);
script.root->accept(visitor);
}
} // namespace DT
} // namespace Director