[Scummvm-git-logs] scummvm master -> 88239fb184d475c1ce8add9ceb6a271802d2c019
sev-
noreply at scummvm.org
Tue May 14 20:32:28 UTC 2024
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
88239fb184 DIRECTOR: Added ProjectorRays' Lingo Decompiler
Commit: 88239fb184d475c1ce8add9ceb6a271802d2c019
https://github.com/scummvm/scummvm/commit/88239fb184d475c1ce8add9ceb6a271802d2c019
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2024-05-14T22:32:02+02:00
Commit Message:
DIRECTOR: Added ProjectorRays' Lingo Decompiler
Hash id 5f23e27fcca9030b42f024d656f00b84c81d3cfc
Changed paths:
A engines/director/lingo/lingodec/ast.cpp
A engines/director/lingo/lingodec/ast.h
A engines/director/lingo/lingodec/codewriter.cpp
A engines/director/lingo/lingodec/codewriter.h
A engines/director/lingo/lingodec/context.cpp
A engines/director/lingo/lingodec/context.h
A engines/director/lingo/lingodec/enums.h
A engines/director/lingo/lingodec/handler.cpp
A engines/director/lingo/lingodec/handler.h
A engines/director/lingo/lingodec/names.cpp
A engines/director/lingo/lingodec/names.h
A engines/director/lingo/lingodec/resolver.h
A engines/director/lingo/lingodec/script.cpp
A engines/director/lingo/lingodec/script.h
engines/director/lingo/lingo.cpp
engines/director/module.mk
engines/director/movie.cpp
engines/director/util.cpp
engines/director/util.h
diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp
index dc1ec563c00..8c8ce36f24f 100644
--- a/engines/director/lingo/lingo.cpp
+++ b/engines/director/lingo/lingo.cpp
@@ -336,6 +336,8 @@ Symbol Lingo::getHandler(const Common::String &name) {
if (_state->context && _state->context->_functionHandlers.contains(name))
return _state->context->_functionHandlers[name];
+ warning("getHandler('%s'), movie is: %s", name.c_str(), g_director->getCurrentMovie()->getMacName().c_str());
+
sym = g_director->getCurrentMovie()->getHandler(name);
if (sym.type != VOIDSYM)
return sym;
diff --git a/engines/director/lingo/lingodec/ast.cpp b/engines/director/lingo/lingodec/ast.cpp
new file mode 100644
index 00000000000..49aa410ac6f
--- /dev/null
+++ b/engines/director/lingo/lingodec/ast.cpp
@@ -0,0 +1,1169 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include "common/util.h"
+#include "./ast.h"
+#include "./codewriter.h"
+#include "./handler.h"
+#include "./names.h"
+#include "./script.h"
+
+namespace LingoDec {
+
+/* Datum */
+
+int Datum::toInt() {
+ switch (type) {
+ case kDatumInt:
+ return i;
+ case kDatumFloat:
+ return f;
+ default:
+ break;
+ }
+ return 0;
+}
+
+void Datum::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ switch (type) {
+ case kDatumVoid:
+ code.write("VOID");
+ return;
+ case kDatumSymbol:
+ code.write("#" + s);
+ return;
+ case kDatumVarRef:
+ code.write(s);
+ return;
+ case kDatumString:
+ if (s.size() == 0) {
+ code.write("EMPTY");
+ return;
+ }
+ if (s.size() == 1) {
+ switch (s[0]) {
+ case '\x03':
+ code.write("ENTER");
+ return;
+ case '\x08':
+ code.write("BACKSPACE");
+ return;
+ case '\t':
+ code.write("TAB");
+ return;
+ case '\r':
+ code.write("RETURN");
+ return;
+ case '"':
+ code.write("QUOTE");
+ return;
+ default:
+ break;
+ }
+ }
+ if (sum) {
+ code.write("\"" + Common::toPrintable(s) + "\"");
+ return;
+ }
+ code.write("\"" + s + "\"");
+ return;
+ case kDatumInt:
+ code.write(Common::String::format("%d", i));
+ return;
+ case kDatumFloat:
+ code.write(Common::String::format("%g", f));
+ return;
+ case kDatumList:
+ case kDatumArgList:
+ case kDatumArgListNoRet:
+ {
+ if (type == kDatumList)
+ code.write("[");
+ for (size_t ii = 0; ii < l.size(); ii++) {
+ if (ii > 0)
+ code.write(", ");
+ l[ii]->writeScriptText(code, dot, sum);
+ }
+ if (type == kDatumList)
+ code.write("]");
+ }
+ return;
+ case kDatumPropList:
+ {
+ code.write("[");
+ if (l.size() == 0) {
+ code.write(":");
+ } else {
+ for (size_t ii = 0; ii < l.size(); ii += 2) {
+ if (ii > 0)
+ code.write(", ");
+ l[ii]->writeScriptText(code, dot, sum);
+ code.write(": ");
+ l[ii + 1]->writeScriptText(code, dot, sum);
+ }
+ }
+ code.write("]");
+ }
+ return;
+ }
+}
+
+/* AST */
+
+void AST::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ root->writeScriptText(code, dot, sum);
+}
+
+void AST::addStatement(Common::SharedPtr<Node> statement) {
+ currentBlock->addChild(Common::move(statement));
+}
+
+void AST::enterBlock(BlockNode *block) {
+ currentBlock = block;
+}
+
+void AST::exitBlock() {
+ auto ancestorStatement = currentBlock->ancestorStatement();
+ if (!ancestorStatement) {
+ currentBlock = nullptr;
+ return;
+ }
+
+ auto block = ancestorStatement->parent;
+ if (!block || block->type != kBlockNode) {
+ currentBlock = nullptr;
+ return;
+ }
+
+ currentBlock = static_cast<BlockNode *>(block);
+}
+
+/* Node */
+
+Common::SharedPtr<Datum> Node::getValue() {
+ return Common::SharedPtr<Datum>(new Datum());
+}
+
+Node *Node::ancestorStatement() {
+ Node *ancestor = parent;
+ while (ancestor && !ancestor->isStatement) {
+ ancestor = ancestor->parent;
+ }
+ return ancestor;
+}
+
+LoopNode *Node::ancestorLoop() {
+ Node *ancestor = parent;
+ while (ancestor && !ancestor->isLoop) {
+ ancestor = ancestor->parent;
+ }
+ return static_cast<LoopNode *>(ancestor);
+}
+
+bool Node::hasSpaces(bool) {
+ return true;
+}
+
+/* ErrorNode */
+
+void ErrorNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("ERROR");
+}
+
+bool ErrorNode::hasSpaces(bool) {
+ return false;
+}
+
+/* CommentNode */
+
+void CommentNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("-- ");
+ code.write(text);
+}
+
+/* LiteralNode */
+
+void LiteralNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ value->writeScriptText(code, dot, sum);
+}
+
+Common::SharedPtr<Datum> LiteralNode::getValue() {
+ return value;
+}
+
+bool LiteralNode::hasSpaces(bool) {
+ return false;
+}
+
+/* BlockNode */
+
+void BlockNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ for (const auto &child : children) {
+ child->writeScriptText(code, dot, sum);
+ code.writeLine();
+ }
+}
+
+void BlockNode::addChild(Common::SharedPtr<Node> child) {
+ child->parent = this;
+ children.push_back(Common::move(child));
+}
+
+/* HandlerNode */
+
+void HandlerNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ if (handler->isGenericEvent) {
+ block->writeScriptText(code, dot, sum);
+ } else {
+ Script *script = handler->script;
+ bool isMethod = script->isFactory();
+ if (isMethod) {
+ code.write("method ");
+ } else {
+ code.write("on ");
+ }
+ code.write(handler->name);
+ if (handler->argumentNames.size() > 0) {
+ code.write(" ");
+ for (size_t i = 0; i < handler->argumentNames.size(); i++) {
+ if (i > 0)
+ code.write(", ");
+ code.write(handler->argumentNames[i]);
+ }
+ }
+ code.writeLine();
+ code.indent();
+ if (isMethod && script->propertyNames.size() > 0 && handler == &script->handlers[0]) {
+ code.write("instance ");
+ for (size_t i = 0; i < script->propertyNames.size(); i++) {
+ if (i > 0)
+ code.write(", ");
+ code.write(script->propertyNames[i]);
+ }
+ code.writeLine();
+ }
+ if (handler->globalNames.size() > 0) {
+ code.write("global ");
+ for (size_t i = 0; i < handler->globalNames.size(); i++) {
+ if (i > 0)
+ code.write(", ");
+ code.write(handler->globalNames[i]);
+ }
+ code.writeLine();
+ }
+ block->writeScriptText(code, dot, sum);
+ code.unindent();
+ if (!isMethod) {
+ code.writeLine("end");
+ }
+ }
+}
+
+/* ExitStmtNode */
+
+void ExitStmtNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("exit");
+}
+
+/* InverseOpNode */
+
+void InverseOpNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("-");
+
+ bool parenOperand = operand->hasSpaces(dot);
+ if (parenOperand) {
+ code.write("(");
+ }
+ operand->writeScriptText(code, dot, sum);
+ if (parenOperand) {
+ code.write(")");
+ }
+}
+
+/* NotOpNode */
+
+void NotOpNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("not ");
+
+ bool parenOperand = operand->hasSpaces(dot);
+ if (parenOperand) {
+ code.write("(");
+ }
+ operand->writeScriptText(code, dot, sum);
+ if (parenOperand) {
+ code.write(")");
+ }
+}
+
+/* BinaryOpNode */
+
+void BinaryOpNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ unsigned int precedence = getPrecedence();
+ bool parenLeft = false;
+ bool parenRight = false;
+ if (precedence) {
+ if (left->type == kBinaryOpNode) {
+ auto leftBinaryOpNode = static_cast<BinaryOpNode *>(left.get());
+ parenLeft = (leftBinaryOpNode->getPrecedence() != precedence);
+ }
+ parenRight = (right->type == kBinaryOpNode);
+ }
+
+ if (parenLeft) {
+ code.write("(");
+ }
+ left->writeScriptText(code, dot, sum);
+ if (parenLeft) {
+ code.write(")");
+ }
+
+ code.write(" ");
+ code.write(StandardNames::getName(StandardNames::binaryOpNames, opcode));
+ code.write(" ");
+
+ if (parenRight) {
+ code.write("(");
+ }
+ right->writeScriptText(code, dot, sum);
+ if (parenRight) {
+ code.write(")");
+ }
+}
+
+unsigned int BinaryOpNode::getPrecedence() const {
+ switch (opcode) {
+ case kOpMul:
+ case kOpDiv:
+ case kOpMod:
+ return 1;
+ case kOpAdd:
+ case kOpSub:
+ return 2;
+ case kOpLt:
+ case kOpLtEq:
+ case kOpNtEq:
+ case kOpEq:
+ case kOpGt:
+ case kOpGtEq:
+ return 3;
+ case kOpAnd:
+ return 4;
+ case kOpOr:
+ return 5;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* ChunkExprNode */
+
+void ChunkExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write(StandardNames::getName(StandardNames::chunkTypeNames, type));
+ code.write(" ");
+ first->writeScriptText(code, dot, sum);
+ if (!(last->type == kLiteralNode && last->getValue()->type == kDatumInt && last->getValue()->i == 0)) {
+ code.write(" to ");
+ last->writeScriptText(code, dot, sum);
+ }
+ code.write(" of ");
+ string->writeScriptText(code, false, sum); // we want the string to always be verbose
+}
+
+/* ChunkHiliteStmtNode */
+
+void ChunkHiliteStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("hilite ");
+ chunk->writeScriptText(code, dot, sum);
+}
+
+/* ChunkDeleteStmtNode */
+
+void ChunkDeleteStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("delete ");
+ chunk->writeScriptText(code, dot, sum);
+}
+
+/* SpriteIntersectsExprNode */
+
+void SpriteIntersectsExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("sprite ");
+
+ bool parenFirstSprite = (firstSprite->type == kBinaryOpNode);
+ if (parenFirstSprite) {
+ code.write("(");
+ }
+ firstSprite->writeScriptText(code, dot, sum);
+ if (parenFirstSprite) {
+ code.write(")");
+ }
+
+ code.write(" intersects ");
+
+ bool parenSecondSprite = (secondSprite->type == kBinaryOpNode);
+ if (parenSecondSprite) {
+ code.write("(");
+ }
+ secondSprite->writeScriptText(code, dot, sum);
+ if (parenSecondSprite) {
+ code.write(")");
+ }
+}
+
+/* SpriteWithinExprNode */
+
+void SpriteWithinExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("sprite ");
+
+ bool parenFirstSprite = (firstSprite->type == kBinaryOpNode);
+ if (parenFirstSprite) {
+ code.write("(");
+ }
+ firstSprite->writeScriptText(code, dot, sum);
+ if (parenFirstSprite) {
+ code.write(")");
+ }
+
+ code.write(" within ");
+
+ bool parenSecondSprite = (secondSprite->type == kBinaryOpNode);
+ if (parenSecondSprite) {
+ code.write("(");
+ }
+ secondSprite->writeScriptText(code, dot, sum);
+ if (parenSecondSprite) {
+ code.write(")");
+ }
+}
+
+/* MemberExprNode */
+
+void MemberExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ bool hasCastID = castID && !(castID->type == kLiteralNode && castID->getValue()->type == kDatumInt && castID->getValue()->i == 0);
+ code.write(type);
+ if (dot) {
+ code.write("(");
+ memberID->writeScriptText(code, dot, sum);
+ if (hasCastID) {
+ code.write(", ");
+ castID->writeScriptText(code, dot, sum);
+ }
+ code.write(")");
+ } else {
+ code.write(" ");
+
+ bool parenMemberID = (memberID->type == kBinaryOpNode);
+ if (parenMemberID) {
+ code.write("(");
+ }
+ memberID->writeScriptText(code, dot, sum);
+ if (parenMemberID) {
+ code.write(")");
+ }
+
+ if (hasCastID) {
+ code.write(" of castLib ");
+
+ bool parenCastID = (castID->type == kBinaryOpNode);
+ if (parenCastID) {
+ code.write("(");
+ }
+ castID->writeScriptText(code, dot, sum);
+ if (parenCastID) {
+ code.write(")");
+ }
+ }
+ }
+}
+
+bool MemberExprNode::hasSpaces(bool dot) {
+ return !dot;
+}
+
+/* VarNode */
+
+void VarNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write(varName);
+}
+
+bool VarNode::hasSpaces(bool) {
+ return false;
+}
+
+/* AssignmentStmtNode */
+
+void AssignmentStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ if (!dot || forceVerbose) {
+ code.write("set ");
+ variable->writeScriptText(code, false, sum); // we want the variable to always be verbose
+ code.write(" to ");
+ value->writeScriptText(code, dot, sum);
+ } else {
+ variable->writeScriptText(code, dot, sum);
+ code.write(" = ");
+ value->writeScriptText(code, dot, sum);
+ }
+}
+
+/* IfStmtNode */
+
+void IfStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("if ");
+ condition->writeScriptText(code, dot, sum);
+ code.write(" then");
+ if (sum) {
+ if (hasElse) {
+ code.write(" / else");
+ }
+ } else {
+ code.writeLine();
+ code.indent();
+ block1->writeScriptText(code, dot, sum);
+ code.unindent();
+ if (hasElse) {
+ code.writeLine("else");
+ code.indent();
+ block2->writeScriptText(code, dot, sum);
+ code.unindent();
+ }
+ code.write("end if");
+ }
+}
+
+/* RepeatWhileStmtNode */
+
+void RepeatWhileStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("repeat while ");
+ condition->writeScriptText(code, dot, sum);
+ if (!sum) {
+ code.writeLine();
+ code.indent();
+ block->writeScriptText(code, dot, sum);
+ code.unindent();
+ code.write("end repeat");
+ }
+}
+
+/* RepeatWithInStmtNode */
+
+void RepeatWithInStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("repeat with ");
+ code.write(varName);
+ code.write(" in ");
+ list->writeScriptText(code, dot, sum);
+ if (!sum) {
+ code.writeLine();
+ code.indent();
+ block->writeScriptText(code, dot, sum);
+ code.unindent();
+ code.write("end repeat");
+ }
+}
+
+/* RepeatWithToStmtNode */
+
+void RepeatWithToStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("repeat with ");
+ code.write(varName);
+ code.write(" = ");
+ start->writeScriptText(code, dot, sum);
+ if (up) {
+ code.write(" to ");
+ } else {
+ code.write(" down to ");
+ }
+ end->writeScriptText(code, dot, sum);
+ if (!sum) {
+ code.writeLine();
+ code.indent();
+ block->writeScriptText(code, dot, sum);
+ code.unindent();
+ code.write("end repeat");
+ }
+}
+
+/* CaseLabelNode */
+
+void CaseLabelNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ if (sum) {
+ code.write("(case) ");
+ if (parent->type == kCaseLabelNode) {
+ auto parentLabel = static_cast<CaseLabelNode *>(parent);
+ if (parentLabel->nextOr.get() == this) {
+ code.write("..., ");
+ }
+ }
+
+ bool parenValue = value->hasSpaces(dot);
+ if (parenValue) {
+ code.write("(");
+ }
+ value->writeScriptText(code, dot, sum);
+ if (parenValue) {
+ code.write(")");
+ }
+
+ if (nextOr) {
+ code.write(", ...");
+ } else {
+ code.write(":");
+ }
+ } else {
+ bool parenValue = value->hasSpaces(dot);
+ if (parenValue) {
+ code.write("(");
+ }
+ value->writeScriptText(code, dot, sum);
+ if (parenValue) {
+ code.write(")");
+ }
+
+ if (nextOr) {
+ code.write(", ");
+ nextOr->writeScriptText(code, dot, sum);
+ } else {
+ code.writeLine(":");
+ code.indent();
+ block->writeScriptText(code, dot, sum);
+ code.unindent();
+ }
+ if (nextLabel) {
+ nextLabel->writeScriptText(code, dot, sum);
+ }
+ }
+}
+
+/* OtherwiseNode */
+
+void OtherwiseNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ if (sum) {
+ code.write("(case) otherwise:");
+ } else {
+ code.writeLine("otherwise:");
+ code.indent();
+ block->writeScriptText(code, dot, sum);
+ code.unindent();
+ }
+}
+
+/* EndCaseNode */
+
+void EndCaseNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("end case");
+}
+
+/* CaseStmtNode */
+
+void CaseStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("case ");
+ value->writeScriptText(code, dot, sum);
+ code.write(" of");
+ if (sum) {
+ if (!firstLabel) {
+ if (otherwise) {
+ code.write(" / otherwise:");
+ } else {
+ code.write(" / end case");
+ }
+ }
+ } else {
+ code.writeLine();
+ code.indent();
+ if (firstLabel) {
+ firstLabel->writeScriptText(code, dot, sum);
+ }
+ if (otherwise) {
+ otherwise->writeScriptText(code, dot, sum);
+ }
+ code.unindent();
+ code.write("end case");
+ }
+}
+
+void CaseStmtNode::addOtherwise() {
+ otherwise = Common::SharedPtr<OtherwiseNode>(new OtherwiseNode());
+ otherwise->parent = this;
+ otherwise->block->endPos = endPos;
+}
+
+/* TellStmtNode */
+
+void TellStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("tell ");
+ window->writeScriptText(code, dot, sum);
+ if (!sum) {
+ code.writeLine();
+ code.indent();
+ block->writeScriptText(code, dot, sum);
+ code.unindent();
+ code.write("end tell");
+ }
+}
+
+/* SoundCmdStmtNode */
+
+void SoundCmdStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("sound ");
+ code.write(cmd);
+ if (argList->getValue()->l.size() > 0) {
+ code.write(" ");
+ argList->writeScriptText(code, dot, sum);
+ }
+}
+
+/* PlayCmdStmtNode */
+
+void PlayCmdStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ auto &rawArgs = argList->getValue()->l;
+
+ code.write("play");
+
+ if (rawArgs.size() == 0) {
+ code.write(" done");
+ return;
+ }
+
+ auto &frame = rawArgs[0];
+ if (rawArgs.size() == 1) {
+ code.write(" frame ");
+ frame->writeScriptText(code, dot, sum);
+ return;
+ }
+
+ auto &movie = rawArgs[1];
+ if (!(frame->type == kLiteralNode && frame->getValue()->type == kDatumInt && frame->getValue()->i == 1)) {
+ code.write(" frame ");
+ frame->writeScriptText(code, dot, sum);
+ code.write(" of");
+ }
+ code.write(" movie ");
+ movie->writeScriptText(code, dot, sum);
+}
+
+/* CallNode */
+
+bool CallNode::noParens() const {
+ if (isStatement) {
+ // TODO: Make a complete list of commonly paren-less commands
+ if (name == "put")
+ return true;
+ if (name == "return")
+ return true;
+ }
+
+ return false;
+}
+
+bool CallNode::isMemberExpr() const {
+ if (isExpression) {
+ size_t nargs = argList->getValue()->l.size();
+ if (name == "cast" && (nargs == 1 || nargs == 2))
+ return true;
+ if (name == "member" && (nargs == 1 || nargs == 2))
+ return true;
+ if (name == "script" && (nargs == 1 || nargs == 2))
+ return true;
+ if (name == "castLib" && nargs == 1)
+ return true;
+ if (name == "window" && nargs == 1)
+ return true;
+ }
+
+ return false;
+}
+
+void CallNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ if (isExpression && argList->getValue()->l.size() == 0) {
+ if (name == "pi") {
+ code.write("PI");
+ return;
+ }
+ if (name == "space") {
+ code.write("SPACE");
+ return;
+ }
+ if (name == "void") {
+ code.write("VOID");
+ return;
+ }
+ }
+
+ if (!dot && isMemberExpr()) {
+ /**
+ * In some cases, member expressions such as `member 1 of castLib 1` compile
+ * to the function call `member(1, 1)`. However, this doesn't parse correctly
+ * in pre-dot-syntax versions of Director, and `put(member(1, 1))` does not
+ * compile. Therefore, we rewrite these expressions to the verbose syntax when
+ * in verbose mode.
+ */
+ code.write(name);
+ code.write(" ");
+
+ auto memberID = argList->getValue()->l[0];
+ bool parenMemberID = (memberID->type == kBinaryOpNode);
+ if (parenMemberID) {
+ code.write("(");
+ }
+ memberID->writeScriptText(code, dot, sum);
+ if (parenMemberID) {
+ code.write(")");
+ }
+
+ if (argList->getValue()->l.size() == 2) {
+ code.write(" of castLib ");
+
+ auto castID = argList->getValue()->l[1];
+ bool parenCastID = (castID->type == kBinaryOpNode);
+ if (parenCastID) {
+ code.write("(");
+ }
+ castID->writeScriptText(code, dot, sum);
+ if (parenCastID) {
+ code.write(")");
+ }
+ }
+ return;
+ }
+
+ code.write(name);
+ if (noParens()) {
+ code.write(" ");
+ argList->writeScriptText(code, dot, sum);
+ } else {
+ code.write("(");
+ argList->writeScriptText(code, dot, sum);
+ code.write(")");
+ }
+}
+
+bool CallNode::hasSpaces(bool dot) {
+ if (!dot && isMemberExpr())
+ return true;
+
+ if (noParens())
+ return true;
+
+ return false;
+}
+
+/* ObjCallNode */
+
+void ObjCallNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ auto &rawArgs = argList->getValue()->l;
+
+ auto &obj = rawArgs[0];
+ bool parenObj = obj->hasSpaces(dot);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, dot, sum);
+ if (parenObj) {
+ code.write(")");
+ }
+
+ code.write(".");
+ code.write(name);
+ code.write("(");
+ for (size_t i = 1; i < rawArgs.size(); i++) {
+ if (i > 1)
+ code.write(", ");
+ rawArgs[i]->writeScriptText(code, dot, sum);
+ }
+ code.write(")");
+}
+
+bool ObjCallNode::hasSpaces(bool) {
+ return false;
+}
+
+/* ObjCallV4Node */
+
+void ObjCallV4Node::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ obj->writeScriptText(code, dot, sum);
+ code.write("(");
+ argList->writeScriptText(code, dot, sum);
+ code.write(")");
+}
+
+bool ObjCallV4Node::hasSpaces(bool) {
+ return false;
+}
+
+/* TheExprNode */
+
+void TheExprNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("the ");
+ code.write(prop);
+}
+
+/* LastStringChunkExprNode */
+
+void LastStringChunkExprNode::writeScriptText(CodeWriter &code, bool, bool sum) const {
+ code.write("the last ");
+ code.write(StandardNames::getName(StandardNames::chunkTypeNames, type));
+ code.write(" in ");
+
+ bool parenObj = (obj->type == kBinaryOpNode);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, false, sum); // we want the object to always be verbose
+ if (parenObj) {
+ code.write(")");
+ }
+}
+
+/* StringChunkCountExprNode */
+
+void StringChunkCountExprNode::writeScriptText(CodeWriter &code, bool, bool sum) const {
+ code.write("the number of ");
+ code.write(StandardNames::getName(StandardNames::chunkTypeNames, type)); // we want the object to always be verbose
+ code.write("s in ");
+
+ bool parenObj = (obj->type == kBinaryOpNode);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, false, sum);
+ if (parenObj) {
+ code.write(")");
+ }
+}
+
+/* MenuPropExprNode */
+
+void MenuPropExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("the ");
+ code.write(StandardNames::getName(StandardNames::menuPropertyNames, prop));
+ code.write(" of menu ");
+
+ bool parenMenuID = (menuID->type == kBinaryOpNode);
+ if (parenMenuID) {
+ code.write("(");
+ }
+ menuID->writeScriptText(code, dot, sum);
+ if (parenMenuID) {
+ code.write(")");
+ }
+}
+
+/* MenuItemPropExprNode */
+
+void MenuItemPropExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("the ");
+ code.write(StandardNames::getName(StandardNames::menuItemPropertyNames, prop));
+ code.write(" of menuItem ");
+
+ bool parenItemID = (itemID->type == kBinaryOpNode);
+ if (parenItemID) {
+ code.write("(");
+ }
+ itemID->writeScriptText(code, dot, sum);
+ if (parenItemID) {
+ code.write(")");
+ }
+
+ code.write(" of menu ");
+
+ bool parenMenuID = (menuID->type == kBinaryOpNode);
+ if (parenMenuID) {
+ code.write("(");
+ }
+ menuID->writeScriptText(code, dot, sum);
+ if (parenMenuID) {
+ code.write(")");
+ }
+}
+
+/* SoundPropExprNode */
+
+void SoundPropExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("the ");
+ code.write(StandardNames::getName(StandardNames::soundPropertyNames, prop));
+ code.write(" of sound ");
+
+ bool parenSoundID = (soundID->type == kBinaryOpNode);
+ if (parenSoundID) {
+ code.write("(");
+ }
+ soundID->writeScriptText(code, dot, sum);
+ if (parenSoundID) {
+ code.write(")");
+ }
+}
+
+/* SpritePropExprNode */
+
+void SpritePropExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("the ");
+ code.write(StandardNames::getName(StandardNames::spritePropertyNames, prop));
+ code.write(" of sprite ");
+
+ bool parenSpriteID = (spriteID->type == kBinaryOpNode);
+ if (parenSpriteID) {
+ code.write("(");
+ }
+ spriteID->writeScriptText(code, dot, sum);
+ if (parenSpriteID) {
+ code.write(")");
+ }
+}
+
+/* ThePropExprNode */
+
+void ThePropExprNode::writeScriptText(CodeWriter &code, bool, bool sum) const {
+ code.write("the ");
+ code.write(prop);
+ code.write(" of ");
+
+ bool parenObj = (obj->type == kBinaryOpNode);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, false, sum); // we want the object to always be verbose
+ if (parenObj) {
+ code.write(")");
+ }
+}
+
+/* ObjPropExprNode */
+
+void ObjPropExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ if (dot) {
+ bool parenObj = obj->hasSpaces(dot);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, dot, sum);
+ if (parenObj) {
+ code.write(")");
+ }
+
+ code.write(".");
+ code.write(prop);
+ } else {
+ code.write("the ");
+ code.write(prop);
+ code.write(" of ");
+
+ bool parenObj = (obj->type == kBinaryOpNode);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, dot, sum);
+ if (parenObj) {
+ code.write(")");
+ }
+ }
+}
+
+bool ObjPropExprNode::hasSpaces(bool dot) {
+ return !dot;
+}
+
+/* ObjBracketExprNode */
+
+void ObjBracketExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ bool parenObj = obj->hasSpaces(dot);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, dot, sum);
+ if (parenObj) {
+ code.write(")");
+ }
+
+ code.write("[");
+ prop->writeScriptText(code, dot, sum);
+ code.write("]");
+}
+
+bool ObjBracketExprNode::hasSpaces(bool) {
+ return false;
+}
+
+/* ObjPropIndexExprNode */
+
+void ObjPropIndexExprNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ bool parenObj = obj->hasSpaces(dot);
+ if (parenObj) {
+ code.write("(");
+ }
+ obj->writeScriptText(code, dot, sum);
+ if (parenObj) {
+ code.write(")");
+ }
+
+ code.write(".");
+ code.write(prop);
+ code.write("[");
+ index->writeScriptText(code, dot, sum);
+ if (index2) {
+ code.write("..");
+ index2->writeScriptText(code, dot, sum);
+ }
+ code.write("]");
+}
+
+bool ObjPropIndexExprNode::hasSpaces(bool) {
+ return false;
+}
+
+/* ExitRepeatStmtNode */
+
+void ExitRepeatStmtNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("exit repeat");
+}
+
+/* NextRepeatStmtNode */
+
+void NextRepeatStmtNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("next repeat");
+}
+
+/* PutStmtNode */
+
+void PutStmtNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("put ");
+ value->writeScriptText(code, dot, sum);
+ code.write(" ");
+ code.write(StandardNames::getName(StandardNames::putTypeNames, type));
+ code.write(" ");
+ variable->writeScriptText(code, false, sum); // we want the variable to always be verbose
+}
+
+/* WhenStmtNode */
+
+void WhenStmtNode::writeScriptText(CodeWriter &code, bool, bool) const {
+ code.write("when ");
+ code.write(StandardNames::getName(StandardNames::whenEventNames, event));
+ code.write(" then");
+
+ code.doIndentation = false;
+ for (size_t i = 0; i < script.size(); i++) {
+ char ch = script[i];
+ if (ch == '\r') {
+ if (i != script.size() - 1) {
+ code.writeLine();
+ }
+ } else {
+ code.write(ch);
+ }
+ }
+ code.doIndentation = true;
+}
+
+/* NewObjNode */
+
+void NewObjNode::writeScriptText(CodeWriter &code, bool dot, bool sum) const {
+ code.write("new ");
+ code.write(objType);
+ code.write("(");
+ objArgs->writeScriptText(code, dot, sum);
+ code.write(")");
+}
+
+} // namespace LingoDec
diff --git a/engines/director/lingo/lingodec/ast.h b/engines/director/lingo/lingodec/ast.h
new file mode 100644
index 00000000000..391b0427fb1
--- /dev/null
+++ b/engines/director/lingo/lingodec/ast.h
@@ -0,0 +1,860 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_AST_H
+#define LINGODEC_AST_H
+
+#include "common/array.h"
+#include "common/ptr.h"
+#include "common/str.h"
+#include "common/util.h"
+#include "./codewriter.h"
+#include "./enums.h"
+
+namespace LingoDec {
+
+struct CaseLabelNode;
+struct Handler;
+struct LoopNode;
+struct Node;
+struct RepeatWithInStmtNode;
+
+/* Datum */
+
+struct Datum {
+ DatumType type;
+ int i;
+ double f;
+ Common::String s;
+ Common::Array<Common::SharedPtr<Node>> l;
+
+ Datum() {
+ type = kDatumVoid;
+ }
+ Datum(int val) {
+ type = kDatumInt;
+ i = val;
+ }
+ Datum(double val) {
+ type = kDatumFloat;
+ f = val;
+ }
+ Datum(DatumType t, Common::String val) {
+ type = t;
+ s = val;
+ }
+ Datum(DatumType t, Common::Array<Common::SharedPtr<Node>> val) {
+ type = t;
+ l = val;
+ }
+
+ int toInt();
+ void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* Node */
+
+struct Node {
+ NodeType type;
+ bool isExpression;
+ bool isStatement;
+ bool isLabel;
+ bool isLoop;
+ Node *parent;
+
+ Node(NodeType t) : type(t), isExpression(false), isStatement(false), isLabel(false), isLoop(false), parent(nullptr) {}
+ virtual ~Node() = default;
+ virtual void writeScriptText(CodeWriter&, bool, bool) const {}
+ virtual Common::SharedPtr<Datum> getValue();
+ Node *ancestorStatement();
+ LoopNode *ancestorLoop();
+ virtual bool hasSpaces(bool dot);
+};
+
+/* ExprNode */
+
+struct ExprNode : Node {
+ ExprNode(NodeType t) : Node(t) {
+ isExpression = true;
+ }
+ virtual ~ExprNode() = default;
+};
+
+/* StmtNode */
+
+struct StmtNode : Node {
+ StmtNode(NodeType t) : Node(t) {
+ isStatement = true;
+ }
+ virtual ~StmtNode() = default;
+};
+
+/* LabelNode */
+
+struct LabelNode : Node {
+ LabelNode(NodeType t) : Node(t) {
+ isLabel = true;
+ }
+ virtual ~LabelNode() = default;
+};
+
+/* LoopNode */
+
+struct LoopNode : StmtNode {
+ uint32_t startIndex;
+
+ LoopNode(NodeType t, uint32_t startIndex) : StmtNode(t), startIndex(startIndex) {
+ isLoop = true;
+ }
+ virtual ~LoopNode() = default;
+};
+
+/* ErrorNode */
+
+struct ErrorNode : ExprNode {
+ ErrorNode() : ExprNode(kErrorNode) {}
+ virtual ~ErrorNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* CommentNode */
+
+struct CommentNode : Node {
+ Common::String text;
+
+ CommentNode(Common::String t) : Node(kCommentNode), text(t) {}
+ virtual ~CommentNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* LiteralNode */
+
+struct LiteralNode : ExprNode {
+ Common::SharedPtr<Datum> value;
+
+ LiteralNode(Common::SharedPtr<Datum> d) : ExprNode(kLiteralNode) {
+ value = Common::move(d);
+ }
+ virtual ~LiteralNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual Common::SharedPtr<Datum> getValue();
+ virtual bool hasSpaces(bool dot);
+};
+
+/* BlockNode */
+
+struct BlockNode : Node {
+ Common::Array<Common::SharedPtr<Node>> children;
+
+ // for use during translation:
+ uint32_t endPos;
+ CaseLabelNode *currentCaseLabel;
+
+ BlockNode() : Node(kBlockNode), endPos(-1), currentCaseLabel(nullptr) {}
+ virtual ~BlockNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ void addChild(Common::SharedPtr<Node> child);
+};
+
+/* HandlerNode */
+
+struct HandlerNode : Node {
+ Handler *handler;
+ Common::SharedPtr<BlockNode> block;
+
+ HandlerNode(Handler *h)
+ : Node(kHandlerNode), handler(h) {
+ block = Common::SharedPtr<BlockNode>(new BlockNode());
+ block->parent = this;
+ }
+ virtual ~HandlerNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* ExitStmtNode */
+
+struct ExitStmtNode : StmtNode {
+ ExitStmtNode() : StmtNode(kExitStmtNode) {}
+ virtual ~ExitStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* InverseOpNode */
+
+struct InverseOpNode : ExprNode {
+ Common::SharedPtr<Node> operand;
+
+ InverseOpNode(Common::SharedPtr<Node> o) : ExprNode(kInverseOpNode) {
+ operand = Common::move(o);
+ operand->parent = this;
+ }
+ virtual ~InverseOpNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* NotOpNode */
+
+struct NotOpNode : ExprNode {
+ Common::SharedPtr<Node> operand;
+
+ NotOpNode(Common::SharedPtr<Node> o) : ExprNode(kNotOpNode) {
+ operand = Common::move(o);
+ operand->parent = this;
+ }
+ virtual ~NotOpNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* BinaryOpNode */
+
+struct BinaryOpNode : ExprNode {
+ OpCode opcode;
+ Common::SharedPtr<Node> left;
+ Common::SharedPtr<Node> right;
+
+ BinaryOpNode(OpCode op, Common::SharedPtr<Node> a, Common::SharedPtr<Node> b)
+ : ExprNode(kBinaryOpNode), opcode(op) {
+ left = Common::move(a);
+ left->parent = this;
+ right = Common::move(b);
+ right->parent = this;
+ }
+ virtual ~BinaryOpNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual unsigned int getPrecedence() const;
+};
+
+/* ChunkExprNode */
+
+struct ChunkExprNode : ExprNode {
+ ChunkExprType type;
+ Common::SharedPtr<Node> first;
+ Common::SharedPtr<Node> last;
+ Common::SharedPtr<Node> string;
+
+ ChunkExprNode(ChunkExprType t, Common::SharedPtr<Node> a, Common::SharedPtr<Node> b, Common::SharedPtr<Node> s)
+ : ExprNode(kChunkExprNode), type(t) {
+ first = Common::move(a);
+ first->parent = this;
+ last = Common::move(b);
+ last->parent = this;
+ string = Common::move(s);
+ string->parent = this;
+ }
+ virtual ~ChunkExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* ChunkHiliteStmtNode */
+
+struct ChunkHiliteStmtNode : StmtNode {
+ Common::SharedPtr<Node> chunk;
+
+ ChunkHiliteStmtNode(Common::SharedPtr<Node> c) : StmtNode(kChunkHiliteStmtNode) {
+ chunk = Common::move(c);
+ chunk->parent = this;
+ }
+ virtual ~ChunkHiliteStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* ChunkDeleteStmtNode */
+
+struct ChunkDeleteStmtNode : StmtNode {
+ Common::SharedPtr<Node> chunk;
+
+ ChunkDeleteStmtNode(Common::SharedPtr<Node> c) : StmtNode(kChunkDeleteStmtNode) {
+ chunk = Common::move(c);
+ chunk->parent = this;
+ }
+ virtual ~ChunkDeleteStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* SpriteIntersectsExprNode */
+
+struct SpriteIntersectsExprNode : ExprNode {
+ Common::SharedPtr<Node> firstSprite;
+ Common::SharedPtr<Node> secondSprite;
+
+ SpriteIntersectsExprNode(Common::SharedPtr<Node> a, Common::SharedPtr<Node> b)
+ : ExprNode(kSpriteIntersectsExprNode) {
+ firstSprite = Common::move(a);
+ firstSprite->parent = this;
+ secondSprite = Common::move(b);
+ secondSprite->parent = this;
+ }
+ virtual ~SpriteIntersectsExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* SpriteWithinExprNode */
+
+struct SpriteWithinExprNode : ExprNode {
+ Common::SharedPtr<Node> firstSprite;
+ Common::SharedPtr<Node> secondSprite;
+
+ SpriteWithinExprNode(Common::SharedPtr<Node> a, Common::SharedPtr<Node> b)
+ : ExprNode(kSpriteWithinExprNode) {
+ firstSprite = Common::move(a);
+ firstSprite->parent = this;
+ secondSprite = Common::move(b);
+ secondSprite->parent = this;
+ }
+ virtual ~SpriteWithinExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* MemberExprNode */
+
+struct MemberExprNode : ExprNode {
+ Common::String type;
+ Common::SharedPtr<Node> memberID;
+ Common::SharedPtr<Node> castID;
+
+ MemberExprNode(Common::String type, Common::SharedPtr<Node> memberID, Common::SharedPtr<Node> castID)
+ : ExprNode(kMemberExprNode), type(type) {
+ this->memberID = Common::move(memberID);
+ this->memberID->parent = this;
+ if (castID) {
+ this->castID = Common::move(castID);
+ this->castID->parent = this;
+ }
+ }
+ virtual ~MemberExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* VarNode */
+
+struct VarNode : ExprNode {
+ Common::String varName;
+
+ VarNode(Common::String v) : ExprNode(kVarNode), varName(v) {}
+ virtual ~VarNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* AssignmentStmtNode */
+
+struct AssignmentStmtNode : StmtNode {
+ Common::SharedPtr<Node> variable;
+ Common::SharedPtr<Node> value;
+ bool forceVerbose;
+
+ AssignmentStmtNode(Common::SharedPtr<Node> var, Common::SharedPtr<Node> val, bool forceVerbose = false)
+ : StmtNode(kAssignmentStmtNode), forceVerbose(forceVerbose) {
+ variable = Common::move(var);
+ variable->parent = this;
+ value = Common::move(val);
+ value->parent = this;
+ }
+
+ virtual ~AssignmentStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* IfStmtNode */
+
+struct IfStmtNode : StmtNode {
+ bool hasElse;
+ Common::SharedPtr<Node> condition;
+ Common::SharedPtr<BlockNode> block1;
+ Common::SharedPtr<BlockNode> block2;
+
+ IfStmtNode(Common::SharedPtr<Node> c) : StmtNode(kIfStmtNode), hasElse(false) {
+ condition = Common::move(c);
+ condition->parent = this;
+ block1 = Common::SharedPtr<BlockNode>(new BlockNode());
+ block1->parent = this;
+ block2 = Common::SharedPtr<BlockNode>(new BlockNode());
+ block2->parent = this;
+ }
+ virtual ~IfStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* RepeatWhileStmtNode */
+
+struct RepeatWhileStmtNode : LoopNode {
+ Common::SharedPtr<Node> condition;
+ Common::SharedPtr<BlockNode> block;
+
+ RepeatWhileStmtNode(uint32_t startIndex, Common::SharedPtr<Node> c)
+ : LoopNode(kRepeatWhileStmtNode, startIndex) {
+ condition = Common::move(c);
+ condition->parent = this;
+ block = Common::SharedPtr<BlockNode>(new BlockNode());
+ block->parent = this;
+ }
+ virtual ~RepeatWhileStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* RepeatWithInStmtNode */
+
+struct RepeatWithInStmtNode : LoopNode {
+ Common::String varName;
+ Common::SharedPtr<Node> list;
+ Common::SharedPtr<BlockNode> block;
+
+ RepeatWithInStmtNode(uint32_t startIndex, Common::String v, Common::SharedPtr<Node> l)
+ : LoopNode(kRepeatWithInStmtNode, startIndex) {
+ varName = v;
+ list = Common::move(l);
+ list->parent = this;
+ block = Common::SharedPtr<BlockNode>(new BlockNode());
+ block->parent = this;
+ }
+ virtual ~RepeatWithInStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* RepeatWithToStmtNode */
+
+struct RepeatWithToStmtNode : LoopNode {
+ Common::String varName;
+ Common::SharedPtr<Node> start;
+ bool up;
+ Common::SharedPtr<Node> end;
+ Common::SharedPtr<BlockNode> block;
+
+ RepeatWithToStmtNode(uint32_t startIndex, Common::String v, Common::SharedPtr<Node> s, bool up, Common::SharedPtr<Node> e)
+ : LoopNode(kRepeatWithToStmtNode, startIndex), up(up) {
+ varName = v;
+ start = Common::move(s);
+ start->parent = this;
+ end = Common::move(e);
+ end->parent = this;
+ block = Common::SharedPtr<BlockNode>(new BlockNode());
+ block->parent = this;
+ }
+ virtual ~RepeatWithToStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* CaseLabelNode */
+
+struct CaseLabelNode : LabelNode {
+ Common::SharedPtr<Node> value;
+ CaseExpect expect;
+
+ Common::SharedPtr<CaseLabelNode> nextOr;
+
+ Common::SharedPtr<CaseLabelNode> nextLabel;
+ Common::SharedPtr<BlockNode> block;
+
+ CaseLabelNode(Common::SharedPtr<Node> v, CaseExpect e) : LabelNode(kCaseLabelNode), expect(e) {
+ value = Common::move(v);
+ value->parent = this;
+ }
+ virtual ~CaseLabelNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* OtherwiseNode */
+
+struct OtherwiseNode : LabelNode {
+ Common::SharedPtr<BlockNode> block;
+
+ OtherwiseNode() : LabelNode(kOtherwiseNode) {
+ block = Common::SharedPtr<BlockNode>(new BlockNode());
+ block->parent = this;
+ }
+ virtual ~OtherwiseNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* EndCaseNode */
+
+struct EndCaseNode : LabelNode {
+ EndCaseNode() : LabelNode(kEndCaseNode) {}
+ virtual ~EndCaseNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* CaseStmtNode */
+
+struct CaseStmtNode : StmtNode {
+ Common::SharedPtr<Node> value;
+ Common::SharedPtr<CaseLabelNode> firstLabel;
+ Common::SharedPtr<OtherwiseNode> otherwise;
+
+ // for use during translation:
+ int32_t endPos = -1;
+ int32_t potentialOtherwisePos = -1;
+
+ CaseStmtNode(Common::SharedPtr<Node> v) : StmtNode(kCaseStmtNode) {
+ value = Common::move(v);
+ value->parent = this;
+ }
+ virtual ~CaseStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ void addOtherwise();
+};
+
+/* TellStmtNode */
+
+struct TellStmtNode : StmtNode {
+ Common::SharedPtr<Node> window;
+ Common::SharedPtr<BlockNode> block;
+
+ TellStmtNode(Common::SharedPtr<Node> w) : StmtNode(kTellStmtNode) {
+ window = Common::move(w);
+ window->parent = this;
+ block = Common::SharedPtr<BlockNode>(new BlockNode());
+ block->parent = this;
+ }
+ virtual ~TellStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* SoundCmdStmtNode */
+
+struct SoundCmdStmtNode : StmtNode {
+ Common::String cmd;
+ Common::SharedPtr<Node> argList;
+
+ SoundCmdStmtNode(Common::String c, Common::SharedPtr<Node> a) : StmtNode(kSoundCmdStmtNode) {
+ cmd = c;
+ argList = Common::move(a);
+ argList->parent = this;
+ }
+ virtual ~SoundCmdStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* PlayCmdStmtNode */
+
+struct PlayCmdStmtNode : StmtNode {
+ Common::SharedPtr<Node> argList;
+
+ PlayCmdStmtNode(Common::SharedPtr<Node> a) : StmtNode(kPlayCmdStmtNode) {
+ argList = Common::move(a);
+ argList->parent = this;
+ }
+ virtual ~PlayCmdStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* CallNode */
+
+struct CallNode : Node {
+ Common::String name;
+ Common::SharedPtr<Node> argList;
+
+ CallNode(Common::String n, Common::SharedPtr<Node> a) : Node(kCallNode) {
+ name = n;
+ argList = Common::move(a);
+ argList->parent = this;
+ if (argList->getValue()->type == kDatumArgListNoRet)
+ isStatement = true;
+ else
+ isExpression = true;
+ }
+ virtual ~CallNode() = default;
+ bool noParens() const;
+ bool isMemberExpr() const;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* ObjCallNode */
+
+struct ObjCallNode : Node {
+ Common::String name;
+ Common::SharedPtr<Node> argList;
+
+ ObjCallNode(Common::String n, Common::SharedPtr<Node> a) : Node(kObjCallNode) {
+ name = n;
+ argList = Common::move(a);
+ argList->parent = this;
+ if (argList->getValue()->type == kDatumArgListNoRet)
+ isStatement = true;
+ else
+ isExpression = true;
+ }
+ virtual ~ObjCallNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* ObjCallV4Node */
+
+struct ObjCallV4Node : Node {
+ Common::SharedPtr<Node> obj;
+ Common::SharedPtr<Node> argList;
+
+ ObjCallV4Node(Common::SharedPtr<Node> o, Common::SharedPtr<Node> a) : Node(kObjCallV4Node) {
+ obj = o;
+ argList = Common::move(a);
+ argList->parent = this;
+ if (argList->getValue()->type == kDatumArgListNoRet)
+ isStatement = true;
+ else
+ isExpression = true;
+ }
+ virtual ~ObjCallV4Node() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* TheExprNode */
+
+struct TheExprNode : ExprNode {
+ Common::String prop;
+
+ TheExprNode(Common::String p) : ExprNode(kTheExprNode), prop(p) {}
+ virtual ~TheExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* LastStringChunkExprNode */
+
+struct LastStringChunkExprNode : ExprNode {
+ ChunkExprType type;
+ Common::SharedPtr<Node> obj;
+
+ LastStringChunkExprNode(ChunkExprType t, Common::SharedPtr<Node> o)
+ : ExprNode(kLastStringChunkExprNode), type(t) {
+ obj = Common::move(o);
+ obj->parent = this;
+ }
+ virtual ~LastStringChunkExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* StringChunkCountExprNode */
+
+struct StringChunkCountExprNode : ExprNode {
+ ChunkExprType type;
+ Common::SharedPtr<Node> obj;
+
+ StringChunkCountExprNode(ChunkExprType t, Common::SharedPtr<Node> o)
+ : ExprNode(kStringChunkCountExprNode), type(t) {
+ obj = Common::move(o);
+ obj->parent = this;
+ }
+ virtual ~StringChunkCountExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* MenuPropExprNode */
+
+struct MenuPropExprNode : ExprNode {
+ Common::SharedPtr<Node> menuID;
+ unsigned int prop;
+
+ MenuPropExprNode(Common::SharedPtr<Node> m, unsigned int p)
+ : ExprNode(kMenuPropExprNode), prop(p) {
+ menuID = Common::move(m);
+ menuID->parent = this;
+ }
+ virtual ~MenuPropExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* MenuItemPropExprNode */
+
+struct MenuItemPropExprNode : ExprNode {
+ Common::SharedPtr<Node> menuID;
+ Common::SharedPtr<Node> itemID;
+ unsigned int prop;
+
+ MenuItemPropExprNode(Common::SharedPtr<Node> m, Common::SharedPtr<Node> i, unsigned int p)
+ : ExprNode(kMenuItemPropExprNode), prop(p) {
+ menuID = Common::move(m);
+ menuID->parent = this;
+ itemID = Common::move(i);
+ itemID->parent = this;
+ }
+ virtual ~MenuItemPropExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* SoundPropExprNode */
+
+struct SoundPropExprNode : ExprNode {
+ Common::SharedPtr<Node> soundID;
+ unsigned int prop;
+
+ SoundPropExprNode(Common::SharedPtr<Node> s, unsigned int p)
+ : ExprNode(kSoundPropExprNode), prop(p) {
+ soundID = Common::move(s);
+ soundID->parent = this;
+ }
+ virtual ~SoundPropExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* SpritePropExprNode */
+
+struct SpritePropExprNode : ExprNode {
+ Common::SharedPtr<Node> spriteID;
+ unsigned int prop;
+
+ SpritePropExprNode(Common::SharedPtr<Node> s, unsigned int p)
+ : ExprNode(kSpritePropExprNode), prop(p) {
+ spriteID = Common::move(s);
+ spriteID->parent = this;
+ }
+ virtual ~SpritePropExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* ThePropExprNode */
+
+struct ThePropExprNode : ExprNode {
+ Common::SharedPtr<Node> obj;
+ Common::String prop;
+
+ ThePropExprNode(Common::SharedPtr<Node> o, Common::String p)
+ : ExprNode(kThePropExprNode), prop(p) {
+ obj = Common::move(o);
+ obj->parent = this;
+ }
+ virtual ~ThePropExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* ObjPropExprNode */
+
+struct ObjPropExprNode : ExprNode {
+ Common::SharedPtr<Node> obj;
+ Common::String prop;
+
+ ObjPropExprNode(Common::SharedPtr<Node> o, Common::String p)
+ : ExprNode(kObjPropExprNode), prop(p) {
+ obj = Common::move(o);
+ obj->parent = this;
+ }
+ virtual ~ObjPropExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* ObjBracketExprNode */
+
+struct ObjBracketExprNode : ExprNode {
+ Common::SharedPtr<Node> obj;
+ Common::SharedPtr<Node> prop;
+
+ ObjBracketExprNode(Common::SharedPtr<Node> o, Common::SharedPtr<Node> p)
+ : ExprNode(kObjBracketExprNode) {
+ obj = Common::move(o);
+ obj->parent = this;
+ prop = Common::move(p);
+ prop->parent = this;
+ }
+ virtual ~ObjBracketExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* ObjPropIndexExprNode */
+
+struct ObjPropIndexExprNode : ExprNode {
+ Common::SharedPtr<Node> obj;
+ Common::String prop;
+ Common::SharedPtr<Node> index;
+ Common::SharedPtr<Node> index2;
+
+ ObjPropIndexExprNode(Common::SharedPtr<Node> o, Common::String p, Common::SharedPtr<Node> i, Common::SharedPtr<Node> i2)
+ : ExprNode(kObjPropIndexExprNode), prop(p) {
+ obj = Common::move(o);
+ obj->parent = this;
+ index = Common::move(i);
+ index->parent = this;
+ if (i2) {
+ index2 = Common::move(i2);
+ index2->parent = this;
+ }
+ }
+ virtual ~ObjPropIndexExprNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ virtual bool hasSpaces(bool dot);
+};
+
+/* ExitRepeatStmtNode */
+
+struct ExitRepeatStmtNode : StmtNode {
+ ExitRepeatStmtNode() : StmtNode(kExitRepeatStmtNode) {}
+ virtual ~ExitRepeatStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* NextRepeatStmtNode */
+
+struct NextRepeatStmtNode : StmtNode {
+ NextRepeatStmtNode() : StmtNode(kNextRepeatStmtNode) {}
+ virtual ~NextRepeatStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* PutStmtNode */
+
+struct PutStmtNode : StmtNode {
+ PutType type;
+ Common::SharedPtr<Node> variable;
+ Common::SharedPtr<Node> value;
+
+ PutStmtNode(PutType t, Common::SharedPtr<Node> var, Common::SharedPtr<Node> val)
+ : StmtNode(kPutStmtNode), type(t) {
+ variable = Common::move(var);
+ variable->parent = this;
+ value = Common::move(val);
+ value->parent = this;
+ }
+ virtual ~PutStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* WhenStmtNode */
+
+struct WhenStmtNode : StmtNode {
+ int event;
+ Common::String script;
+
+ WhenStmtNode(int e, Common::String s)
+ : StmtNode(kWhenStmtNode), event(e), script(s) {}
+ virtual ~WhenStmtNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* NewObjNode */
+
+struct NewObjNode : ExprNode {
+ Common::String objType;
+ Common::SharedPtr<Node> objArgs;
+
+ NewObjNode(Common::String o, Common::SharedPtr<Node> args) : ExprNode(kNewObjNode), objType(o), objArgs(args) {}
+ virtual ~NewObjNode() = default;
+ virtual void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+};
+
+/* AST */
+
+struct AST {
+ Common::SharedPtr<HandlerNode> root;
+ BlockNode *currentBlock;
+
+ AST(Handler *handler){
+ root = Common::SharedPtr<HandlerNode>(new HandlerNode(handler));
+ currentBlock = root->block.get();
+ }
+
+ void writeScriptText(CodeWriter &code, bool dot, bool sum) const;
+ void addStatement(Common::SharedPtr<Node> statement);
+ void enterBlock(BlockNode *block);
+ void exitBlock();
+};
+
+} // namespace LingoDec
+
+#endif // LINGODEC_AST_H
diff --git a/engines/director/lingo/lingodec/codewriter.cpp b/engines/director/lingo/lingodec/codewriter.cpp
new file mode 100644
index 00000000000..fa20eae8883
--- /dev/null
+++ b/engines/director/lingo/lingodec/codewriter.cpp
@@ -0,0 +1,75 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include "./codewriter.h"
+
+namespace LingoDec {
+
+void CodeWriter::write(Common::String str) {
+ if (str.empty())
+ return;
+
+ writeIndentation();
+ _res += str;
+ _lineWidth += str.size();
+ _size += str.size();
+}
+
+void CodeWriter::write(char ch) {
+ writeIndentation();
+ _res += ch;
+ _lineWidth += 1;
+ _size += 1;
+}
+
+void CodeWriter::writeLine(Common::String str) {
+ if (str.empty()) {
+ _res += _lineEnding;
+ } else {
+ writeIndentation();
+ _res += str;
+ _res += _lineEnding;
+ }
+ _indentationWritten = false;
+ _lineWidth = 0;
+ _size += str.size() + _lineEnding.size();
+}
+
+void CodeWriter::writeLine() {
+ _res += _lineEnding;
+ _indentationWritten = false;
+ _lineWidth = 0;
+ _size += _lineEnding.size();
+}
+
+void CodeWriter::indent() {
+ _indentationLevel += 1;
+}
+
+void CodeWriter::unindent() {
+ if (_indentationLevel > 0) {
+ _indentationLevel -= 1;
+ }
+}
+
+Common::String CodeWriter::str() const {
+ return _res;
+}
+
+void CodeWriter::writeIndentation() {
+ if (_indentationWritten || !doIndentation)
+ return;
+
+ for (int i = 0; i < _indentationLevel; i++) {
+ _res += _indentation;
+ }
+
+ _indentationWritten = true;
+ _lineWidth = _indentationLevel * _indentation.size();
+ _size += _lineWidth;
+}
+
+} // namespace Common
diff --git a/engines/director/lingo/lingodec/codewriter.h b/engines/director/lingo/lingodec/codewriter.h
new file mode 100644
index 00000000000..81e0f8d7209
--- /dev/null
+++ b/engines/director/lingo/lingodec/codewriter.h
@@ -0,0 +1,51 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_CODEWRITER_H
+#define LINGODEC_CODEWRITER_H
+
+#include "common/str.h"
+
+namespace LingoDec {
+
+class CodeWriter {
+protected:
+ Common::String _res;
+
+ Common::String _lineEnding;
+ Common::String _indentation;
+
+ int _indentationLevel = 0;
+ bool _indentationWritten = false;
+ size_t _lineWidth = 0;
+ size_t _size = 0;
+
+public:
+ bool doIndentation = true;
+
+public:
+ CodeWriter(Common::String lineEnding, Common::String indentation = " ")
+ : _lineEnding(lineEnding), _indentation(indentation) {}
+
+ void write(Common::String str);
+ void write(char ch);
+ void writeLine(Common::String str);
+ void writeLine();
+
+ void indent();
+ void unindent();
+
+ Common::String str() const;
+ size_t lineWidth() const { return _lineWidth; }
+ size_t size() const { return _size; }
+
+protected:
+ void writeIndentation();
+};
+
+} // namespace Common
+
+#endif // LINGODEC_CODEWRITER_H
diff --git a/engines/director/lingo/lingodec/context.cpp b/engines/director/lingo/lingodec/context.cpp
new file mode 100644
index 00000000000..e2307bbb6a0
--- /dev/null
+++ b/engines/director/lingo/lingodec/context.cpp
@@ -0,0 +1,84 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include "common/stream.h"
+#include "common/util.h"
+#include "./context.h"
+#include "./names.h"
+#include "./resolver.h"
+#include "./script.h"
+
+namespace LingoDec {
+
+struct ScriptContextMapEntry;
+
+/* ScriptContext */
+
+void ScriptContext::read(Common::SeekableReadStream &stream) {
+ // Lingo scripts are always big endian regardless of file endianness
+ unknown0 = stream.readSint32BE();
+ unknown1 = stream.readSint32BE();
+ entryCount = stream.readUint32BE();
+ entryCount2 = stream.readUint32BE();
+ entriesOffset = stream.readUint16BE();
+ unknown2 = stream.readSint16BE();
+ unknown3 = stream.readSint32BE();
+ unknown4 = stream.readSint32BE();
+ unknown5 = stream.readSint32BE();
+ lnamSectionID = stream.readSint32BE();
+ validCount = stream.readUint16BE();
+ flags = stream.readUint16BE();
+ freePointer = stream.readSint16BE();
+
+ stream.seek(entriesOffset);
+ sectionMap.resize(entryCount);
+ for (auto &entry : sectionMap) {
+ entry.read(stream);
+ }
+
+ lnam = resolver->getScriptNames(lnamSectionID);
+ for (uint32_t i = 1; i <= entryCount; i++) {
+ auto section = sectionMap[i - 1];
+ if (section.sectionID > -1) {
+ Script *script = resolver->getScript(section.sectionID);
+ script->setContext(this);
+ scripts[i] = script;
+ }
+ }
+
+ for (auto it = scripts.begin(); it != scripts.end(); ++it) {
+ Script *script = it->second;
+ if (script->isFactory()) {
+ Script *parent = scripts[script->parentNumber + 1];
+ parent->factories.push_back(script);
+ }
+ }
+}
+
+bool ScriptContext::validName(int id) const {
+ return lnam->validName(id);
+}
+
+Common::String ScriptContext::getName(int id) const {
+ return lnam->getName(id);
+}
+
+void ScriptContext::parseScripts() {
+ for (auto it = scripts.begin(); it != scripts.end(); ++it) {
+ it->second->parse();
+ }
+}
+
+/* ScriptContextMapEntry */
+
+void ScriptContextMapEntry::read(Common::SeekableReadStream &stream) {
+ unknown0 = stream.readSint32BE();
+ sectionID = stream.readSint32BE();
+ unknown1 = stream.readUint16BE();
+ unknown2 = stream.readUint16BE();
+}
+
+} // namespace LingoDec
diff --git a/engines/director/lingo/lingodec/context.h b/engines/director/lingo/lingodec/context.h
new file mode 100644
index 00000000000..a8846eb07b3
--- /dev/null
+++ b/engines/director/lingo/lingodec/context.h
@@ -0,0 +1,70 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_CONTEXT_H
+#define LINGODEC_CONTEXT_H
+
+#include "common/array.h"
+#include "common/stablemap.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace LingoDec {
+
+class ChunkResolver;
+struct Script;
+struct ScriptContextMapEntry;
+struct ScriptNames;
+
+/* ScriptContext */
+
+struct ScriptContext {
+ int32_t unknown0;
+ int32_t unknown1;
+ uint32_t entryCount;
+ uint32_t entryCount2;
+ uint16_t entriesOffset;
+ int16_t unknown2;
+ int32_t unknown3;
+ int32_t unknown4;
+ int32_t unknown5;
+ int32_t lnamSectionID;
+ uint16_t validCount;
+ uint16_t flags;
+ int16_t freePointer;
+
+ unsigned int version;
+ ChunkResolver *resolver;
+ ScriptNames *lnam;
+ Common::Array<ScriptContextMapEntry> sectionMap;
+ Common::StableMap<uint32_t, Script *> scripts;
+
+ ScriptContext(unsigned int version, ChunkResolver *resolver) : version(version),
+ resolver(resolver),
+ lnam(nullptr) {}
+
+ void read(Common::SeekableReadStream &stream);
+ bool validName(int id) const;
+ Common::String getName(int id) const;
+ void parseScripts();
+};
+
+/* ScriptContextMapEntry */
+
+struct ScriptContextMapEntry {
+ int32_t unknown0;
+ int32_t sectionID;
+ uint16_t unknown1;
+ uint16_t unknown2;
+
+ void read(Common::SeekableReadStream &stream);
+};
+
+} // namespace LingoDec
+
+#endif // LINGODEC_CONTEXT_H
diff --git a/engines/director/lingo/lingodec/enums.h b/engines/director/lingo/lingodec/enums.h
new file mode 100644
index 00000000000..3dd65fae1e4
--- /dev/null
+++ b/engines/director/lingo/lingodec/enums.h
@@ -0,0 +1,216 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_ENUMS_H
+#define LINGODEC_ENUMS_H
+
+namespace LingoDec {
+
+enum OpCode {
+ // single-byte
+ kOpRet = 0x01,
+ kOpRetFactory = 0x02,
+ kOpPushZero = 0x03,
+ kOpMul = 0x04,
+ kOpAdd = 0x05,
+ kOpSub = 0x06,
+ kOpDiv = 0x07,
+ kOpMod = 0x08,
+ kOpInv = 0x09,
+ kOpJoinStr = 0x0a,
+ kOpJoinPadStr = 0x0b,
+ kOpLt = 0x0c,
+ kOpLtEq = 0x0d,
+ kOpNtEq = 0x0e,
+ kOpEq = 0x0f,
+ kOpGt = 0x10,
+ kOpGtEq = 0x11,
+ kOpAnd = 0x12,
+ kOpOr = 0x13,
+ kOpNot = 0x14,
+ kOpContainsStr = 0x15,
+ kOpContains0Str = 0x16,
+ kOpGetChunk = 0x17,
+ kOpHiliteChunk = 0x18,
+ kOpOntoSpr = 0x19,
+ kOpIntoSpr = 0x1a,
+ kOpGetField = 0x1b,
+ kOpStartTell = 0x1c,
+ kOpEndTell = 0x1d,
+ kOpPushList = 0x1e,
+ kOpPushPropList = 0x1f,
+ kOpSwap = 0x21,
+
+ // multi-byte
+ kOpPushInt8 = 0x41,
+ kOpPushArgListNoRet = 0x42,
+ kOpPushArgList = 0x43,
+ kOpPushCons = 0x44,
+ kOpPushSymb = 0x45,
+ kOpPushVarRef = 0x46,
+ kOpGetGlobal2 = 0x48,
+ kOpGetGlobal = 0x49,
+ kOpGetProp = 0x4a,
+ kOpGetParam = 0x4b,
+ kOpGetLocal = 0x4c,
+ kOpSetGlobal2 = 0x4e,
+ kOpSetGlobal = 0x4f,
+ kOpSetProp = 0x50,
+ kOpSetParam = 0x51,
+ kOpSetLocal = 0x52,
+ kOpJmp = 0x53,
+ kOpEndRepeat = 0x54,
+ kOpJmpIfZ = 0x55,
+ kOpLocalCall = 0x56,
+ kOpExtCall = 0x57,
+ kOpObjCallV4 = 0x58,
+ kOpPut = 0x59,
+ kOpPutChunk = 0x5a,
+ kOpDeleteChunk = 0x5b,
+ kOpGet = 0x5c,
+ kOpSet = 0x5d,
+ kOpGetMovieProp = 0x5f,
+ kOpSetMovieProp = 0x60,
+ kOpGetObjProp = 0x61,
+ kOpSetObjProp = 0x62,
+ kOpTellCall = 0x63,
+ kOpPeek = 0x64,
+ kOpPop = 0x65,
+ kOpTheBuiltin = 0x66,
+ kOpObjCall = 0x67,
+ kOpPushChunkVarRef = 0x6d,
+ kOpPushInt16 = 0x6e,
+ kOpPushInt32 = 0x6f,
+ kOpGetChainedProp = 0x70,
+ kOpPushFloat32 = 0x71,
+ kOpGetTopLevelProp = 0x72,
+ kOpNewObj = 0x73
+};
+
+enum DatumType {
+ kDatumVoid,
+ kDatumSymbol,
+ kDatumVarRef,
+ kDatumString,
+ kDatumInt,
+ kDatumFloat,
+ kDatumList,
+ kDatumArgList,
+ kDatumArgListNoRet,
+ kDatumPropList
+};
+
+enum ChunkExprType {
+ kChunkChar = 0x01,
+ kChunkWord = 0x02,
+ kChunkItem = 0x03,
+ kChunkLine = 0x04
+};
+
+enum PutType {
+ kPutInto = 0x01,
+ kPutAfter = 0x02,
+ kPutBefore = 0x03
+};
+
+enum NodeType {
+ kNoneNode,
+ kErrorNode,
+ kTempNode,
+ kCommentNode,
+ kLiteralNode,
+ kBlockNode,
+ kHandlerNode,
+ kExitStmtNode,
+ kInverseOpNode,
+ kNotOpNode,
+ kBinaryOpNode,
+ kChunkExprNode,
+ kChunkHiliteStmtNode,
+ kChunkDeleteStmtNode,
+ kSpriteIntersectsExprNode,
+ kSpriteWithinExprNode,
+ kMemberExprNode,
+ kVarNode,
+ kAssignmentStmtNode,
+ kIfStmtNode,
+ kRepeatWhileStmtNode,
+ kRepeatWithInStmtNode,
+ kRepeatWithToStmtNode,
+ kCaseStmtNode,
+ kCaseLabelNode,
+ kOtherwiseNode,
+ kEndCaseNode,
+ kTellStmtNode,
+ kSoundCmdStmtNode,
+ kPlayCmdStmtNode,
+ kCallNode,
+ kObjCallNode,
+ kObjCallV4Node,
+ kTheExprNode,
+ kLastStringChunkExprNode,
+ kStringChunkCountExprNode,
+ kMenuPropExprNode,
+ kMenuItemPropExprNode,
+ kSoundPropExprNode,
+ kSpritePropExprNode,
+ kThePropExprNode,
+ kObjPropExprNode,
+ kObjBracketExprNode,
+ kObjPropIndexExprNode,
+ kExitRepeatStmtNode,
+ kNextRepeatStmtNode,
+ kPutStmtNode,
+ kWhenStmtNode,
+ kNewObjNode
+};
+
+enum BytecodeTag {
+ kTagNone,
+ kTagSkip,
+ kTagRepeatWhile,
+ kTagRepeatWithIn,
+ kTagRepeatWithTo,
+ kTagRepeatWithDownTo,
+ kTagNextRepeatTarget,
+ kTagEndCase
+};
+
+enum CaseExpect {
+ kCaseExpectEnd,
+ kCaseExpectOr,
+ kCaseExpectNext,
+ kCaseExpectOtherwise
+};
+
+enum ScriptFlag {
+ kScriptFlagUnused = (1 << 0x0),
+ kScriptFlagFuncsGlobal = (1 << 0x1),
+ kScriptFlagVarsGlobal = (1 << 0x2), // Occurs in event scripts (which have no local vars). Correlated with use of alternate global var opcodes.
+ kScriptFlagUnk3 = (1 << 0x3),
+ kScriptFlagFactoryDef = (1 << 0x4),
+ kScriptFlagUnk5 = (1 << 0x5),
+ kScriptFlagUnk6 = (1 << 0x6),
+ kScriptFlagUnk7 = (1 << 0x7),
+ kScriptFlagHasFactory = (1 << 0x8),
+ kScriptFlagEventScript = (1 << 0x9),
+ kScriptFlagEventScript2 = (1 << 0xa),
+ kScriptFlagUnkB = (1 << 0xb),
+ kScriptFlagUnkC = (1 << 0xc),
+ kScriptFlagUnkD = (1 << 0xd),
+ kScriptFlagUnkE = (1 << 0xe),
+ kScriptFlagUnkF = (1 << 0xf)
+};
+
+enum LiteralType {
+ kLiteralString = 1,
+ kLiteralInt = 4,
+ kLiteralFloat = 9
+};
+
+}
+
+#endif // LINGODEC_ENUMS_H
diff --git a/engines/director/lingo/lingodec/handler.cpp b/engines/director/lingo/lingodec/handler.cpp
new file mode 100644
index 00000000000..b4bfc1b85c3
--- /dev/null
+++ b/engines/director/lingo/lingodec/handler.cpp
@@ -0,0 +1,1305 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include "common/stream.h"
+#include "common/util.h"
+#include "./ast.h"
+#include "./codewriter.h"
+#include "./handler.h"
+#include "./names.h"
+#include "./script.h"
+
+namespace LingoDec {
+
+/* Handler */
+
+void Handler::readRecord(Common::SeekableReadStream &stream) {
+ nameID = stream.readSint16BE();
+ vectorPos = stream.readUint16BE();
+ compiledLen = stream.readUint32BE();
+ compiledOffset = stream.readUint32BE();
+ argumentCount = stream.readUint16BE();
+ argumentOffset = stream.readUint32BE();
+ localsCount = stream.readUint16BE();
+ localsOffset = stream.readUint32BE();
+ globalsCount = stream.readUint16BE();
+ globalsOffset = stream.readUint32BE();
+ unknown1 = stream.readUint32BE();
+ unknown2 = stream.readUint16BE();
+ lineCount = stream.readUint16BE();
+ lineOffset = stream.readUint32BE();
+ // yet to implement
+ if (script->version >= 850)
+ stackHeight = stream.readUint32BE();
+}
+
+void Handler::readData(Common::SeekableReadStream &stream) {
+ stream.seek(compiledOffset);
+ while (stream.pos() < compiledOffset + compiledLen) {
+ uint32_t pos = stream.pos() - compiledOffset;
+ uint8_t op = stream.readByte();
+ OpCode opcode = static_cast<OpCode>(op >= 0x40 ? 0x40 + op % 0x40 : op);
+ // argument can be one, two or four bytes
+ int32_t obj = 0;
+ if (op >= 0xc0) {
+ // four bytes
+ obj = stream.readSint32BE();
+ } else if (op >= 0x80) {
+ // two bytes
+ if (opcode == kOpPushInt16 || opcode == kOpPushInt8) {
+ // treat pushint's arg as signed
+ // pushint8 may be used to push a 16-bit int in older Lingo
+ obj = stream.readSint16BE();
+ } else {
+ obj = stream.readUint16BE();
+ }
+ } else if (op >= 0x40) {
+ // one byte
+ if (opcode == kOpPushInt8) {
+ // treat pushint's arg as signed
+ obj = stream.readSByte();
+ } else {
+ obj = stream.readByte();
+ }
+ }
+ Bytecode bytecode(op, obj, pos);
+ bytecodeArray.push_back(bytecode);
+ bytecodePosMap[pos] = bytecodeArray.size() - 1;
+ }
+
+ argumentNameIDs = readVarnamesTable(stream, argumentCount, argumentOffset);
+ localNameIDs = readVarnamesTable(stream, localsCount, localsOffset);
+ globalNameIDs = readVarnamesTable(stream, globalsCount, globalsOffset);
+}
+
+Common::Array<int16_t> Handler::readVarnamesTable(Common::SeekableReadStream &stream, uint16_t count, uint32_t offset) {
+ stream.seek(offset);
+ Common::Array<int16_t> nameIDs;
+ nameIDs.resize(count);
+ for (size_t i = 0; i < count; i++) {
+ nameIDs[i] = stream.readUint16BE();
+ }
+ return nameIDs;
+}
+
+void Handler::readNames() {
+ if (!isGenericEvent) {
+ name = getName(nameID);
+ }
+ for (size_t i = 0; i < argumentNameIDs.size(); i++) {
+ if (i == 0 && script->isFactory())
+ continue;
+ argumentNames.push_back(getName(argumentNameIDs[i]));
+ }
+ for (auto nameID_ : localNameIDs) {
+ if (validName(nameID_)) {
+ localNames.push_back(getName(nameID_));
+ }
+ }
+ for (auto nameID_ : globalNameIDs) {
+ if (validName(nameID_)) {
+ globalNames.push_back(getName(nameID_));
+ }
+ }
+}
+
+bool Handler::validName(int id) const {
+ return script->validName(id);
+}
+
+Common::String Handler::getName(int id) const {
+ return script->getName(id);
+}
+
+Common::String Handler::getArgumentName(int id) const {
+ if (-1 < id && (unsigned)id < argumentNameIDs.size())
+ return getName(argumentNameIDs[id]);
+ return Common::String::format("UNKNOWN_ARG_%d", id);
+}
+
+Common::String Handler::getLocalName(int id) const {
+ if (-1 < id && (unsigned)id < localNameIDs.size())
+ return getName(localNameIDs[id]);
+ return Common::String::format("UNKNOWN_LOCAL_%d", id);
+}
+
+Common::SharedPtr<Node> Handler::pop() {
+ if (stack.empty())
+ return Common::SharedPtr<Node>(new ErrorNode());
+
+ auto res = stack.back();
+ stack.pop_back();
+ return res;
+}
+
+int Handler::variableMultiplier() {
+ if (script->version >= 850)
+ return 1;
+ if (script->version >= 500)
+ return 8;
+ return 6;
+}
+
+Common::SharedPtr<Node> Handler::readVar(int varType) {
+ Common::SharedPtr<Node> castID;
+ if (varType == 0x6 && script->version >= 500) // field cast ID
+ castID = pop();
+ Common::SharedPtr<Node> id = pop();
+
+ switch (varType) {
+ case 0x1: // global
+ case 0x2: // global
+ case 0x3: // property/instance
+ return id;
+ case 0x4: // arg
+ {
+ Common::String name_ = getArgumentName(id->getValue()->i / variableMultiplier());
+ auto ref = Common::SharedPtr<Datum>(new Datum(kDatumVarRef, name_));
+ return Common::SharedPtr<Node>(new LiteralNode(Common::move(ref)));
+ }
+ case 0x5: // local
+ {
+ Common::String name_ = getLocalName(id->getValue()->i / variableMultiplier());
+ auto ref = Common::SharedPtr<Datum>(new Datum(kDatumVarRef, name_));
+ return Common::SharedPtr<Node>(new LiteralNode(Common::move(ref)));
+ }
+ case 0x6: // field
+ return Common::SharedPtr<Node>(new MemberExprNode("field", Common::move(id), Common::move(castID)));
+ default:
+ warning(Common::String::format("findVar: unhandled var type %d", varType).c_str());
+ break;
+ }
+ return Common::SharedPtr<Node>(new ErrorNode());
+}
+
+Common::String Handler::getVarNameFromSet(const Bytecode &bytecode) {
+ Common::String varName;
+ switch (bytecode.opcode) {
+ case kOpSetGlobal:
+ case kOpSetGlobal2:
+ varName = getName(bytecode.obj);
+ break;
+ case kOpSetProp:
+ varName = getName(bytecode.obj);
+ break;
+ case kOpSetParam:
+ varName = getArgumentName(bytecode.obj / variableMultiplier());
+ break;
+ case kOpSetLocal:
+ varName = getLocalName(bytecode.obj / variableMultiplier());
+ break;
+ default:
+ varName = "ERROR";
+ break;
+ }
+ return varName;
+}
+
+Common::SharedPtr<Node> Handler::readV4Property(int propertyType, int propertyID) {
+ switch (propertyType) {
+ case 0x00:
+ {
+ if (propertyID <= 0x0b) { // movie property
+ auto propName = StandardNames::getName(StandardNames::moviePropertyNames, propertyID);
+ return Common::SharedPtr<Node>(new TheExprNode(propName));
+ } else { // last chunk
+ auto string = pop();
+ auto chunkType = static_cast<ChunkExprType>(propertyID - 0x0b);
+ return Common::SharedPtr<Node>(new LastStringChunkExprNode(chunkType, Common::move(string)));
+ }
+ }
+ break;
+ case 0x01: // number of chunks
+ {
+ auto string = pop();
+ return Common::SharedPtr<Node>(new StringChunkCountExprNode(static_cast<ChunkExprType>(propertyID), Common::move(string)));
+ }
+ break;
+ case 0x02: // menu property
+ {
+ auto menuID = pop();
+ return Common::SharedPtr<Node>(new MenuPropExprNode(Common::move(menuID), propertyID));
+ }
+ break;
+ case 0x03: // menu item property
+ {
+ auto menuID = pop();
+ auto itemID = pop();
+ return Common::SharedPtr<Node>(new MenuItemPropExprNode(Common::move(menuID), Common::move(itemID), propertyID));
+ }
+ break;
+ case 0x04: // sound property
+ {
+ auto soundID = pop();
+ return Common::SharedPtr<Node>(new SoundPropExprNode(Common::move(soundID), propertyID));
+ }
+ break;
+ case 0x05: // resource property - unused?
+ return Common::SharedPtr<Node>(new CommentNode("ERROR: Resource property"));
+ case 0x06: // sprite property
+ {
+ auto spriteID = pop();
+ return Common::SharedPtr<Node>(new SpritePropExprNode(Common::move(spriteID), propertyID));
+ }
+ break;
+ case 0x07: // animation property
+ return Common::SharedPtr<Node>(new TheExprNode(StandardNames::getName(StandardNames::animationPropertyNames, propertyID)));
+ case 0x08: // animation 2 property
+ if (propertyID == 0x02 && script->version >= 500) { // the number of castMembers supports castLib selection from Director 5.0
+ auto castLib = pop();
+ if (!(castLib->type == kLiteralNode && castLib->getValue()->type == kDatumInt && castLib->getValue()->toInt() == 0)) {
+ auto castLibNode = Common::SharedPtr<Node>(new MemberExprNode("castLib", castLib, nullptr));
+ return Common::SharedPtr<Node>(new ThePropExprNode(castLibNode, StandardNames::getName(StandardNames::animation2PropertyNames, propertyID)));
+ }
+ }
+ return Common::SharedPtr<Node>(new TheExprNode(StandardNames::getName(StandardNames::animation2PropertyNames, propertyID)));
+ case 0x09: // generic cast member
+ case 0x0a: // chunk of cast member
+ case 0x0b: // field
+ case 0x0c: // chunk of field
+ case 0x0d: // digital video
+ case 0x0e: // bitmap
+ case 0x0f: // sound
+ case 0x10: // button
+ case 0x11: // shape
+ case 0x12: // movie
+ case 0x13: // script
+ case 0x14: // scriptText
+ case 0x15: // chunk of scriptText
+ {
+ auto propName = StandardNames::getName(StandardNames::memberPropertyNames, propertyID);
+ Common::SharedPtr<Node> castID;
+ if (script->version >= 500) {
+ castID = pop();
+ }
+ auto memberID = pop();
+ Common::String prefix;
+ if (propertyType == 0x0b || propertyType == 0x0c) {
+ prefix = "field";
+ } else if (propertyType == 0x14 || propertyType == 0x15) {
+ prefix = "script";
+ } else {
+ prefix = (script->version >= 500) ? "member" : "cast";
+ }
+ auto member = Common::SharedPtr<Node>(new MemberExprNode(prefix, Common::move(memberID), Common::move(castID)));
+ Common::SharedPtr<Node> entity;
+ if (propertyType == 0x0a || propertyType == 0x0c || propertyType == 0x15) {
+ entity = readChunkRef(Common::move(member));
+ } else {
+ entity = member;
+ }
+ return Common::SharedPtr<Node>(new ThePropExprNode(Common::move(entity), propName));
+ }
+ break;
+ default:
+ break;
+ }
+ return Common::SharedPtr<Node>(new CommentNode(Common::String::format("ERROR: Unknown property type %d", propertyType)));
+}
+
+Common::SharedPtr<Node> Handler::readChunkRef(Common::SharedPtr<Node> string) {
+ auto lastLine = pop();
+ auto firstLine = pop();
+ auto lastItem = pop();
+ auto firstItem = pop();
+ auto lastWord = pop();
+ auto firstWord = pop();
+ auto lastChar = pop();
+ auto firstChar = pop();
+
+ if (!(firstLine->type == kLiteralNode && firstLine->getValue()->type == kDatumInt && firstLine->getValue()->toInt() == 0))
+ string = Common::SharedPtr<Node>(new ChunkExprNode(kChunkLine, Common::move(firstLine), Common::move(lastLine), Common::move(string)));
+ if (!(firstItem->type == kLiteralNode && firstItem->getValue()->type == kDatumInt && firstItem->getValue()->toInt() == 0))
+ string = Common::SharedPtr<Node>(new ChunkExprNode(kChunkItem, Common::move(firstItem), Common::move(lastItem), Common::move(string)));
+ if (!(firstWord->type == kLiteralNode && firstWord->getValue()->type == kDatumInt && firstWord->getValue()->toInt() == 0))
+ string = Common::SharedPtr<Node>(new ChunkExprNode(kChunkWord, Common::move(firstWord), Common::move(lastWord), Common::move(string)));
+ if (!(firstChar->type == kLiteralNode && firstChar->getValue()->type == kDatumInt && firstChar->getValue()->toInt() == 0))
+ string = Common::SharedPtr<Node>(new ChunkExprNode(kChunkChar, Common::move(firstChar), Common::move(lastChar), Common::move(string)));
+
+ return string;
+}
+
+void Handler::tagLoops() {
+ // Tag any jmpifz which is a loop with the loop type
+ // (kTagRepeatWhile, kTagRepeatWithIn, kTagRepeatWithTo, kTagRepeatWithDownTo).
+ // Tag the instruction which `next repeat` jumps to with kTagNextRepeatTarget.
+ // Tag any instructions which are internal loop logic with kTagSkip, so that
+ // they will be skipped during translation.
+
+ for (uint32_t startIndex = 0; startIndex < bytecodeArray.size(); startIndex++) {
+ // All loops begin with jmpifz...
+ auto &jmpifz = bytecodeArray[startIndex];
+ if (jmpifz.opcode != kOpJmpIfZ)
+ continue;
+
+ // ...and end with endrepeat.
+ uint32_t jmpPos = jmpifz.pos + jmpifz.obj;
+ uint32_t endIndex = bytecodePosMap[jmpPos];
+ auto &endRepeat = bytecodeArray[endIndex - 1];
+ if (endRepeat.opcode != kOpEndRepeat || (endRepeat.pos - endRepeat.obj) > jmpifz.pos)
+ continue;
+
+ BytecodeTag loopType = identifyLoop(startIndex, endIndex);
+ bytecodeArray[startIndex].tag = loopType;
+
+ if (loopType == kTagRepeatWithIn) {
+ for (uint32_t i = startIndex - 7, end = startIndex - 1; i <= end; i++)
+ bytecodeArray[i].tag = kTagSkip;
+ for (uint32_t i = startIndex + 1, end = startIndex + 5; i <= end; i++)
+ bytecodeArray[i].tag = kTagSkip;
+ bytecodeArray[endIndex - 3].tag = kTagNextRepeatTarget; // pushint8 1
+ bytecodeArray[endIndex - 3].ownerLoop = startIndex;
+ bytecodeArray[endIndex - 2].tag = kTagSkip; // add
+ bytecodeArray[endIndex - 1].tag = kTagSkip; // endrepeat
+ bytecodeArray[endIndex - 1].ownerLoop = startIndex;
+ bytecodeArray[endIndex].tag = kTagSkip; // pop 3
+ } else if (loopType == kTagRepeatWithTo || loopType == kTagRepeatWithDownTo) {
+ uint32_t conditionStartIndex = bytecodePosMap[endRepeat.pos - endRepeat.obj];
+ bytecodeArray[conditionStartIndex - 1].tag = kTagSkip; // set
+ bytecodeArray[conditionStartIndex].tag = kTagSkip; // get
+ bytecodeArray[startIndex - 1].tag = kTagSkip; // lteq / gteq
+ bytecodeArray[endIndex - 5].tag = kTagNextRepeatTarget; // pushint8 1 / pushint8 -1
+ bytecodeArray[endIndex - 5].ownerLoop = startIndex;
+ bytecodeArray[endIndex - 4].tag = kTagSkip; // get
+ bytecodeArray[endIndex - 3].tag = kTagSkip; // add
+ bytecodeArray[endIndex - 2].tag = kTagSkip; // set
+ bytecodeArray[endIndex - 1].tag = kTagSkip; // endrepeat
+ bytecodeArray[endIndex - 1].ownerLoop = startIndex;
+ } else if (loopType == kTagRepeatWhile) {
+ bytecodeArray[endIndex - 1].tag = kTagNextRepeatTarget; // endrepeat
+ bytecodeArray[endIndex - 1].ownerLoop = startIndex;
+ }
+ }
+}
+
+bool Handler::isRepeatWithIn(uint32_t startIndex, uint32_t endIndex) {
+ if (startIndex < 7 || startIndex > bytecodeArray.size() - 6)
+ return false;
+ if (!(bytecodeArray[startIndex - 7].opcode == kOpPeek && bytecodeArray[startIndex - 7].obj == 0))
+ return false;
+ if (!(bytecodeArray[startIndex - 6].opcode == kOpPushArgList && bytecodeArray[startIndex - 6].obj == 1))
+ return false;
+ if (!(bytecodeArray[startIndex - 5].opcode == kOpExtCall && getName(bytecodeArray[startIndex - 5].obj) == "count"))
+ return false;
+ if (!(bytecodeArray[startIndex - 4].opcode == kOpPushInt8 && bytecodeArray[startIndex - 4].obj == 1))
+ return false;
+ if (!(bytecodeArray[startIndex - 3].opcode == kOpPeek && bytecodeArray[startIndex - 3].obj == 0))
+ return false;
+ if (!(bytecodeArray[startIndex - 2].opcode == kOpPeek && bytecodeArray[startIndex - 2].obj == 2))
+ return false;
+ if (!(bytecodeArray[startIndex - 1].opcode == kOpLtEq))
+ return false;
+ // if (!(bytecodeArray[startIndex].opcode == kOpJmpIfZ))
+ // return false;
+ if (!(bytecodeArray[startIndex + 1].opcode == kOpPeek && bytecodeArray[startIndex + 1].obj == 2))
+ return false;
+ if (!(bytecodeArray[startIndex + 2].opcode == kOpPeek && bytecodeArray[startIndex + 2].obj == 1))
+ return false;
+ if (!(bytecodeArray[startIndex + 3].opcode == kOpPushArgList && bytecodeArray[startIndex + 3].obj == 2))
+ return false;
+ if (!(bytecodeArray[startIndex + 4].opcode == kOpExtCall && getName(bytecodeArray[startIndex + 4].obj) == "getAt"))
+ return false;
+ if (!(bytecodeArray[startIndex + 5].opcode == kOpSetGlobal || bytecodeArray[startIndex + 5].opcode == kOpSetProp
+ || bytecodeArray[startIndex + 5].opcode == kOpSetParam || bytecodeArray[startIndex + 5].opcode == kOpSetLocal))
+ return false;
+
+ if (endIndex < 3)
+ return false;
+ if (!(bytecodeArray[endIndex - 3].opcode == kOpPushInt8 && bytecodeArray[endIndex - 3].obj == 1))
+ return false;
+ if (!(bytecodeArray[endIndex - 2].opcode == kOpAdd))
+ return false;
+ // if (!(bytecodeArray[startIndex - 1].opcode == kOpEndRepeat))
+ // return false;
+ if (!(bytecodeArray[endIndex].opcode == kOpPop && bytecodeArray[endIndex].obj == 3))
+ return false;
+
+ return true;
+}
+
+BytecodeTag Handler::identifyLoop(uint32_t startIndex, uint32_t endIndex) {
+ if (isRepeatWithIn(startIndex, endIndex))
+ return kTagRepeatWithIn;
+
+ if (startIndex < 1)
+ return kTagRepeatWhile;
+
+ bool up;
+ switch (bytecodeArray[startIndex - 1].opcode) {
+ case kOpLtEq:
+ up = true;
+ break;
+ case kOpGtEq:
+ up = false;
+ break;
+ default:
+ return kTagRepeatWhile;
+ }
+
+ auto &endRepeat = bytecodeArray[endIndex - 1];
+ uint32_t conditionStartIndex = bytecodePosMap[endRepeat.pos - endRepeat.obj];
+
+ if (conditionStartIndex < 1)
+ return kTagRepeatWhile;
+
+ OpCode getOp;
+ switch (bytecodeArray[conditionStartIndex - 1].opcode) {
+ case kOpSetGlobal:
+ getOp = kOpGetGlobal;
+ break;
+ case kOpSetGlobal2:
+ getOp = kOpGetGlobal2;
+ break;
+ case kOpSetProp:
+ getOp = kOpGetProp;
+ break;
+ case kOpSetParam:
+ getOp = kOpGetParam;
+ break;
+ case kOpSetLocal:
+ getOp = kOpGetLocal;
+ break;
+ default:
+ return kTagRepeatWhile;
+ }
+ OpCode setOp = bytecodeArray[conditionStartIndex - 1].opcode;
+ int32_t varID = bytecodeArray[conditionStartIndex - 1].obj;
+
+ if (!(bytecodeArray[conditionStartIndex].opcode == getOp && bytecodeArray[conditionStartIndex].obj == varID))
+ return kTagRepeatWhile;
+
+ if (endIndex < 5)
+ return kTagRepeatWhile;
+ if (up) {
+ if (!(bytecodeArray[endIndex - 5].opcode == kOpPushInt8 && bytecodeArray[endIndex - 5].obj == 1))
+ return kTagRepeatWhile;
+ } else {
+ if (!(bytecodeArray[endIndex - 5].opcode == kOpPushInt8 && bytecodeArray[endIndex - 5].obj == -1))
+ return kTagRepeatWhile;
+ }
+ if (!(bytecodeArray[endIndex - 4].opcode == getOp && bytecodeArray[endIndex - 4].obj == varID))
+ return kTagRepeatWhile;
+ if (!(bytecodeArray[endIndex - 3].opcode == kOpAdd))
+ return kTagRepeatWhile;
+ if (!(bytecodeArray[endIndex - 2].opcode == setOp && bytecodeArray[endIndex - 2].obj == varID))
+ return kTagRepeatWhile;
+
+ return up ? kTagRepeatWithTo : kTagRepeatWithDownTo;
+}
+
+void Handler::parse() {
+ tagLoops();
+ stack.clear();
+ uint32_t i = 0;
+ while (i < bytecodeArray.size()) {
+ auto &bytecode = bytecodeArray[i];
+ uint32_t pos = bytecode.pos;
+ // exit last block if at end
+ while (pos == ast.currentBlock->endPos) {
+ auto exitedBlock = ast.currentBlock;
+ auto ancestorStmt = ast.currentBlock->ancestorStatement();
+ ast.exitBlock();
+ if (ancestorStmt) {
+ if (ancestorStmt->type == kIfStmtNode) {
+ auto ifStatement = static_cast<IfStmtNode *>(ancestorStmt);
+ if (ifStatement->hasElse && exitedBlock == ifStatement->block1.get()) {
+ ast.enterBlock(ifStatement->block2.get());
+ }
+ } else if (ancestorStmt->type == kCaseStmtNode) {
+ auto caseStmt = static_cast<CaseStmtNode *>(ancestorStmt);
+ auto caseLabel = ast.currentBlock->currentCaseLabel;
+ if (caseLabel) {
+ if (caseLabel->expect == kCaseExpectOtherwise) {
+ ast.currentBlock->currentCaseLabel = nullptr;
+ caseStmt->addOtherwise();
+ size_t otherwiseIndex = bytecodePosMap[caseStmt->potentialOtherwisePos];
+ bytecodeArray[otherwiseIndex].translation = Common::SharedPtr<Node>(caseStmt->otherwise);
+ ast.enterBlock(caseStmt->otherwise->block.get());
+ } else if (caseLabel->expect == kCaseExpectEnd) {
+ ast.currentBlock->currentCaseLabel = nullptr;
+ }
+ }
+ }
+ }
+ }
+ auto translateSize = translateBytecode(bytecode, i);
+ i += translateSize;
+ }
+}
+
+uint32_t Handler::translateBytecode(Bytecode &bytecode, uint32_t index) {
+ if (bytecode.tag == kTagSkip || bytecode.tag == kTagNextRepeatTarget) {
+ // This is internal loop logic. Skip it.
+ return 1;
+ }
+
+ Common::SharedPtr<Node> translation = nullptr;
+ BlockNode *nextBlock = nullptr;
+
+ switch (bytecode.opcode) {
+ case kOpRet:
+ case kOpRetFactory:
+ if (index == bytecodeArray.size() - 1) {
+ return 1; // end of handler
+ }
+ translation = Common::SharedPtr<Node>(new ExitStmtNode());
+ break;
+ case kOpPushZero:
+ translation = Common::SharedPtr<Node>(new LiteralNode(Common::SharedPtr<Datum>(new Datum(0))));
+ break;
+ case kOpMul:
+ case kOpAdd:
+ case kOpSub:
+ case kOpDiv:
+ case kOpMod:
+ case kOpJoinStr:
+ case kOpJoinPadStr:
+ case kOpLt:
+ case kOpLtEq:
+ case kOpNtEq:
+ case kOpEq:
+ case kOpGt:
+ case kOpGtEq:
+ case kOpAnd:
+ case kOpOr:
+ case kOpContainsStr:
+ case kOpContains0Str:
+ {
+ auto b = pop();
+ auto a = pop();
+ translation = Common::SharedPtr<Node>(new BinaryOpNode(bytecode.opcode, Common::move(a), Common::move(b)));
+ }
+ break;
+ case kOpInv:
+ {
+ auto x = pop();
+ translation = Common::SharedPtr<Node>(new InverseOpNode(Common::move(x)));
+ }
+ break;
+ case kOpNot:
+ {
+ auto x = pop();
+ translation = Common::SharedPtr<Node>(new NotOpNode(Common::move(x)));
+ }
+ break;
+ case kOpGetChunk:
+ {
+ auto string = pop();
+ translation = readChunkRef(Common::move(string));
+ }
+ break;
+ case kOpHiliteChunk:
+ {
+ Common::SharedPtr<Node> castID;
+ if (script->version >= 500)
+ castID = pop();
+ auto fieldID = pop();
+ auto field = Common::SharedPtr<Node>(new MemberExprNode("field", Common::move(fieldID), Common::move(castID)));
+ auto chunk = readChunkRef(Common::move(field));
+ if (chunk->type == kCommentNode) { // error comment
+ translation = chunk;
+ } else {
+ translation = Common::SharedPtr<Node>(new ChunkHiliteStmtNode(Common::move(chunk)));
+ }
+ }
+ break;
+ case kOpOntoSpr:
+ {
+ auto secondSprite = pop();
+ auto firstSprite = pop();
+ translation = Common::SharedPtr<Node>(new SpriteIntersectsExprNode(Common::move(firstSprite), Common::move(secondSprite)));
+ }
+ break;
+ case kOpIntoSpr:
+ {
+ auto secondSprite = pop();
+ auto firstSprite = pop();
+ translation = Common::SharedPtr<Node>(new SpriteWithinExprNode(Common::move(firstSprite), Common::move(secondSprite)));
+ }
+ break;
+ case kOpGetField:
+ {
+ Common::SharedPtr<Node> castID;
+ if (script->version >= 500)
+ castID = pop();
+ auto fieldID = pop();
+ translation = Common::SharedPtr<Node>(new MemberExprNode("field", Common::move(fieldID), Common::move(castID)));
+ }
+ break;
+ case kOpStartTell:
+ {
+ auto window = pop();
+ auto tellStmt = Common::SharedPtr<TellStmtNode>(new TellStmtNode(Common::move(window)));
+ translation = tellStmt;
+ nextBlock = tellStmt->block.get();
+ }
+ break;
+ case kOpEndTell:
+ {
+ ast.exitBlock();
+ return 1;
+ }
+ break;
+ case kOpPushList:
+ {
+ auto list = pop();
+ list->getValue()->type = kDatumList;
+ translation = list;
+ }
+ break;
+ case kOpPushPropList:
+ {
+ auto list = pop();
+ list->getValue()->type = kDatumPropList;
+ translation = list;
+ }
+ break;
+ case kOpSwap:
+ if (stack.size() >= 2) {
+ std::swap(stack[stack.size() - 1], stack[stack.size() - 2]);
+ } else {
+ warning("kOpSwap: Stack too small!");
+ }
+ return 1;
+ case kOpPushInt8:
+ case kOpPushInt16:
+ case kOpPushInt32:
+ {
+ auto i = Common::SharedPtr<Datum>(new Datum(bytecode.obj));
+ translation = Common::SharedPtr<Node>(new LiteralNode(Common::move(i)));
+ }
+ break;
+ case kOpPushFloat32:
+ {
+ auto f = Common::SharedPtr<Datum>(new Datum(*(float *)(&bytecode.obj)));
+ translation = Common::SharedPtr<Node>(new LiteralNode(Common::move(f)));
+ }
+ break;
+ case kOpPushArgListNoRet:
+ {
+ auto argCount = bytecode.obj;
+ Common::Array<Common::SharedPtr<Node>> args;
+ args.resize(argCount);
+ while (argCount) {
+ argCount--;
+ args[argCount] = pop();
+ }
+ auto argList = Common::SharedPtr<Datum>(new Datum(kDatumArgListNoRet, args));
+ translation = Common::SharedPtr<Node>(new LiteralNode(Common::move(argList)));
+ }
+ break;
+ case kOpPushArgList:
+ {
+ auto argCount = bytecode.obj;
+ Common::Array<Common::SharedPtr<Node>> args;
+ args.resize(argCount);
+ while (argCount) {
+ argCount--;
+ args[argCount] = pop();
+ }
+ auto argList = Common::SharedPtr<Datum>(new Datum(kDatumArgList, args));
+ translation = Common::SharedPtr<Node>(new LiteralNode(Common::move(argList)));
+ }
+ break;
+ case kOpPushCons:
+ {
+ int literalID = bytecode.obj / variableMultiplier();
+ if (-1 < literalID && (unsigned)literalID < script->literals.size()) {
+ translation = Common::SharedPtr<Node>(new LiteralNode(script->literals[literalID].value));
+ } else {
+ translation = Common::SharedPtr<Node>(new ErrorNode());
+ }
+ break;
+ }
+ case kOpPushSymb:
+ {
+ auto sym = Common::SharedPtr<Datum>(new Datum(kDatumSymbol, getName(bytecode.obj)));
+ translation = Common::SharedPtr<Node>(new LiteralNode(Common::move(sym)));
+ }
+ break;
+ case kOpPushVarRef:
+ {
+ auto ref = Common::SharedPtr<Datum>(new Datum(kDatumVarRef, getName(bytecode.obj)));
+ translation = Common::SharedPtr<Node>(new LiteralNode(Common::move(ref)));
+ }
+ break;
+ case kOpGetGlobal:
+ case kOpGetGlobal2:
+ {
+ auto name_ = getName(bytecode.obj);
+ translation = Common::SharedPtr<Node>(new VarNode(name_));
+ }
+ break;
+ case kOpGetProp:
+ translation = Common::SharedPtr<Node>(new VarNode(getName(bytecode.obj)));
+ break;
+ case kOpGetParam:
+ translation = Common::SharedPtr<Node>(new VarNode(getArgumentName(bytecode.obj / variableMultiplier())));
+ break;
+ case kOpGetLocal:
+ translation = Common::SharedPtr<Node>(new VarNode(getLocalName(bytecode.obj / variableMultiplier())));
+ break;
+ case kOpSetGlobal:
+ case kOpSetGlobal2:
+ {
+ auto varName = getName(bytecode.obj);
+ auto var = Common::SharedPtr<Node>(new VarNode(varName));
+ auto value = pop();
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(var), Common::move(value)));
+ }
+ break;
+ case kOpSetProp:
+ {
+ auto var = Common::SharedPtr<Node>(new VarNode(getName(bytecode.obj)));
+ auto value = pop();
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(var), Common::move(value)));
+ }
+ break;
+ case kOpSetParam:
+ {
+ auto var = Common::SharedPtr<Node>(new VarNode(getArgumentName(bytecode.obj / variableMultiplier())));
+ auto value = pop();
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(var), Common::move(value)));
+ }
+ break;
+ case kOpSetLocal:
+ {
+ auto var = Common::SharedPtr<Node>(new VarNode(getLocalName(bytecode.obj / variableMultiplier())));
+ auto value = pop();
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(var), Common::move(value)));
+ }
+ break;
+ case kOpJmp:
+ {
+ uint32_t targetPos = bytecode.pos + bytecode.obj;
+ size_t targetIndex = bytecodePosMap[targetPos];
+ auto &targetBytecode = bytecodeArray[targetIndex];
+ auto ancestorLoop = ast.currentBlock->ancestorLoop();
+ if (ancestorLoop) {
+ if (bytecodeArray[targetIndex - 1].opcode == kOpEndRepeat && bytecodeArray[targetIndex - 1].ownerLoop == ancestorLoop->startIndex) {
+ translation = Common::SharedPtr<Node>(new ExitRepeatStmtNode());
+ break;
+ } else if (bytecodeArray[targetIndex].tag == kTagNextRepeatTarget && bytecodeArray[targetIndex].ownerLoop == ancestorLoop->startIndex) {
+ translation = Common::SharedPtr<Node>(new NextRepeatStmtNode());
+ break;
+ }
+ }
+ auto &nextBytecode = bytecodeArray[index + 1];
+ auto ancestorStatement = ast.currentBlock->ancestorStatement();
+ if (ancestorStatement && nextBytecode.pos == ast.currentBlock->endPos) {
+ if (ancestorStatement->type == kIfStmtNode) {
+ auto ifStmt = static_cast<IfStmtNode *>(ancestorStatement);
+ if (ast.currentBlock == ifStmt->block1.get()) {
+ ifStmt->hasElse = true;
+ ifStmt->block2->endPos = targetPos;
+ return 1; // if statement amended, nothing to push
+ }
+ } else if (ancestorStatement->type == kCaseStmtNode) {
+ auto caseStmt = static_cast<CaseStmtNode *>(ancestorStatement);
+ caseStmt->potentialOtherwisePos = bytecode.pos;
+ caseStmt->endPos = targetPos;
+ targetBytecode.tag = kTagEndCase;
+ return 1;
+ }
+ }
+ if (targetBytecode.opcode == kOpPop && targetBytecode.obj == 1) {
+ // This is a case statement starting with 'otherwise'
+ auto value = pop();
+ auto caseStmt = Common::SharedPtr<CaseStmtNode>(new CaseStmtNode(Common::move(value)));
+ caseStmt->endPos = targetPos;
+ targetBytecode.tag = kTagEndCase;
+ caseStmt->addOtherwise();
+ translation = caseStmt;
+ nextBlock = caseStmt->otherwise->block.get();
+ break;
+ }
+ translation = Common::SharedPtr<Node>(new CommentNode("ERROR: Could not identify jmp"));
+ }
+ break;
+ case kOpEndRepeat:
+ // This should normally be tagged kTagSkip or kTagNextRepeatTarget and skipped.
+ translation = Common::SharedPtr<Node>(new CommentNode("ERROR: Stray endrepeat"));
+ break;
+ case kOpJmpIfZ:
+ {
+ uint32_t endPos = bytecode.pos + bytecode.obj;
+ uint32_t endIndex = bytecodePosMap[endPos];
+ switch (bytecode.tag) {
+ case kTagRepeatWhile:
+ {
+ auto condition = pop();
+ auto loop = Common::SharedPtr<RepeatWhileStmtNode>(new RepeatWhileStmtNode(index, Common::move(condition)));
+ loop->block->endPos = endPos;
+ translation = loop;
+ nextBlock = loop->block.get();
+ }
+ break;
+ case kTagRepeatWithIn:
+ {
+ auto list = pop();
+ Common::String varName = getVarNameFromSet(bytecodeArray[index + 5]);
+ auto loop = Common::SharedPtr<RepeatWithInStmtNode>(new RepeatWithInStmtNode(index, varName, Common::move(list)));
+ loop->block->endPos = endPos;
+ translation = loop;
+ nextBlock = loop->block.get();
+ }
+ break;
+ case kTagRepeatWithTo:
+ case kTagRepeatWithDownTo:
+ {
+ bool up = (bytecode.tag == kTagRepeatWithTo);
+ auto end = pop();
+ auto start = pop();
+ auto endRepeat = bytecodeArray[endIndex - 1];
+ uint32_t conditionStartIndex = bytecodePosMap[endRepeat.pos - endRepeat.obj];
+ Common::String varName = getVarNameFromSet(bytecodeArray[conditionStartIndex - 1]);
+ auto loop = Common::SharedPtr<RepeatWithToStmtNode>(new RepeatWithToStmtNode(index, varName, Common::move(start), up, Common::move(end)));
+ loop->block->endPos = endPos;
+ translation = loop;
+ nextBlock = loop->block.get();
+ }
+ break;
+ default:
+ {
+ auto condition = pop();
+ auto ifStmt = Common::SharedPtr<IfStmtNode>(new IfStmtNode(Common::move(condition)));
+ ifStmt->block1->endPos = endPos;
+ translation = ifStmt;
+ nextBlock = ifStmt->block1.get();
+ }
+ break;
+ }
+ }
+ break;
+ case kOpLocalCall:
+ {
+ auto argList = pop();
+ translation = Common::SharedPtr<Node>(new CallNode(script->handlers[bytecode.obj].name, Common::move(argList)));
+ }
+ break;
+ case kOpExtCall:
+ case kOpTellCall:
+ {
+ Common::String name_ = getName(bytecode.obj);
+ auto argList = pop();
+ bool isStatement = (argList->getValue()->type == kDatumArgListNoRet);
+ auto &rawArgList = argList->getValue()->l;
+ size_t nargs = rawArgList.size();
+ if (isStatement && name_ == "sound" && nargs > 0 && rawArgList[0]->type == kLiteralNode && rawArgList[0]->getValue()->type == kDatumSymbol) {
+ Common::String cmd = rawArgList[0]->getValue()->s;
+ rawArgList.erase(rawArgList.begin());
+ translation = Common::SharedPtr<Node>(new SoundCmdStmtNode(cmd, Common::move(argList)));
+ } else if (isStatement && name_ == "play" && nargs <= 2) {
+ translation = Common::SharedPtr<Node>(new PlayCmdStmtNode(Common::move(argList)));
+ } else {
+ translation = Common::SharedPtr<Node>(new CallNode(name_, Common::move(argList)));
+ }
+ }
+ break;
+ case kOpObjCallV4:
+ {
+ auto object = readVar(bytecode.obj);
+ auto argList = pop();
+ auto &rawArgList = argList->getValue()->l;
+ if (rawArgList.size() > 0) {
+ // first arg is a symbol
+ // replace it with a variable
+ rawArgList[0] = Common::SharedPtr<Node>(new VarNode(rawArgList[0]->getValue()->s));
+ }
+ translation = Common::SharedPtr<Node>(new ObjCallV4Node(Common::move(object), Common::move(argList)));
+ }
+ break;
+ case kOpPut:
+ {
+ PutType putType = static_cast<PutType>((bytecode.obj >> 4) & 0xF);
+ uint32_t varType = bytecode.obj & 0xF;
+ auto var = readVar(varType);
+ auto val = pop();
+ translation = Common::SharedPtr<Node>(new PutStmtNode(putType, Common::move(var), Common::move(val)));
+ }
+ break;
+ case kOpPutChunk:
+ {
+ PutType putType = static_cast<PutType>((bytecode.obj >> 4) & 0xF);
+ uint32_t varType = bytecode.obj & 0xF;
+ auto var = readVar(varType);
+ auto chunk = readChunkRef(Common::move(var));
+ auto val = pop();
+ if (chunk->type == kCommentNode) { // error comment
+ translation = chunk;
+ } else {
+ translation = Common::SharedPtr<Node>(new PutStmtNode(putType, Common::move(chunk), Common::move(val)));
+ }
+ }
+ break;
+ case kOpDeleteChunk:
+ {
+ auto var = readVar(bytecode.obj);
+ auto chunk = readChunkRef(Common::move(var));
+ if (chunk->type == kCommentNode) { // error comment
+ translation = chunk;
+ } else {
+ translation = Common::SharedPtr<Node>(new ChunkDeleteStmtNode(Common::move(chunk)));
+ }
+ }
+ break;
+ case kOpGet:
+ {
+ int propertyID = pop()->getValue()->toInt();
+ translation = readV4Property(bytecode.obj, propertyID);
+ }
+ break;
+ case kOpSet:
+ {
+ int propertyID = pop()->getValue()->toInt();
+ auto value = pop();
+ if (bytecode.obj == 0x00 && 0x01 <= propertyID && propertyID <= 0x05 && value->getValue()->type == kDatumString) {
+ // This is either a `set eventScript to "script"` or `when event then script` statement.
+ // If the script starts with a space, it's probably a when statement.
+ // If the script contains a line break, it's definitely a when statement.
+ Common::String script_ = value->getValue()->s;
+ if (script_.size() > 0 && (script_[0] == ' ' || script_.find('\r') != Common::String::npos)) {
+ translation = Common::SharedPtr<Node>(new WhenStmtNode(propertyID, script_));
+ }
+ }
+ if (!translation) {
+ auto prop = readV4Property(bytecode.obj, propertyID);
+ if (prop->type == kCommentNode) { // error comment
+ translation = prop;
+ } else {
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(prop), Common::move(value), true));
+ }
+ }
+ }
+ break;
+ case kOpGetMovieProp:
+ translation = Common::SharedPtr<Node>(new TheExprNode(getName(bytecode.obj)));
+ break;
+ case kOpSetMovieProp:
+ {
+ auto value = pop();
+ auto prop = Common::SharedPtr<TheExprNode>(new TheExprNode(getName(bytecode.obj)));
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(prop), Common::move(value)));
+ }
+ break;
+ case kOpGetObjProp:
+ case kOpGetChainedProp:
+ {
+ auto object = pop();
+ translation = Common::SharedPtr<Node>(new ObjPropExprNode(Common::move(object), getName(bytecode.obj)));
+ }
+ break;
+ case kOpSetObjProp:
+ {
+ auto value = pop();
+ auto object = pop();
+ auto prop = Common::SharedPtr<ObjPropExprNode>(new ObjPropExprNode(Common::move(object), getName(bytecode.obj)));
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(prop), Common::move(value)));
+ }
+ break;
+ case kOpPeek:
+ {
+ // This op denotes the beginning of a 'repeat with ... in list' statement or a case in a cases statement.
+
+ // In a 'repeat with ... in list' statement, this peeked value is the list.
+ // In a cases statement, this is the switch expression.
+
+ auto prevLabel = ast.currentBlock->currentCaseLabel;
+
+ // This must be a case. Find the comparison against the switch expression.
+ auto originalStackSize = stack.size();
+ uint32_t currIndex = index + 1;
+ Bytecode *currBytecode = &bytecodeArray[currIndex];
+ do {
+ translateBytecode(*currBytecode, currIndex);
+ currIndex += 1;
+ currBytecode = &bytecodeArray[currIndex];
+ } while (
+ currIndex < bytecodeArray.size()
+ && !(stack.size() == originalStackSize + 1 && (currBytecode->opcode == kOpEq || currBytecode->opcode == kOpNtEq))
+ );
+ if (currIndex >= bytecodeArray.size()) {
+ bytecode.translation = Common::SharedPtr<Node>(new CommentNode("ERROR: Expected eq or nteq!"));
+ ast.addStatement(bytecode.translation);
+ return currIndex - index + 1;
+ }
+
+ // If the comparison is <>, this is followed by another, equivalent case.
+ // (e.g. this could be case1 in `case1, case2: statement`)
+ bool notEq = (currBytecode->opcode == kOpNtEq);
+ Common::SharedPtr<Node> caseValue = pop(); // This is the value the switch expression is compared against.
+
+ currIndex += 1;
+ currBytecode = &bytecodeArray[currIndex];
+ if (currIndex >= bytecodeArray.size() || currBytecode->opcode != kOpJmpIfZ) {
+ bytecode.translation = Common::SharedPtr<Node>(new CommentNode("ERROR: Expected jmpifz!"));
+ ast.addStatement(bytecode.translation);
+ return currIndex - index + 1;
+ }
+
+ auto &jmpifz = *currBytecode;
+ auto jmpPos = jmpifz.pos + jmpifz.obj;
+ size_t targetIndex = bytecodePosMap[jmpPos];
+ auto &targetBytecode = bytecodeArray[targetIndex];
+ auto &prevFromTarget = bytecodeArray[targetIndex - 1];
+ CaseExpect expect;
+ if (notEq) {
+ expect = kCaseExpectOr; // Expect an equivalent case after this one.
+ } else if (targetBytecode.opcode == kOpPeek) {
+ expect = kCaseExpectNext; // Expect a different case after this one.
+ } else if (targetBytecode.opcode == kOpPop
+ && targetBytecode.obj == 1
+ && (prevFromTarget.opcode != kOpJmp || prevFromTarget.pos + prevFromTarget.obj == targetBytecode.pos)) {
+ expect = kCaseExpectEnd; // Expect the end of the switch statement.
+ } else {
+ expect = kCaseExpectOtherwise; // Expect an 'otherwise' block.
+ }
+
+ auto currLabel = Common::SharedPtr<CaseLabelNode>(new CaseLabelNode(Common::move(caseValue), expect));
+ jmpifz.translation = currLabel;
+ ast.currentBlock->currentCaseLabel = currLabel.get();
+
+ if (!prevLabel) {
+ auto peekedValue = pop();
+ auto caseStmt = Common::SharedPtr<CaseStmtNode>(new CaseStmtNode(Common::move(peekedValue)));
+ caseStmt->firstLabel = currLabel;
+ currLabel->parent = caseStmt.get();
+ bytecode.translation = caseStmt;
+ ast.addStatement(caseStmt);
+ } else if (prevLabel->expect == kCaseExpectOr) {
+ prevLabel->nextOr = currLabel;
+ currLabel->parent = prevLabel;
+ } else if (prevLabel->expect == kCaseExpectNext) {
+ prevLabel->nextLabel = currLabel;
+ currLabel->parent = prevLabel;
+ }
+
+ // The block doesn't start until the after last equivalent case,
+ // so don't create a block yet if we're expecting an equivalent case.
+ if (currLabel->expect != kCaseExpectOr) {
+ currLabel->block = Common::SharedPtr<BlockNode>(new BlockNode());
+ currLabel->block->parent = currLabel.get();
+ currLabel->block->endPos = jmpPos;
+ ast.enterBlock(currLabel->block.get());
+ }
+
+ return currIndex - index + 1;
+ }
+ break;
+ case kOpPop:
+ {
+ // Pop instructions in 'repeat with in' loops are tagged kTagSkip and skipped.
+ if (bytecode.tag == kTagEndCase) {
+ // We've already recognized this as the end of a case statement.
+ // Attach an 'end case' node for the summary only.
+ bytecode.translation = Common::SharedPtr<EndCaseNode>();
+ return 1;
+ }
+ if (bytecode.obj == 1 && stack.size() == 1) {
+ // We have an unused value on the stack, so this must be the end
+ // of a case statement with no labels.
+ auto value = pop();
+ translation = Common::SharedPtr<Node>(new CaseStmtNode(Common::move(value)));
+ break;
+ }
+ // Otherwise, this pop instruction occurs before a 'return' within
+ // a case statement. No translation needed.
+ return 1;
+ }
+ break;
+ case kOpTheBuiltin:
+ {
+ pop(); // empty arglist
+ translation = Common::SharedPtr<Node>(new TheExprNode(getName(bytecode.obj)));
+ }
+ break;
+ case kOpObjCall:
+ {
+ Common::String method = getName(bytecode.obj);
+ auto argList = pop();
+ auto &rawArgList = argList->getValue()->l;
+ size_t nargs = rawArgList.size();
+ if (method == "getAt" && nargs == 2) {
+ // obj.getAt(i) => obj[i]
+ auto obj = rawArgList[0];
+ auto prop = rawArgList[1];
+ translation = Common::SharedPtr<Node>(new ObjBracketExprNode(Common::move(obj), Common::move(prop)));
+ } else if (method == "setAt" && nargs == 3) {
+ // obj.setAt(i) => obj[i] = val
+ auto obj = rawArgList[0];
+ auto prop = rawArgList[1];
+ auto val = rawArgList[2];
+ Common::SharedPtr<Node> propExpr = Common::SharedPtr<Node>(new ObjBracketExprNode(Common::move(obj), Common::move(prop)));
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(propExpr), Common::move(val)));
+ } else if ((method == "getProp" || method == "getPropRef") && (nargs == 3 || nargs == 4) && rawArgList[1]->getValue()->type == kDatumSymbol) {
+ // obj.getProp(#prop, i) => obj.prop[i]
+ // obj.getProp(#prop, i, i2) => obj.prop[i..i2]
+ auto obj = rawArgList[0];
+ Common::String propName = rawArgList[1]->getValue()->s;
+ auto i = rawArgList[2];
+ auto i2 = (nargs == 4) ? rawArgList[3] : nullptr;
+ translation = Common::SharedPtr<Node>(new ObjPropIndexExprNode(Common::move(obj), propName, Common::move(i), Common::move(i2)));
+ } else if (method == "setProp" && (nargs == 4 || nargs == 5) && rawArgList[1]->getValue()->type == kDatumSymbol) {
+ // obj.setProp(#prop, i, val) => obj.prop[i] = val
+ // obj.setProp(#prop, i, i2, val) => obj.prop[i..i2] = val
+ auto obj = rawArgList[0];
+ Common::String propName = rawArgList[1]->getValue()->s;
+ auto i = rawArgList[2];
+ auto i2 = (nargs == 5) ? rawArgList[3] : nullptr;
+ auto propExpr = Common::SharedPtr<ObjPropIndexExprNode>(new ObjPropIndexExprNode(Common::move(obj), propName, Common::move(i), Common::move(i2)));
+ auto val = rawArgList[nargs - 1];
+ translation = Common::SharedPtr<Node>(new AssignmentStmtNode(Common::move(propExpr), Common::move(val)));
+ } else if (method == "count" && nargs == 2 && rawArgList[1]->getValue()->type == kDatumSymbol) {
+ // obj.count(#prop) => obj.prop.count
+ auto obj = rawArgList[0];
+ Common::String propName = rawArgList[1]->getValue()->s;
+ auto propExpr = Common::SharedPtr<ObjPropExprNode>(new ObjPropExprNode(Common::move(obj), propName));
+ translation = Common::SharedPtr<Node>(new ObjPropExprNode(Common::move(propExpr), "count"));
+ } else if ((method == "setContents" || method == "setContentsAfter" || method == "setContentsBefore") && nargs == 2) {
+ // var.setContents(val) => put val into var
+ // var.setContentsAfter(val) => put val after var
+ // var.setContentsBefore(val) => put val before var
+ PutType putType;
+ if (method == "setContents") {
+ putType = kPutInto;
+ } else if (method == "setContentsAfter") {
+ putType = kPutAfter;
+ } else {
+ putType = kPutBefore;
+ }
+ auto var = rawArgList[0];
+ auto val = rawArgList[1];
+ translation = Common::SharedPtr<Node>(new PutStmtNode(putType, Common::move(var), Common::move(val)));
+ } else if (method == "hilite" && nargs == 1) {
+ // chunk.hilite() => hilite chunk
+ auto chunk = rawArgList[0];
+ translation = Common::SharedPtr<Node>(new ChunkHiliteStmtNode(chunk));
+ } else if (method == "delete" && nargs == 1) {
+ // chunk.delete() => delete chunk
+ auto chunk = rawArgList[0];
+ translation = Common::SharedPtr<Node>(new ChunkDeleteStmtNode(chunk));
+ } else {
+ translation = Common::SharedPtr<Node>(new ObjCallNode(method, Common::move(argList)));
+ }
+ }
+ break;
+ case kOpPushChunkVarRef:
+ translation = readVar(bytecode.obj);
+ break;
+ case kOpGetTopLevelProp:
+ {
+ auto name_ = getName(bytecode.obj);
+ translation = Common::SharedPtr<VarNode>(new VarNode(name_));
+ }
+ break;
+ case kOpNewObj:
+ {
+ auto objType = getName(bytecode.obj);
+ auto objArgs = pop();
+ translation = Common::SharedPtr<NewObjNode>(new NewObjNode(objType, Common::move(objArgs)));
+ }
+ break;
+ default:
+ {
+ auto commentText = StandardNames::getOpcodeName(bytecode.opID);
+ if (bytecode.opcode >= 0x40)
+ commentText += Common::String::format(" %d", bytecode.obj);
+ translation = Common::SharedPtr<CommentNode>(new CommentNode(commentText));
+ stack.clear(); // Clear stack so later bytecode won't be too screwed up
+ }
+ }
+
+ if (!translation)
+ translation = Common::SharedPtr<ErrorNode>(new ErrorNode());
+
+ bytecode.translation = translation;
+ if (translation->isExpression) {
+ stack.push_back(Common::move(translation));
+ } else {
+ ast.addStatement(Common::move(translation));
+ }
+
+ if (nextBlock)
+ ast.enterBlock(nextBlock);
+
+ return 1;
+}
+
+Common::String posToString(int32_t pos) {
+ return Common::String::format("[%3d]", pos);
+}
+
+void Handler::writeBytecodeText(CodeWriter &code, bool dotSyntax) const {
+ bool isMethod = script->isFactory();
+
+ if (!isGenericEvent) {
+ if (isMethod) {
+ code.write("method ");
+ } else {
+ code.write("on ");
+ }
+ code.write(name);
+ if (argumentNames.size() > 0) {
+ code.write(" ");
+ for (size_t i = 0; i < argumentNames.size(); i++) {
+ if (i > 0)
+ code.write(", ");
+ code.write(argumentNames[i]);
+ }
+ }
+ code.writeLine();
+ code.indent();
+ }
+ for (auto &bytecode : bytecodeArray) {
+ code.write(posToString(bytecode.pos));
+ code.write(" ");
+ code.write(StandardNames::getOpcodeName(bytecode.opID));
+ switch (bytecode.opcode) {
+ case kOpJmp:
+ case kOpJmpIfZ:
+ code.write(" ");
+ code.write(posToString(bytecode.pos + bytecode.obj));
+ break;
+ case kOpEndRepeat:
+ code.write(" ");
+ code.write(posToString(bytecode.pos - bytecode.obj));
+ break;
+ case kOpPushFloat32:
+ code.write(" ");
+ code.write(Common::String::format("%g", (*(const float *)(&bytecode.obj))));
+ break;
+ default:
+ if (bytecode.opID > 0x40) {
+ code.write(" ");
+ code.write(Common::String::format("%d", bytecode.obj));
+ }
+ break;
+ }
+ if (bytecode.translation) {
+ code.write(" ...");
+ while (code.lineWidth() < 49) {
+ code.write(".");
+ }
+ code.write(" ");
+ if (bytecode.translation->isExpression) {
+ code.write("<");
+ }
+ bytecode.translation->writeScriptText(code, dotSyntax, true);
+ if (bytecode.translation->isExpression) {
+ code.write(">");
+ }
+ }
+ code.writeLine();
+ }
+ if (!isGenericEvent) {
+ code.unindent();
+ if (!isMethod) {
+ code.writeLine("end");
+ }
+ }
+}
+
+} // namespace LingoDec
diff --git a/engines/director/lingo/lingodec/handler.h b/engines/director/lingo/lingodec/handler.h
new file mode 100644
index 00000000000..f5ad04831ae
--- /dev/null
+++ b/engines/director/lingo/lingodec/handler.h
@@ -0,0 +1,110 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_HANDLER_H
+#define LINGODEC_HANDLER_H
+
+#include "common/array.h"
+#include "common/stablemap.h"
+#include "common/str.h"
+#include "./enums.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace LingoDec {
+
+struct AST;
+struct Bytecode;
+class CodeWriter;
+struct Node;
+struct Script;
+
+/* Handler */
+
+struct Handler {
+ int16_t nameID = 0;
+ uint16_t vectorPos = 0;
+ uint32_t compiledLen = 0;
+ uint32_t compiledOffset = 0;
+ uint16_t argumentCount = 0;
+ uint32_t argumentOffset = 0;
+ uint16_t localsCount = 0;
+ uint32_t localsOffset = 0;
+ uint16_t globalsCount = 0;
+ uint32_t globalsOffset = 0;
+ uint32_t unknown1 = 0;
+ uint16_t unknown2 = 0;
+ uint16_t lineCount = 0;
+ uint32_t lineOffset = 0;
+ uint32_t stackHeight = 0;
+
+ Common::Array<int16_t> argumentNameIDs;
+ Common::Array<int16_t> localNameIDs;
+ Common::Array<int16_t> globalNameIDs;
+
+ Script *script = nullptr;
+ Common::Array<Bytecode> bytecodeArray;
+ Common::StableMap<uint32_t, size_t> bytecodePosMap;
+ Common::Array<Common::String> argumentNames;
+ Common::Array<Common::String> localNames;
+ Common::Array<Common::String> globalNames;
+ Common::String name;
+
+ Common::Array<Common::SharedPtr<Node>> stack;
+ AST ast;
+
+ bool isGenericEvent = false;
+
+ Handler(): ast(this) {}
+
+ void setScript(Script *s) {
+ script = s;
+ }
+
+ void readRecord(Common::SeekableReadStream &stream);
+ void readData(Common::SeekableReadStream &stream);
+ Common::Array<int16_t> readVarnamesTable(Common::SeekableReadStream &stream, uint16_t count, uint32_t offset);
+ void readNames();
+ bool validName(int id) const;
+ Common::String getName(int id) const;
+ Common::String getArgumentName(int id) const;
+ Common::String getLocalName(int id) const;
+ Common::SharedPtr<Node> pop();
+ int variableMultiplier();
+ Common::SharedPtr<Node> readVar(int varType);
+ Common::String getVarNameFromSet(const Bytecode &bytecode);
+ Common::SharedPtr<Node> readV4Property(int propertyType, int propertyID);
+ Common::SharedPtr<Node> readChunkRef(Common::SharedPtr<Node> string);
+ void tagLoops();
+ bool isRepeatWithIn(uint32_t startIndex, uint32_t endIndex);
+ BytecodeTag identifyLoop(uint32_t startIndex, uint32_t endIndex);
+ void parse();
+ uint32_t translateBytecode(Bytecode &bytecode, uint32_t index);
+ void writeBytecodeText(CodeWriter &code, bool dotSyntax) const;
+};
+
+/* Bytecode */
+
+struct Bytecode {
+ uint8_t opID;
+ OpCode opcode;
+ int32_t obj;
+ uint32_t pos;
+ BytecodeTag tag;
+ uint32_t ownerLoop;
+ Common::SharedPtr<Node> translation;
+
+ Bytecode(uint8_t op, int32_t o, uint32_t p)
+ : opID(op), obj(o), pos(p), tag(kTagNone), ownerLoop(UINT32_MAX) {
+ opcode = static_cast<OpCode>(op >= 0x40 ? 0x40 + op % 0x40 : op);
+ }
+};
+
+}
+
+#endif // LINGODEC_HANDLER_H
diff --git a/engines/director/lingo/lingodec/names.cpp b/engines/director/lingo/lingodec/names.cpp
new file mode 100644
index 00000000000..3b28d81fcde
--- /dev/null
+++ b/engines/director/lingo/lingodec/names.cpp
@@ -0,0 +1,343 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include "common/str.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "./enums.h"
+#include "./names.h"
+
+namespace LingoDec {
+
+template<class Key, class Val>
+static Common::StableMap<Key, Val> toMap(std::initializer_list<Common::Pair<Key, Val>> items) {
+ Common::StableMap<Key, Val> stableMap;
+ for(auto item : items) {
+ stableMap.insert(Common::Pair<Key, Val>(item.first, item.second));
+ }
+ return stableMap;
+}
+
+/* StandardNames */
+
+Common::StableMap<unsigned int, Common::String> StandardNames::opcodeNames = toMap<unsigned int, Common::String>({
+ // single-byte
+ { kOpRet, "ret" },
+ { kOpRetFactory, "retfactory" },
+ { kOpPushZero, "pushzero" },
+ { kOpMul, "mul" },
+ { kOpAdd, "add" },
+ { kOpSub, "sub" },
+ { kOpDiv, "div" },
+ { kOpMod, "mod" },
+ { kOpInv, "inv" },
+ { kOpJoinStr, "joinstr" },
+ { kOpJoinPadStr, "joinpadstr" },
+ { kOpLt, "lt" },
+ { kOpLtEq, "lteq" },
+ { kOpNtEq, "nteq" },
+ { kOpEq, "eq" },
+ { kOpGt, "gt" },
+ { kOpGtEq, "gteq" },
+ { kOpAnd, "and" },
+ { kOpOr, "or" },
+ { kOpNot, "not" },
+ { kOpContainsStr, "containsstr" },
+ { kOpContains0Str, "contains0str" },
+ { kOpGetChunk, "getchunk" },
+ { kOpHiliteChunk, "hilitechunk" },
+ { kOpOntoSpr, "ontospr" },
+ { kOpIntoSpr, "intospr" },
+ { kOpGetField, "getfield" },
+ { kOpStartTell, "starttell" },
+ { kOpEndTell, "endtell" },
+ { kOpPushList, "pushlist" },
+ { kOpPushPropList, "pushproplist" },
+ { kOpSwap, "swap" },
+
+ // multi-byte
+ { kOpPushInt8, "pushint8" },
+ { kOpPushArgListNoRet, "pusharglistnoret" },
+ { kOpPushArgList, "pusharglist" },
+ { kOpPushCons, "pushcons" },
+ { kOpPushSymb, "pushsymb" },
+ { kOpPushVarRef, "pushvarref" },
+ { kOpGetGlobal2, "getglobal2" },
+ { kOpGetGlobal, "getglobal" },
+ { kOpGetProp, "getprop" },
+ { kOpGetParam, "getparam" },
+ { kOpGetLocal, "getlocal" },
+ { kOpSetGlobal2, "setglobal2" },
+ { kOpSetGlobal, "setglobal" },
+ { kOpSetProp, "setprop" },
+ { kOpSetParam, "setparam" },
+ { kOpSetLocal, "setlocal" },
+ { kOpJmp, "jmp" },
+ { kOpEndRepeat, "endrepeat" },
+ { kOpJmpIfZ, "jmpifz" },
+ { kOpLocalCall, "localcall" },
+ { kOpExtCall, "extcall" },
+ { kOpObjCallV4, "objcallv4" },
+ { kOpPut, "put" },
+ { kOpPutChunk, "putchunk" },
+ { kOpDeleteChunk, "deletechunk" },
+ { kOpGet, "get" },
+ { kOpSet, "set" },
+ { kOpGetMovieProp, "getmovieprop" },
+ { kOpSetMovieProp, "setmovieprop" },
+ { kOpGetObjProp, "getobjprop" },
+ { kOpSetObjProp, "setobjprop" },
+ { kOpTellCall, "tellcall" },
+ { kOpPeek, "peek" },
+ { kOpPop, "pop" },
+ { kOpTheBuiltin, "thebuiltin" },
+ { kOpObjCall, "objcall" },
+ { kOpPushChunkVarRef, "pushchunkvarref" },
+ { kOpPushInt16, "pushint16" },
+ { kOpPushInt32, "pushint32" },
+ { kOpGetChainedProp, "getchainedprop" },
+ { kOpPushFloat32, "pushfloat32" },
+ { kOpGetTopLevelProp, "gettoplevelprop" },
+ { kOpNewObj, "newobj" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::binaryOpNames = toMap<unsigned int, Common::String>({
+ { kOpMul, "*" },
+ { kOpAdd, "+" },
+ { kOpSub, "-" },
+ { kOpDiv, "/" },
+ { kOpMod, "mod" },
+ { kOpJoinStr, "&" },
+ { kOpJoinPadStr, "&&" },
+ { kOpLt, "<" },
+ { kOpLtEq, "<=" },
+ { kOpNtEq, "<>" },
+ { kOpEq, "=" },
+ { kOpGt, ">" },
+ { kOpGtEq, ">=" },
+ { kOpAnd, "and" },
+ { kOpOr, "or" },
+ { kOpContainsStr, "contains" },
+ { kOpContains0Str, "starts" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::chunkTypeNames = toMap<unsigned int, Common::String>({
+ { kChunkChar, "char" },
+ { kChunkWord, "word" },
+ { kChunkItem, "item" },
+ { kChunkLine, "line" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::putTypeNames = toMap<unsigned int, Common::String>({
+ { kPutInto, "into" },
+ { kPutAfter, "after" },
+ { kPutBefore, "before" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::moviePropertyNames = toMap<unsigned int, Common::String>({
+ { 0x00, "floatPrecision" },
+ { 0x01, "mouseDownScript" },
+ { 0x02, "mouseUpScript" },
+ { 0x03, "keyDownScript" },
+ { 0x04, "keyUpScript" },
+ { 0x05, "timeoutScript" },
+ { 0x06, "short time" },
+ { 0x07, "abbr time" },
+ { 0x08, "long time" },
+ { 0x09, "short date" },
+ { 0x0a, "abbr date" },
+ { 0x0b, "long date" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::whenEventNames = toMap<unsigned int, Common::String>({
+ { 0x01, "mouseDown" },
+ { 0x02, "mouseUp" },
+ { 0x03, "keyDown" },
+ { 0x04, "keyUp" },
+ { 0x05, "timeOut" },
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::menuPropertyNames = toMap<unsigned int, Common::String>({
+ { 0x01, "name" },
+ { 0x02, "number of menuItems" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::menuItemPropertyNames = toMap<unsigned int, Common::String>({
+ { 0x01, "name" },
+ { 0x02, "checkMark" },
+ { 0x03, "enabled" },
+ { 0x04, "script" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::soundPropertyNames = toMap<unsigned int, Common::String>({
+ { 0x01, "volume" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::spritePropertyNames = toMap<unsigned int, Common::String>({
+ { 0x01, "type" },
+ { 0x02, "backColor" },
+ { 0x03, "bottom" },
+ { 0x04, "castNum" },
+ { 0x05, "constraint" },
+ { 0x06, "cursor" },
+ { 0x07, "foreColor" },
+ { 0x08, "height" },
+ { 0x09, "immediate" },
+ { 0x0a, "ink" },
+ { 0x0b, "left" },
+ { 0x0c, "lineSize" },
+ { 0x0d, "locH" },
+ { 0x0e, "locV" },
+ { 0x0f, "movieRate" },
+ { 0x10, "movieTime" },
+ { 0x11, "pattern" },
+ { 0x12, "puppet" },
+ { 0x13, "right" },
+ { 0x14, "startTime" },
+ { 0x15, "stopTime" },
+ { 0x16, "stretch" },
+ { 0x17, "top" },
+ { 0x18, "trails" },
+ { 0x19, "visible" },
+ { 0x1a, "volume" },
+ { 0x1b, "width" },
+ { 0x1c, "blend" },
+ { 0x1d, "scriptNum" },
+ { 0x1e, "moveableSprite" },
+ { 0x1f, "editableText" },
+ { 0x20, "scoreColor" },
+ { 0x21, "loc" },
+ { 0x22, "rect" },
+ { 0x23, "memberNum" },
+ { 0x24, "castLibNum" },
+ { 0x25, "member" },
+ { 0x26, "scriptInstanceList" },
+ { 0x27, "currentTime" },
+ { 0x28, "mostRecentCuePoint" },
+ { 0x29, "tweened" },
+ { 0x2a, "name" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::animationPropertyNames = toMap<unsigned int, Common::String>({
+ { 0x01, "beepOn" },
+ { 0x02, "buttonStyle" },
+ { 0x03, "centerStage" },
+ { 0x04, "checkBoxAccess" },
+ { 0x05, "checkboxType" },
+ { 0x06, "colorDepth" },
+ { 0x07, "colorQD" },
+ { 0x08, "exitLock" },
+ { 0x09, "fixStageSize" },
+ { 0x0a, "fullColorPermit" },
+ { 0x0b, "imageDirect" },
+ { 0x0c, "doubleClick" },
+ { 0x0d, "key" },
+ { 0x0e, "lastClick" },
+ { 0x0f, "lastEvent" },
+ { 0x10, "keyCode" },
+ { 0x11, "lastKey" },
+ { 0x12, "lastRoll"},
+ { 0x13, "timeoutLapsed" },
+ { 0x14, "multiSound" },
+ { 0x15, "pauseState" },
+ { 0x16, "quickTimePresent" },
+ { 0x17, "selEnd" },
+ { 0x18, "selStart" },
+ { 0x19, "soundEnabled" },
+ { 0x1a, "soundLevel" },
+ { 0x1b, "stageColor" },
+ // 0x1c indicates dontPassEvent was called.
+ // It doesn't seem to have a Lingo-accessible name.
+ { 0x1d, "switchColorDepth" },
+ { 0x1e, "timeoutKeyDown" },
+ { 0x1f, "timeoutLength" },
+ { 0x20, "timeoutMouse" },
+ { 0x21, "timeoutPlay" },
+ { 0x22, "timer" },
+ { 0x23, "preLoadRAM" },
+ { 0x24, "videoForWindowsPresent" },
+ { 0x25, "netPresent" },
+ { 0x26, "safePlayer" },
+ { 0x27, "soundKeepDevice" },
+ { 0x28, "soundMixMedia" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::animation2PropertyNames = toMap<unsigned int, Common::String>({
+ { 0x01, "perFrameHook" },
+ { 0x02, "number of castMembers" },
+ { 0x03, "number of menus" },
+ { 0x04, "number of castLibs" },
+ { 0x05, "number of xtras" }
+});
+
+Common::StableMap<unsigned int, Common::String> StandardNames::memberPropertyNames = toMap<unsigned int, Common::String>({
+ { 0x01, "name" },
+ { 0x02, "text" },
+ { 0x03, "textStyle" },
+ { 0x04, "textFont" },
+ { 0x05, "textHeight" },
+ { 0x06, "textAlign" },
+ { 0x07, "textSize" },
+ { 0x08, "picture" },
+ { 0x09, "hilite" },
+ { 0x0a, "number" },
+ { 0x0b, "size" },
+ { 0x0c, "loop" },
+ { 0x0d, "duration" },
+ { 0x0e, "controller" },
+ { 0x0f, "directToStage" },
+ { 0x10, "sound" },
+ { 0x11, "foreColor" },
+ { 0x12, "backColor" },
+ { 0x13, "type" }
+});
+
+Common::String StandardNames::getOpcodeName(uint8_t id) {
+ if (id >= 0x40)
+ id = 0x40 + id % 0x40;
+ auto it = opcodeNames.find(id);
+ if (it == opcodeNames.end()){
+ return Common::String::format("unk%02X" , id);
+ }
+ return it->second;
+}
+
+Common::String StandardNames::getName(const Common::StableMap<unsigned int, Common::String> &nameMap, unsigned int id) {
+ auto it = nameMap.find(id);
+ if (it == nameMap.end())
+ return "ERROR";
+ return it->second;
+}
+
+/* ScriptNames */
+
+void ScriptNames::read(Common::SeekableReadStream &stream) {
+ // Lingo scripts are always big endian regardless of file endianness
+ unknown0 = stream.readSint32BE();
+ unknown1 = stream.readSint32BE();
+ len1 = stream.readUint32BE();
+ len2 = stream.readUint32BE();
+ namesOffset = stream.readUint16BE();
+ namesCount = stream.readUint16BE();
+
+ stream.seek(namesOffset);
+ names.resize(namesCount);
+ for (auto &name : names) {
+ name = stream.readPascalString();
+ }
+}
+
+bool ScriptNames::validName(int id) const {
+ return -1 < id && (unsigned)id < names.size();
+}
+
+Common::String ScriptNames::getName(int id) const {
+ if (validName(id))
+ return names[id];
+ return Common::String::format("UNKNOWN_NAME_%d", id);
+}
+
+}
diff --git a/engines/director/lingo/lingodec/names.h b/engines/director/lingo/lingodec/names.h
new file mode 100644
index 00000000000..ae6226de5e0
--- /dev/null
+++ b/engines/director/lingo/lingodec/names.h
@@ -0,0 +1,63 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_NAMES_H
+#define LINGODEC_NAMES_H
+
+#include "common/array.h"
+#include "common/stablemap.h"
+
+namespace Common {
+class SeekableReadStream;
+class String;
+}
+
+namespace LingoDec {
+
+/* StandardNames */
+
+struct StandardNames {
+ static Common::StableMap<unsigned int, Common::String> opcodeNames;
+ static Common::StableMap<unsigned int, Common::String> binaryOpNames;
+ static Common::StableMap<unsigned int, Common::String> chunkTypeNames;
+ static Common::StableMap<unsigned int, Common::String> putTypeNames;
+ static Common::StableMap<unsigned int, Common::String> moviePropertyNames;
+ static Common::StableMap<unsigned int, Common::String> whenEventNames;
+ static Common::StableMap<unsigned int, Common::String> timeNames;
+ static Common::StableMap<unsigned int, Common::String> menuPropertyNames;
+ static Common::StableMap<unsigned int, Common::String> menuItemPropertyNames;
+ static Common::StableMap<unsigned int, Common::String> soundPropertyNames;
+ static Common::StableMap<unsigned int, Common::String> spritePropertyNames;
+ static Common::StableMap<unsigned int, Common::String> animationPropertyNames;
+ static Common::StableMap<unsigned int, Common::String> animation2PropertyNames;
+ static Common::StableMap<unsigned int, Common::String> memberPropertyNames;
+
+ static Common::String getOpcodeName(uint8_t id);
+ static Common::String getName(const Common::StableMap<unsigned int, Common::String> &nameMap, unsigned int id);
+};
+
+/* ScriptNames */
+
+struct ScriptNames {
+ int32_t unknown0;
+ int32_t unknown1;
+ uint32_t len1;
+ uint32_t len2;
+ uint16_t namesOffset;
+ uint16_t namesCount;
+ Common::Array<Common::String> names;
+
+ unsigned int version;
+
+ ScriptNames(unsigned int version) : version(version) {}
+ void read(Common::SeekableReadStream &stream);
+ bool validName(int id) const;
+ Common::String getName(int id) const;
+};
+
+} // namespace LingoDec
+
+#endif // LINGODEC_NAMES_H
diff --git a/engines/director/lingo/lingodec/resolver.h b/engines/director/lingo/lingodec/resolver.h
new file mode 100644
index 00000000000..2bdbdfe0fe3
--- /dev/null
+++ b/engines/director/lingo/lingodec/resolver.h
@@ -0,0 +1,25 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_RESOLVER_H
+#define LINGODEC_RESOLVER_H
+
+namespace LingoDec {
+
+struct Script;
+struct ScriptNames;
+
+class ChunkResolver {
+public:
+ ChunkResolver() {}
+ virtual ~ChunkResolver() {}
+ virtual Script *getScript(int32_t id) = 0;
+ virtual ScriptNames *getScriptNames(int32_t id) = 0;
+};
+
+}
+
+#endif // LINGODEC_RESOLVER_H
diff --git a/engines/director/lingo/lingodec/script.cpp b/engines/director/lingo/lingodec/script.cpp
new file mode 100644
index 00000000000..ffbf9d64804
--- /dev/null
+++ b/engines/director/lingo/lingodec/script.cpp
@@ -0,0 +1,255 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include "common/stream.h"
+#include "./ast.h"
+#include "./codewriter.h"
+#include "./context.h"
+#include "./handler.h"
+#include "./script.h"
+
+double readAppleFloat80(void *ptr);
+
+namespace LingoDec {
+
+/* Script */
+
+Script::Script(unsigned int version) :
+ version(version),
+ context(nullptr) {}
+
+Script::~Script() = default;
+
+void Script::read(Common::SeekableReadStream &stream) {
+ // Lingo scripts are always big endian regardless of file endianness
+ stream.seek(8);
+ /* 8 */ totalLength = stream.readUint32BE();
+ /* 12 */ totalLength2 = stream.readUint32BE();
+ /* 16 */ headerLength = stream.readUint16BE();
+ /* 18 */ scriptNumber = stream.readUint16BE();
+ /* 20 */ unk20 = stream.readSint16BE();
+ /* 22 */ parentNumber = stream.readSint16BE();
+
+ stream.seek(38);
+ /* 38 */ scriptFlags = stream.readUint32BE();
+ /* 42 */ unk42 = stream.readSint16BE();
+ /* 44 */ castID = stream.readSint32BE();
+ /* 48 */ factoryNameID = stream.readSint16BE();
+ /* 50 */ handlerVectorsCount = stream.readUint16BE();
+ /* 52 */ handlerVectorsOffset = stream.readUint32BE();
+ /* 56 */ handlerVectorsSize = stream.readUint32BE();
+ /* 60 */ propertiesCount = stream.readUint16BE();
+ /* 62 */ propertiesOffset = stream.readUint32BE();
+ /* 66 */ globalsCount = stream.readUint16BE();
+ /* 68 */ globalsOffset = stream.readUint32BE();
+ /* 72 */ handlersCount = stream.readUint16BE();
+ /* 74 */ handlersOffset = stream.readUint32BE();
+ /* 78 */ literalsCount = stream.readUint16BE();
+ /* 80 */ literalsOffset = stream.readUint32BE();
+ /* 84 */ literalsDataCount = stream.readUint32BE();
+ /* 88 */ literalsDataOffset = stream.readUint32BE();
+
+ propertyNameIDs = readVarnamesTable(stream, propertiesCount, propertiesOffset);
+ globalNameIDs = readVarnamesTable(stream, globalsCount, globalsOffset);
+
+ handlers.resize(handlersCount);
+ for (auto &handler : handlers) {
+ handler.setScript(this);
+ }
+ if ((scriptFlags & LingoDec::kScriptFlagEventScript) && handlersCount > 0) {
+ handlers[0].isGenericEvent = true;
+ }
+
+ stream.seek(handlersOffset);
+ for (auto &handler : handlers) {
+ handler.readRecord(stream);
+ }
+ for (auto &handler : handlers) {
+ handler.readData(stream);
+ }
+
+ stream.seek(literalsOffset);
+ literals.resize(literalsCount);
+ for (auto &literal : literals) {
+ literal.readRecord(stream, version);
+ }
+ for (auto &literal : literals) {
+ literal.readData(stream, literalsDataOffset);
+ }
+}
+
+Common::Array<int16_t> Script::readVarnamesTable(Common::SeekableReadStream &stream, uint16_t count, uint32_t offset) {
+ stream.seek(offset);
+ Common::Array<int16_t> nameIDs(count);
+ for (uint16_t i = 0; i < count; i++) {
+ nameIDs[i] = stream.readSint16BE();
+ }
+ return nameIDs;
+}
+
+bool Script::validName(int id) const {
+ return context->validName(id);
+}
+
+Common::String Script::getName(int id) const {
+ return context->getName(id);
+}
+
+void Script::setContext(ScriptContext *ctx) {
+ this->context = ctx;
+ if (factoryNameID != -1) {
+ factoryName = getName(factoryNameID);
+ }
+ for (auto nameID : propertyNameIDs) {
+ if (validName(nameID)) {
+ Common::String name = getName(nameID);
+ if (isFactory() && name == "me")
+ continue;
+ propertyNames.push_back(name);
+ }
+ }
+ for (auto nameID : globalNameIDs) {
+ if (validName(nameID)) {
+ globalNames.push_back(getName(nameID));
+ }
+ }
+ for (auto &handler : handlers) {
+ handler.readNames();
+ }
+}
+
+void Script::parse() {
+ for (auto &handler : handlers) {
+ handler.parse();
+ }
+}
+
+void Script::writeVarDeclarations(CodeWriter &code) const {
+ if (!isFactory()) {
+ if (propertyNames.size() > 0) {
+ code.write("property ");
+ for (size_t i = 0; i < propertyNames.size(); i++) {
+ if (i > 0)
+ code.write(", ");
+ code.write(propertyNames[i]);
+ }
+ code.writeLine();
+ }
+ }
+ if (globalNames.size() > 0) {
+ code.write("global ");
+ for (size_t i = 0; i < globalNames.size(); i++) {
+ if (i > 0)
+ code.write(", ");
+ code.write(globalNames[i]);
+ }
+ code.writeLine();
+ }
+}
+
+void Script::writeScriptText(CodeWriter &code, bool dotSyntax) const {
+ size_t origSize = code.size();
+ writeVarDeclarations(code);
+ if (isFactory()) {
+ if (code.size() != origSize) {
+ code.writeLine();
+ }
+ code.write("factory ");
+ code.writeLine(factoryName);
+ }
+ for (size_t i = 0; i < handlers.size(); i++) {
+ if ((!isFactory() || i > 0) && code.size() != origSize) {
+ code.writeLine();
+ }
+ handlers[i].ast.writeScriptText(code, dotSyntax, false);
+ }
+ for (auto factory : factories) {
+ if (code.size() != origSize) {
+ code.writeLine();
+ }
+ factory->writeScriptText(code, dotSyntax);
+ }
+}
+
+Common::String Script::scriptText(const char *lineEnding, bool dotSyntax) const {
+ CodeWriter code(lineEnding);
+ writeScriptText(code, dotSyntax);
+ return code.str();
+}
+
+void Script::writeBytecodeText(CodeWriter &code, bool dotSyntax) const {
+ size_t origSize = code.size();
+ writeVarDeclarations(code);
+ if (isFactory()) {
+ if (code.size() != origSize) {
+ code.writeLine();
+ }
+ code.write("factory ");
+ code.writeLine(factoryName);
+ }
+ for (size_t i = 0; i < handlers.size(); i++) {
+ if ((!isFactory() || i > 0) && code.size() != origSize) {
+ code.writeLine();
+ }
+ handlers[i].writeBytecodeText(code, dotSyntax);
+ }
+ for (auto factory : factories) {
+ if (code.size() != origSize) {
+ code.writeLine();
+ }
+ factory->writeBytecodeText(code, dotSyntax);
+ }
+}
+
+Common::String Script::bytecodeText(const char *lineEnding, bool dotSyntax) const {
+ CodeWriter code(lineEnding);
+ writeBytecodeText(code, dotSyntax);
+ return code.str();
+}
+
+bool Script::isFactory() const {
+ return (scriptFlags & LingoDec::kScriptFlagFactoryDef);
+}
+
+/* LiteralStore */
+
+void LiteralStore::readRecord(Common::SeekableReadStream &stream, int version) {
+ if (version >= 500)
+ type = static_cast<LiteralType>(stream.readUint32BE());
+ else
+ type = static_cast<LiteralType>(stream.readUint16BE());
+ offset = stream.readUint32BE();
+}
+
+void LiteralStore::readData(Common::SeekableReadStream &stream, uint32_t startOffset) {
+ if (type == kLiteralInt) {
+ value = Common::SharedPtr<LingoDec::Datum>(new LingoDec::Datum((int)offset));
+ } else {
+ stream.seek(startOffset + offset);
+ auto length = stream.readUint32BE();
+ if (type == kLiteralString) {
+ char *buf = new char[length];
+ stream.read(buf, length - 1);
+ buf[length - 1] = '\0';
+ value = Common::SharedPtr<LingoDec::Datum>(new LingoDec::Datum(LingoDec::kDatumString, Common::String(buf)));
+ delete[] buf;
+ } else if (type == kLiteralFloat) {
+ double floatVal = 0.0;
+ if (length == 8) {
+ floatVal = stream.readDoubleBE();
+ } else if (length == 10) {
+ uint8_t buf[10];
+ stream.read(buf, 10);
+ floatVal = readAppleFloat80(buf);
+ }
+ value = Common::SharedPtr<LingoDec::Datum>(new LingoDec::Datum(floatVal));
+ } else {
+ value = Common::SharedPtr<LingoDec::Datum>(new LingoDec::Datum());
+ }
+ }
+}
+
+} // namespace LingoDec
diff --git a/engines/director/lingo/lingodec/script.h b/engines/director/lingo/lingodec/script.h
new file mode 100644
index 00000000000..e53d53bf591
--- /dev/null
+++ b/engines/director/lingo/lingodec/script.h
@@ -0,0 +1,96 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LINGODEC_SCRIPT_H
+#define LINGODEC_SCRIPT_H
+
+#include "common/ptr.h"
+#include "common/str.h"
+#include "./enums.h"
+
+namespace Common {
+class ReadStream;
+}
+
+namespace LingoDec {
+
+class CodeWriter;
+struct Datum;
+struct Handler;
+struct ScriptContext;
+
+/* LiteralStore */
+
+struct LiteralStore {
+ LiteralType type;
+ uint32_t offset;
+ Common::SharedPtr<Datum> value;
+
+ void readRecord(Common::SeekableReadStream &stream, int version);
+ void readData(Common::SeekableReadStream &stream, uint32_t startOffset);
+};
+
+/* Script */
+
+struct Script {
+ /* 8 */ uint32_t totalLength;
+ /* 12 */ uint32_t totalLength2;
+ /* 16 */ uint16_t headerLength;
+ /* 18 */ uint16_t scriptNumber;
+ /* 20 */ int16_t unk20;
+ /* 22 */ int16_t parentNumber;
+
+ /* 38 */ uint32_t scriptFlags;
+ /* 42 */ int16_t unk42;
+ /* 44 */ int32_t castID;
+ /* 48 */ int16_t factoryNameID;
+ /* 50 */ uint16_t handlerVectorsCount;
+ /* 52 */ uint32_t handlerVectorsOffset;
+ /* 56 */ uint32_t handlerVectorsSize;
+ /* 60 */ uint16_t propertiesCount;
+ /* 62 */ uint32_t propertiesOffset;
+ /* 66 */ uint16_t globalsCount;
+ /* 68 */ uint32_t globalsOffset;
+ /* 72 */ uint16_t handlersCount;
+ /* 74 */ uint32_t handlersOffset;
+ /* 78 */ uint16_t literalsCount;
+ /* 80 */ uint32_t literalsOffset;
+ /* 84 */ uint32_t literalsDataCount;
+ /* 88 */ uint32_t literalsDataOffset;
+
+ Common::Array<int16_t> propertyNameIDs;
+ Common::Array<int16_t> globalNameIDs;
+
+ Common::String factoryName;
+ Common::Array<Common::String> propertyNames;
+ Common::Array<Common::String> globalNames;
+ Common::Array<Handler> handlers;
+ Common::Array<LiteralStore> literals;
+ Common::Array<Script *> factories;
+
+ unsigned int version;
+ ScriptContext *context;
+
+ Script(unsigned int version);
+ ~Script();
+ void read(Common::SeekableReadStream &stream);
+ Common::Array<int16_t> readVarnamesTable(Common::SeekableReadStream &stream, uint16_t count, uint32_t offset);
+ bool validName(int id) const;
+ Common::String getName(int id) const;
+ void setContext(ScriptContext *ctx);
+ void parse();
+ void writeVarDeclarations(CodeWriter &code) const;
+ void writeScriptText(CodeWriter &code, bool dotSyntax) const;
+ Common::String scriptText(const char *lineEnding, bool dotSyntax) const;
+ void writeBytecodeText(CodeWriter &code, bool dotSyntax) const;
+ Common::String bytecodeText(const char *lineEnding, bool dotSyntax) const;
+
+ bool isFactory() const;
+};
+
+} // namespace LingoDec
+
+#endif // LINGODEC_SCRIPT_H
diff --git a/engines/director/module.mk b/engines/director/module.mk
index 0019d1e0500..36d494a4411 100644
--- a/engines/director/module.mk
+++ b/engines/director/module.mk
@@ -53,6 +53,12 @@ MODULE_OBJS = \
lingo/lingo-preprocessor.o \
lingo/lingo-the.o \
lingo/lingo-utils.o \
+ lingo/lingodec/ast.o \
+ lingo/lingodec/context.o \
+ lingo/lingodec/codewriter.o \
+ lingo/lingodec/handler.o \
+ lingo/lingodec/names.o \
+ lingo/lingodec/script.o \
lingo/xlibs/aiff.o \
lingo/xlibs/applecdxobj.o \
lingo/xlibs/askuser.o \
diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 7d16b72f052..1e673070114 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -607,12 +607,23 @@ ScriptContext *Movie::getScriptContext(ScriptType type, CastMemberID id) {
Symbol Movie::getHandler(const Common::String &name) {
for (auto &it : _casts) {
+ if (name == "waitSignal") {
+ for (auto &i : it._value->_lingoArchive->functionHandlers)
+ debug("%s", i._key.c_str());
+ }
+
if (it._value->_lingoArchive->functionHandlers.contains(name))
return it._value->_lingoArchive->functionHandlers[name];
}
- if (_sharedCast && _sharedCast->_lingoArchive->functionHandlers.contains(name))
+ if (_sharedCast && _sharedCast->_lingoArchive->functionHandlers.contains(name)) {
+ if (name == "waitSignal") {
+ for (auto &i : _sharedCast->_lingoArchive->functionHandlers)
+ debug("sh: %s", i._key.c_str());
+ }
+
return _sharedCast->_lingoArchive->functionHandlers[name];
+ }
return Symbol();
}
diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index 1d43798e6c4..c301f2524c8 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -1651,6 +1651,8 @@ void DirectorEngine::delayMillis(uint32 delay) {
_system->delayMillis(delay);
}
+} // End of namespace Director
+
double readAppleFloat80(byte *ptr) {
// Floats in an "80 bit IEEE Standard 754 floating
// point number (Standard Apple Numeric Environment [SANE] data type
@@ -1661,5 +1663,3 @@ double readAppleFloat80(byte *ptr) {
return Common::XPFloat(signAndExponent, mantissa).toDouble(Common::XPFloat::kSemanticsSANE);
}
-
-} // End of namespace Director
diff --git a/engines/director/util.h b/engines/director/util.h
index 59f4f8d8e91..82acc17127a 100644
--- a/engines/director/util.h
+++ b/engines/director/util.h
@@ -70,8 +70,6 @@ Common::Path dumpFactoryName(const char *prefix, const char *name, const char *e
bool isButtonSprite(SpriteType spriteType);
-double readAppleFloat80(byte *ptr);
-
class RandomState {
public:
uint32 _seed;
@@ -139,4 +137,6 @@ inline void lerpPalette(byte *target, byte *palA, int palALength, byte *palB, in
} // End of namespace Director
+double readAppleFloat80(byte *ptr);
+
#endif
More information about the Scummvm-git-logs
mailing list