/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ // Heavily inspired by hoc // Copyright (C) AT&T 1995 // All Rights Reserved // // Permission to use, copy, modify, and distribute this software and // its documentation for any purpose and without fee is hereby // granted, provided that the above copyright notice appear in all // copies and that both that the copyright notice and this // permission notice and warranty disclaimer appear in supporting // documentation, and that the name of AT&T or any of its entities // not be used in advertising or publicity pertaining to // distribution of the software without specific, written prior // permission. // // AT&T DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, // INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. // IN NO EVENT SHALL AT&T OR ANY OF ITS ENTITIES BE LIABLE FOR ANY // SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, // ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF // THIS SOFTWARE. #include "common/endian.h" #include "director/director.h" #include "director/movie.h" #include "director/lingo/lingo.h" #include "director/lingo/lingo-ast.h" #include "director/lingo/lingo-code.h" #include "director/lingo/lingo-codegen.h" #include "director/lingo/lingo-object.h" #include "director/lingo/lingo-the.h" namespace Director { #define COMPILE(node) \ { \ bool refModeStore = _refMode; \ _refMode = false; \ bool success = (node)->accept(this); \ _refMode = refModeStore; \ if (!success) \ return false; \ } #define COMPILE_REF(node) \ { \ bool refModeStore = _refMode; \ _refMode = true; \ bool success = (node)->accept(this); \ _refMode = refModeStore; \ if (!success) \ return false; \ } #define COMPILE_LIST(list) \ { \ bool refModeStore = _refMode; \ _refMode = false; \ bool success = true; \ for (uint i = 0; i < (list)->size(); i++) { \ success = (*(list))[i]->accept(this); \ if (!success) \ break; \ } \ _refMode = refModeStore; \ if (!success) \ return false; \ } class NodeStore { public: explicit NodeStore(Node *node) : _node(node) { _node->startOffset = g_lingo->_compiler->_currentAssembly->size() - 1; } ~NodeStore() { _node->endOffset = g_lingo->_compiler->_currentAssembly->size() - 1; } private: Node *_node = nullptr; }; LingoCompiler::LingoCompiler() { _assemblyAST = nullptr; _assemblyArchive = nullptr; _currentAssembly = nullptr; _assemblyContext = nullptr; _assemblyId = -1; _indef = false; _methodVars = nullptr; _linenumber = _colnumber = _bytenumber = 0; _lines[0] = _lines[1] = _lines[2] = nullptr; _inFactory = false; _currentLoop = nullptr; _refMode = false; _hadError = false; } ScriptContext *LingoCompiler::compileAnonymous(const Common::U32String &code, uint32 preprocFlags) { debugC(1, kDebugCompile, "Compiling anonymous lingo\n" "***********\n%s\n\n***********", code.encode().c_str()); return compileLingo(code, nullptr, kNoneScript, CastMemberID(0, 0), "[anonymous]", true, preprocFlags); } ScriptContext *LingoCompiler::compileLingo(const Common::U32String &code, LingoArchive *archive, ScriptType type, CastMemberID id, const Common::String &scriptName, bool anonymous, uint32 preprocFlags) { _assemblyArchive = archive; _assemblyAST = nullptr; _assemblyId = id.member; ScriptContext *mainContext = _assemblyContext = new ScriptContext(scriptName, type, _assemblyId, id.castLib); _currentAssembly = new ScriptData; _methodVars = new VarTypeHash; _linenumber = _colnumber = 1; _hadError = false; // Preprocess the code for ease of the parser Common::U32String codePrep = codePreprocessor(code, archive, type, id, preprocFlags); // Search the methods mainContext->_methodNames = prescanMethods(codePrep); Common::String codeNorm = codePrep.encode(Common::kUtf8); const char *utf8Code = codeNorm.c_str(); // Parse the Lingo and build an AST parse(utf8Code); // If it doesn't work, and we have kLPPTrimGarbage enabled, // have another try with the input trimmed to the last valid character. if (!_assemblyAST && (preprocFlags & kLPPTrimGarbage)) { delete _assemblyContext; delete _currentAssembly; delete _methodVars; _assemblyId = id.member; mainContext = _assemblyContext = new ScriptContext(scriptName, type, _assemblyId); _currentAssembly = new ScriptData; _methodVars = new VarTypeHash; mainContext->_methodNames = prescanMethods(codePrep); _linenumber = _colnumber = 1; _hadError = false; codeNorm = codeNorm.substr(0, _bytenumber - 1) + "\n"; utf8Code = codeNorm.c_str(); parse(utf8Code); } if (!_assemblyAST) { delete _assemblyContext; delete _currentAssembly; delete _methodVars; _assemblyId = -1; return nullptr; } // Generate bytecode if (!_assemblyAST->accept(this)) { _hadError = true; delete _assemblyContext; delete _currentAssembly; delete _methodVars; _assemblyId = -1; return nullptr; } // for D4 and above, there usually won't be any code left. // all scoped methods will be defined and stored by the code parser // however D3 and below allow scopeless functions! // and these can show up in D4 when imported from other movies if (!_currentAssembly->empty()) { // end of script, add a c_procret so stack frames work as expected code1(LC::c_procret); code1(STOP); if (debugChannelSet(3, kDebugCompile)) { if (_currentAssembly->size() && !_hadError) Common::hexdump((byte *)&_currentAssembly->front(), _currentAssembly->size() * sizeof(inst)); debugC(2, kDebugCompile, ""); uint pc = 0; while (pc < _currentAssembly->size()) { uint spc = pc; Common::String instr = g_lingo->decodeInstruction(_currentAssembly, pc, &pc); debugC(2, kDebugCompile, "[%5d] %s", spc, instr.c_str()); } debugC(2, kDebugCompile, ""); } Symbol currentFunc; currentFunc.type = HANDLER; currentFunc.u.defn = _currentAssembly; Common::String typeStr = Common::String(scriptType2str(type)); currentFunc.name = new Common::String("scummvm_" + typeStr + "_" + _assemblyContext->getName()); currentFunc.ctx = _assemblyContext; currentFunc.anonymous = anonymous; Common::Array *argNames = new Common::Array; Common::Array *varNames = new Common::Array; for (auto &it : *_methodVars) { if (it._value == kVarLocal) varNames->push_back(Common::String(it._key)); } if (debugChannelSet(1, kDebugCompile)) { debug("Function vars"); debugN(" Args: "); for (uint i = 0; i < argNames->size(); i++) { debugN("%s, ", (*argNames)[i].c_str()); } debugN("\n"); debugN(" Local vars: "); for (uint i = 0; i < varNames->size(); i++) { debugN("%s, ", (*varNames)[i].c_str()); } debugN("\n"); } currentFunc.argNames = argNames; currentFunc.varNames = varNames; _assemblyContext->_functionHandlers[*currentFunc.name] = currentFunc; _assemblyContext->_eventHandlers[kEventGeneric] = currentFunc; } else { delete _currentAssembly; } // Register this context's functions with the containing archive. if (_assemblyArchive) { for (auto &it : _assemblyContext->_functionHandlers) { if (!_assemblyArchive->functionHandlers.contains(it._key)) { _assemblyArchive->functionHandlers[it._key] = it._value; } } } delete _methodVars; _methodVars = nullptr; _currentAssembly = nullptr; if (debugChannelSet(-1, kDebugImGui)) { _assemblyContext->_assemblyAST = _assemblyAST; } else { _assemblyAST.reset(); } _assemblyAST = nullptr; _assemblyContext = nullptr; _assemblyArchive = nullptr; _assemblyId = -1; return mainContext; } int LingoCompiler::codeString(const char *str) { int numInsts = calcStringAlignment(str); // Where we copy the string over int pos = _currentAssembly->size(); // Allocate needed space in script for (int i = 0; i < numInsts; i++) _currentAssembly->push_back(0); byte *dst = (byte *)&_currentAssembly->front() + pos * sizeof(inst); memcpy(dst, str, strlen(str) + 1); return _currentAssembly->size(); } int LingoCompiler::codeFloat(double f) { int numInsts = calcCodeAlignment(sizeof(double)); // Where we copy the string over int pos = _currentAssembly->size(); // Allocate needed space in script for (int i = 0; i < numInsts; i++) _currentAssembly->push_back(0); double *dst = (double *)((byte *)&_currentAssembly->front() + pos * sizeof(inst)); *dst = f; return _currentAssembly->size(); } int LingoCompiler::codeInt(int val) { inst i = nullptr; WRITE_UINT32(&i, val); code1(i); return _currentAssembly->size(); } int LingoCompiler::codeCmd(const Common::String &s, int numpar) { int ret = code1(LC::c_callcmd); codeString(s.c_str()); inst num = nullptr; WRITE_UINT32(&num, numpar); code1(num); return ret; } int LingoCompiler::codeFunc(const Common::String &s, int numpar) { int ret = code1(LC::c_callfunc); codeString(s.c_str()); inst num = nullptr; WRITE_UINT32(&num, numpar); code1(num); return ret; } void LingoCompiler::codeVarSet(const Common::String &name) { registerMethodVar(name); codeVarRef(name); code1(LC::c_assign); } void LingoCompiler::codeVarRef(const Common::String &name) { VarType type; if (_methodVars->contains(name)) { type = (*_methodVars)[name]; } else { type = kVarGeneric; } switch (type) { case kVarGeneric: code1(LC::c_varrefpush); break; case kVarGlobal: code1(LC::c_globalrefpush); break; case kVarLocal: case kVarArgument: code1(LC::c_localrefpush); break; case kVarProperty: case kVarInstance: code1(LC::c_proprefpush); break; } codeString(name.c_str()); } void LingoCompiler::codeVarGet(const Common::String &name) { VarType type; if (_methodVars->contains(name)) { type = (*_methodVars)[name]; } else { if (_indef) warning("LingoCompiler::codeVarGet: var %s referenced before definition", name.c_str()); type = kVarGeneric; } switch (type) { case kVarGeneric: code1(LC::c_varpush); break; case kVarGlobal: code1(LC::c_globalpush); break; case kVarLocal: case kVarArgument: code1(LC::c_localpush); break; case kVarProperty: case kVarInstance: code1(LC::c_proppush); break; } codeString(name.c_str()); } void LingoCompiler::registerMethodVar(const Common::String &name, VarType type) { if (!_methodVars->contains(name)) { if (_indef && type == kVarGeneric) { type = kVarLocal; } (*_methodVars)[name] = type; if (type == kVarProperty || type == kVarInstance) { if (!_assemblyContext->hasProp(name)) _assemblyContext->setProp(name, Datum(), true); } else if (type == kVarGlobal) { if (!g_lingo->_globalvars.contains(name)) g_lingo->_globalvars[name] = Datum(); } } } void LingoCompiler::registerFactory(Common::String &name) { _assemblyContext->setName(name); _assemblyContext->setFactory(true); g_lingo->_globalvars[name] = _assemblyContext; // Add the factory to the list in the archive if (_assemblyArchive) { if (!_assemblyArchive->factoryContexts.contains(_assemblyId)) { _assemblyArchive->factoryContexts[_assemblyId] = new Common::HashMap(); } if (!_assemblyArchive->factoryContexts[_assemblyId]->contains(name)) { _assemblyContext->incRefCount(); (*_assemblyArchive->factoryContexts[_assemblyId])[name] = _assemblyContext; } } } void LingoCompiler::updateLoopJumps(uint nextTargetPos, uint exitTargetPos) { if (!_currentLoop) return; for (uint i = 0; i < _currentLoop->nextRepeats.size(); i++) { uint nextRepeatPos = _currentLoop->nextRepeats[i]; inst jmpOffset = nullptr; WRITE_UINT32(&jmpOffset, nextTargetPos - nextRepeatPos); (*_currentAssembly)[nextRepeatPos + 1] = jmpOffset; } for (uint i = 0; i < _currentLoop->exitRepeats.size(); i++) { uint exitRepeatPos = _currentLoop->exitRepeats[i]; inst jmpOffset = nullptr; WRITE_UINT32(&jmpOffset, exitTargetPos - exitRepeatPos); (*_currentAssembly)[exitRepeatPos + 1] = jmpOffset; } } /* ScriptNode */ bool LingoCompiler::visitScriptNode(ScriptNode *node) { NodeStore store(node); COMPILE_LIST(node->children); return true; } /* FactoryNode */ bool LingoCompiler::visitFactoryNode(FactoryNode *node) { NodeStore store(node); _inFactory = true; ScriptContext *mainContext = _assemblyContext; _assemblyContext = new ScriptContext(*node->name, mainContext->_scriptType, mainContext->_id); COMPILE_LIST(node->methods); registerFactory(*node->name); _inFactory = false; _assemblyContext = mainContext; return true; } /* HandlerNode */ bool LingoCompiler::visitHandlerNode(HandlerNode *node) { NodeStore store(node); _indef = true; ScriptData *mainAssembly = _currentAssembly; _currentAssembly = new ScriptData; VarTypeHash *mainMethodVars = _methodVars; _methodVars = new VarTypeHash; if (_inFactory) { registerMethodVar("me", kVarArgument); } for (uint i = 0; i < node->args->size(); i++) { registerMethodVar(*(*node->args)[i], kVarArgument); } for (auto &i : *mainMethodVars) { if (i._value == kVarGlobal) registerMethodVar(i._key, kVarGlobal); } for (uint32 i = 1; i <= _assemblyContext->getPropCount(); i++) { registerMethodVar(_assemblyContext->getPropAt(i), _inFactory ? kVarInstance : kVarProperty); } COMPILE_LIST(node->stmts); code1(LC::c_procret); if (debugChannelSet(1, kDebugCompile)) debug("define handler \"%s\" (len: %d)", node->name->c_str(), _currentAssembly->size() - 1); Common::Array *argNames = new Common::Array; if (_inFactory) { argNames->push_back("me"); } for (uint i = 0; i < node->args->size(); i++) { argNames->push_back(Common::String((*node->args)[i]->c_str())); } Common::Array *varNames = new Common::Array; for (auto &it : *_methodVars) { if (it._value == kVarLocal) varNames->push_back(Common::String(it._key)); } if (debugChannelSet(1, kDebugCompile)) { debug("Function vars"); debugN(" Args: "); for (uint i = 0; i < argNames->size(); i++) { debugN("%s, ", (*argNames)[i].c_str()); } debugN("\n"); debugN(" Local vars: "); for (uint i = 0; i < varNames->size(); i++) { debugN("%s, ", (*varNames)[i].c_str()); } debugN("\n"); } _assemblyContext->define(*node->name, _currentAssembly, argNames, varNames); _indef = false; _currentAssembly = mainAssembly; delete _methodVars; _methodVars = mainMethodVars; return true; } /* CmdNode */ bool LingoCompiler::visitCmdNode(CmdNode *node) { NodeStore store(node); uint numargs = node->args->size(); if (node->name->equalsIgnoreCase("go") && numargs == 1 && (*node->args)[0]->type == kVarNode){ VarNode *var = static_cast((*node->args)[0]); if (var->name->equalsIgnoreCase("loop") || var->name->equalsIgnoreCase("next") || var->name->equalsIgnoreCase("previous")) { code1(LC::c_symbolpush); codeString(var->name->c_str()); codeCmd(*node->name, 1); return true; } } // `play done` compiles to `play()` if (node->name->equalsIgnoreCase("play") && numargs == 1 && (*node->args)[0]->type == kVarNode) { VarNode *var = static_cast((*node->args)[0]); if (var->name->equalsIgnoreCase("done")) { codeCmd(*node->name, 0); return true; } } if (node->name->equalsIgnoreCase("playAccel")) { for (uint i = 0; i < numargs; i++) { Node *arg = (*node->args)[i]; if (arg->type == kVarNode) { code1(LC::c_symbolpush); codeString(static_cast(arg)->name->c_str()); } else { COMPILE(arg); } } codeCmd(*node->name, numargs); return true; } if (node->name->equalsIgnoreCase("sound") && numargs >= 1 && (*node->args)[0]->type == kVarNode) { VarNode *var = static_cast((*node->args)[0]); if (var->name->equalsIgnoreCase("close") || var->name->equalsIgnoreCase("fadeIn") || var->name->equalsIgnoreCase("fadeOut") || var->name->equalsIgnoreCase("playFile") || var->name->equalsIgnoreCase("stop")) { code1(LC::c_symbolpush); codeString(var->name->c_str()); for (uint i = 1; i < numargs; i++) { COMPILE((*node->args)[i]); } codeCmd(*node->name, numargs); return true; } } if (numargs >= 1 && (*node->args)[0]->type == kVarNode) { // This could be a method call. Code the first arg as a reference. COMPILE_REF((*node->args)[0]); for (uint i = 1; i < numargs; i++) { COMPILE((*node->args)[i]); } } else { COMPILE_LIST(node->args); } // Insert current line number to our asserts if (node->name->equalsIgnoreCase("scummvmAssert") || node->name->equalsIgnoreCase("scummvmAssertEqual")) { code1(LC::c_intpush); codeInt(node->lineNumber); numargs++; } codeCmd(*node->name, numargs); return true; } /* PutIntoNode */ bool LingoCompiler::visitPutIntoNode(PutIntoNode *node) { NodeStore store(node); if (node->var->type == kVarNode) { registerMethodVar(*static_cast(node->var)->name); } COMPILE(node->val); COMPILE_REF(node->var); code1(LC::c_assign); return true; } /* PutAfterNode */ bool LingoCompiler::visitPutAfterNode(PutAfterNode *node) { NodeStore store(node); if (node->var->type == kVarNode) { registerMethodVar(*static_cast(node->var)->name); } COMPILE(node->val); COMPILE_REF(node->var); code1(LC::c_putafter); return true; } /* PutBeforeNode */ bool LingoCompiler::visitPutBeforeNode(PutBeforeNode *node) { NodeStore store(node); if (node->var->type == kVarNode) { registerMethodVar(*static_cast(node->var)->name); } COMPILE(node->val); COMPILE_REF(node->var); code1(LC::c_putbefore); return true; } /* SetNode */ int LingoCompiler::getTheFieldID(int entity, const Common::String &field, bool silent) { Common::String fieldKey = Common::String::format("%d%s", entity, field.c_str()); if (!g_lingo->_theEntityFields.contains(fieldKey)) { if (!silent) warning("BUILDBOT: LingoCompiler::getTheFieldId: Unhandled the field %s of %s", field.c_str(), g_lingo->entity2str(entity)); return -1; } return g_lingo->_theEntityFields[fieldKey]->field; } bool LingoCompiler::visitSetNode(SetNode *node) { NodeStore store(node); if (node->var->type == kTheNode) { TheNode *the = static_cast(node->var); if (g_lingo->_theEntities.contains(*the->prop) && !g_lingo->_theEntities[*the->prop]->hasId) { COMPILE(node->val); code1(LC::c_intpush); codeInt(0); // Put dummy id code1(LC::c_theentityassign); codeInt(g_lingo->_theEntities[*the->prop]->entity); codeInt(0); // No field return true; } warning("BUILDBOT: LingoCompiler:visitSetNode: Unhandled the entity '%s'", the->prop->c_str()); return false; } if (node->var->type == kTheOfNode) { TheOfNode *the = static_cast(node->var); switch (the->obj->type) { case kChunkExprNode: { int fieldId = getTheFieldID(kTheChunk, *the->prop, true); if (fieldId >= 0) { COMPILE(node->val); COMPILE_REF(the->obj); code1(LC::c_theentityassign); codeInt(kTheChunk); codeInt(fieldId); return true; } // fall back to generic object } break; case kFuncNode: { FuncNode *func = static_cast(the->obj); if (func->args->size() == 1) { if (func->name->equalsIgnoreCase("cast")) { int fieldId = getTheFieldID(kTheCast, *the->prop, true); if (fieldId >= 0) { COMPILE(node->val); COMPILE((*func->args)[0]); code1(LC::c_theentityassign); codeInt(kTheCast); codeInt(fieldId); return true; } // fall back to generic object } if (func->name->equalsIgnoreCase("field")) { int fieldId = getTheFieldID(kTheField, *the->prop, true); if (fieldId >= 0) { COMPILE(node->val); COMPILE((*func->args)[0]); code1(LC::c_theentityassign); codeInt(kTheField); codeInt(fieldId); return true; } // fall back to generic object } // window is an object and is handled by that case } } break; case kMenuNode: { MenuNode *menu = static_cast(the->obj); int fieldId = getTheFieldID(kTheMenu, *the->prop); if (fieldId < 0) return false; COMPILE(node->val); COMPILE(menu->arg); code1(LC::c_theentityassign); codeInt(kTheMenu); codeInt(fieldId); return true; } break; case kMenuItemNode: { MenuItemNode *menuItem = static_cast(the->obj); int fieldId = getTheFieldID(kTheMenuItem, *the->prop); if (fieldId < 0) return false; COMPILE(node->val); COMPILE(menuItem->arg1) COMPILE(menuItem->arg2); code1(LC::c_theentityassign); codeInt(kTheMenuItem); codeInt(fieldId); return true; } break; case kSoundNode: { SoundNode *sound = static_cast(the->obj); int fieldId = getTheFieldID(kTheSoundEntity, *the->prop); if (fieldId < 0) return false; COMPILE(node->val); COMPILE(sound->arg); code1(LC::c_theentityassign); codeInt(kTheSoundEntity); codeInt(fieldId); return true; } break; case kSpriteNode: { SpriteNode *sprite = static_cast(the->obj); int fieldId = getTheFieldID(kTheSprite, *the->prop); if (fieldId < 0) return false; COMPILE(node->val); COMPILE(sprite->arg); code1(LC::c_theentityassign); codeInt(kTheSprite); codeInt(fieldId); return true; } break; case kVarNode: { VarNode *var = static_cast(the->obj); if (the->prop->equalsIgnoreCase("number") && var->name->equalsIgnoreCase("castMembers")) { COMPILE(node->val); code1(LC::c_intpush); codeInt(0); // Put dummy id code1(LC::c_theentityassign); codeInt(kTheCastMembers); codeInt(kTheNumber); return true; } } break; default: break; } if (g_director->getVersion() >= 400) { COMPILE(node->val); COMPILE(the->obj); code1(LC::c_objectpropassign); codeString(the->prop->c_str()); return true; } warning("BUILDBOT: LingoCompiler::visitSetNode: Unhandled the field %s", the->prop->c_str()); return false; } if (node->var->type == kVarNode) { registerMethodVar(*static_cast(node->var)->name); } COMPILE(node->val); COMPILE_REF(node->var); code1(LC::c_assign); return true; } /* GlobalNode */ bool LingoCompiler::visitGlobalNode(GlobalNode *node) { NodeStore store(node); for (uint i = 0; i < node->names->size(); i++) { registerMethodVar(*(*node->names)[i], kVarGlobal); } // Before Director 4, a global statement initializes the var to 0. if (g_director->getVersion() < 400) { for (uint i = 0; i < node->names->size(); i++) { code1(LC::c_globalinit); codeString((*node->names)[i]->c_str()); } } return true; } /* PropertyNode */ bool LingoCompiler::visitPropertyNode(PropertyNode *node) { NodeStore store(node); for (uint i = 0; i < node->names->size(); i++) { registerMethodVar(*(*node->names)[i], kVarProperty); } return true; } /* InstanceNode */ bool LingoCompiler::visitInstanceNode(InstanceNode *node) { NodeStore store(node); for (uint i = 0; i < node->names->size(); i++) { registerMethodVar(*(*node->names)[i], kVarInstance); } return true; } /* IfStmtNode */ bool LingoCompiler::visitIfStmtNode(IfStmtNode *node) { NodeStore store(node); COMPILE(node->cond); uint jzPos = _currentAssembly->size(); code2(LC::c_jumpifz, nullptr); COMPILE_LIST(node->stmts); uint endPos = _currentAssembly->size(); inst jzOffset = nullptr; WRITE_UINT32(&jzOffset, endPos - jzPos); (*_currentAssembly)[jzPos + 1] = jzOffset; return true; } /* IfElseStmtNode */ bool LingoCompiler::visitIfElseStmtNode(IfElseStmtNode *node) { NodeStore store(node); COMPILE(node->cond); uint jzPos = _currentAssembly->size(); code2(LC::c_jumpifz, nullptr); COMPILE_LIST(node->stmts1); uint jmpPos = _currentAssembly->size(); code2(LC::c_jump, nullptr); uint block2StartPos = _currentAssembly->size(); COMPILE_LIST(node->stmts2); uint endPos = _currentAssembly->size(); inst jzOffset = nullptr; WRITE_UINT32(&jzOffset, block2StartPos - jzPos); (*_currentAssembly)[jzPos + 1] = jzOffset; inst jmpOffset = nullptr; WRITE_UINT32(&jmpOffset, endPos - jmpPos); (*_currentAssembly)[jmpPos + 1] = jmpOffset; return true; } /* RepeatWhileNode */ bool LingoCompiler::visitRepeatWhileNode(RepeatWhileNode *node) { NodeStore store(node); LoopNode *prevLoop = _currentLoop; _currentLoop = node; uint startPos = _currentAssembly->size(); COMPILE(node->cond); uint jzPos = _currentAssembly->size(); code2(LC::c_jumpifz, nullptr); COMPILE_LIST(node->stmts); uint jmpPos = _currentAssembly->size(); code2(LC::c_jump, nullptr); uint endPos = _currentAssembly->size(); inst jzOffset = nullptr; WRITE_UINT32(&jzOffset, endPos - jzPos); (*_currentAssembly)[jzPos + 1] = jzOffset; inst jmpOffset = nullptr; WRITE_UINT32(&jmpOffset, startPos - jmpPos); (*_currentAssembly)[jmpPos + 1] = jmpOffset; updateLoopJumps(jmpPos, endPos); _currentLoop = prevLoop; return true; } /* RepeatWithToNode */ bool LingoCompiler::visitRepeatWithToNode(RepeatWithToNode *node) { NodeStore store(node); LoopNode *prevLoop = _currentLoop; _currentLoop = node; COMPILE(node->start); codeVarSet(*node->var); uint startPos = _currentAssembly->size(); codeVarGet(*node->var); COMPILE(node->end); if (node->down) { code1(LC::c_ge); } else { code1(LC::c_le); } uint jzPos = _currentAssembly->size(); code2(LC::c_jumpifz, nullptr); COMPILE_LIST(node->stmts); uint incrementPos = _currentAssembly->size(); codeVarGet(*node->var); code1(LC::c_intpush); codeInt(1); if (node->down) { code1(LC::c_sub); } else { code1(LC::c_add); } codeVarSet(*node->var); uint jmpPos = _currentAssembly->size(); code2(LC::c_jump, nullptr); uint endPos = _currentAssembly->size(); inst jzOffset = nullptr; WRITE_UINT32(&jzOffset, endPos - jzPos); (*_currentAssembly)[jzPos + 1] = jzOffset; inst jmpOffset = nullptr; WRITE_UINT32(&jmpOffset, startPos - jmpPos); (*_currentAssembly)[jmpPos + 1] = jmpOffset; updateLoopJumps(incrementPos, endPos); _currentLoop = prevLoop; return true; } /* RepeatWithInNode */ bool LingoCompiler::visitRepeatWithInNode(RepeatWithInNode *node) { NodeStore store(node); LoopNode *prevLoop = _currentLoop; _currentLoop = node; COMPILE(node->list); code1(LC::c_stackpeek); codeInt(0); codeFunc("count", 1); code1(LC::c_intpush); // start counter codeInt(1); uint startPos = _currentAssembly->size(); code1(LC::c_stackpeek); // get counter codeInt(0); code1(LC::c_stackpeek); // get array size codeInt(2); code1(LC::c_le); uint jzPos = _currentAssembly->size(); code2(LC::c_jumpifz, nullptr); code1(LC::c_stackpeek); // get list codeInt(2); code1(LC::c_stackpeek); // get counter codeInt(1); codeFunc("getAt", 2); codeVarSet(*node->var); COMPILE_LIST(node->stmts); uint incrementPos = _currentAssembly->size(); code1(LC::c_intpush); codeInt(1); code1(LC::c_add); // Increment counter uint jmpPos = _currentAssembly->size(); code2(LC::c_jump, nullptr); uint endPos = _currentAssembly->size(); code1(LC::c_stackdrop); // remove list, size, counter codeInt(3); inst jzOffset = nullptr; WRITE_UINT32(&jzOffset, endPos - jzPos); (*_currentAssembly)[jzPos + 1] = jzOffset; inst jmpOffset = nullptr; WRITE_UINT32(&jmpOffset, startPos - jmpPos); (*_currentAssembly)[jmpPos + 1] = jmpOffset; updateLoopJumps(incrementPos, endPos); _currentLoop = prevLoop; return true; } /* NextRepeatNode */ bool LingoCompiler::visitNextRepeatNode(NextRepeatNode *node) { NodeStore store(node); if (!_currentLoop) { warning("BUILDBOT: LingoCompiler::visitNextRepeatNode: next repeat not inside repeat loop"); return false; } _currentLoop->nextRepeats.push_back(_currentAssembly->size()); code2(LC::c_jump, nullptr); return true; } /* ExitRepeatNode */ bool LingoCompiler::visitExitRepeatNode(ExitRepeatNode *node) { NodeStore store(node); if (!_currentLoop) { warning("BUILDBOT: LingoCompiler::visitExitRepeatLoop: exit repeat not inside repeat loop"); return false; } _currentLoop->exitRepeats.push_back(_currentAssembly->size()); code2(LC::c_jump, nullptr); return true; } /* ExitNode */ bool LingoCompiler::visitExitNode(ExitNode *node) { NodeStore store(node); code1(LC::c_procret); return true; } /* ReturnNode */ bool LingoCompiler::visitReturnNode(ReturnNode *node) { NodeStore store(node); if (node->expr) { COMPILE_REF(node->expr); codeCmd("return", 1); } else { codeCmd("return", 0); } return true; } /* TellNode */ bool LingoCompiler::visitTellNode(TellNode *node) { NodeStore store(node); COMPILE(node->target); code1(LC::c_tell); COMPILE_LIST(node->stmts); code1(LC::c_telldone); return true; } /* WhenNode */ bool LingoCompiler::visitWhenNode(WhenNode *node) { NodeStore store(node); code1(LC::c_stringpush); codeString(node->code->c_str()); code1(LC::c_whencode); codeString(node->event->c_str()); return true; } /* DeleteNode */ bool LingoCompiler::visitDeleteNode(DeleteNode *node) { NodeStore store(node); COMPILE_REF(node->chunk); code1(LC::c_delete); return true; } /* HiliteNode */ bool LingoCompiler::visitHiliteNode(HiliteNode *node) { NodeStore store(node); COMPILE_REF(node->chunk); code1(LC::c_hilite); return true; } /* AssertErrorNode */ bool LingoCompiler::visitAssertErrorNode(AssertErrorNode *node) { NodeStore store(node); code1(LC::c_asserterror); COMPILE(node->stmt); code1(LC::c_asserterrordone); return true; } /* IntNode */ bool LingoCompiler::visitIntNode(IntNode *node) { NodeStore store(node); code1(LC::c_intpush); codeInt(node->val); return true; } /* FloatNode */ bool LingoCompiler::visitFloatNode(FloatNode *node) { NodeStore store(node); code1(LC::c_floatpush); codeFloat(node->val); return true; } /* SymbolNode */ bool LingoCompiler::visitSymbolNode(SymbolNode *node) { NodeStore store(node); code1(LC::c_symbolpush); codeString(node->val->c_str()); return true; } /* StringNode */ bool LingoCompiler::visitStringNode(StringNode *node) { NodeStore store(node); code1(LC::c_stringpush); codeString(node->val->c_str()); return true; } /* ListNode */ bool LingoCompiler::visitListNode(ListNode *node) { NodeStore store(node); COMPILE_LIST(node->items); code1(LC::c_arraypush); codeInt(node->items->size()); return true; } /* PropListNode */ bool LingoCompiler::visitPropListNode(PropListNode *node) { NodeStore store(node); bool refModeStore = _refMode; _refMode = false; bool success = true; for (uint i = 0; i < node->items->size(); i++) { Node *item = (*node->items)[i]; if (item->type != kPropPairNode) { // We have a keyless expression, as in ["key": "value", "keyless expression"] // Automatically set its key to its index in the list. code1(LC::c_intpush); codeInt(i + 1); } success = item->accept(this); if (!success) break; } _refMode = refModeStore; if (!success) return false; code1(LC::c_proparraypush); codeInt(node->items->size()); return true; } /* PropPairNode */ bool LingoCompiler::visitPropPairNode(PropPairNode *node) { NodeStore store(node); COMPILE(node->key); COMPILE(node->val); return true; } /* FuncNode */ bool LingoCompiler::visitFuncNode(FuncNode *node) { NodeStore store(node); if (node->name->equalsIgnoreCase("field") && node->args->size() >= 1) { COMPILE((*node->args)[0]); if (_refMode) { code1(LC::c_fieldref); } else { code1(LC::c_field); } return true; } if (node->args->size() >= 1 && (*node->args)[0]->type == kVarNode) { // This could be a method call. Code the first arg as a reference. COMPILE_REF((*node->args)[0]); for (uint i = 1; i < node->args->size(); i++) { COMPILE((*node->args)[i]); } } else { COMPILE_LIST(node->args); } codeFunc(*node->name, node->args->size()); return true; } /* VarNode */ bool LingoCompiler::visitVarNode(VarNode *node) { NodeStore store(node); if (g_director->getVersion() < 400 || (g_director->getCurrentMovie() && g_director->getCurrentMovie()->_allowOutdatedLingo)) { int val = castNumToNum(node->name->c_str()); if (val != -1) { code1(LC::c_intpush); codeInt(val); return true; } } if (g_lingo->_builtinConsts.contains(*node->name)) { code1(LC::c_constpush); codeString(node->name->c_str()); return true; } if (_refMode) { codeVarRef(*node->name); return true; } codeVarGet(*node->name); return true; } /* ParensNode */ bool LingoCompiler::visitParensNode(ParensNode *node) { NodeStore store(node); COMPILE(node->expr); return true; } /* UnaryOpNode */ bool LingoCompiler::visitUnaryOpNode(UnaryOpNode *node) { NodeStore store(node); COMPILE(node->arg); code1(node->op); return true; } /* BinaryOpNode */ bool LingoCompiler::visitBinaryOpNode(BinaryOpNode *node) { NodeStore store(node); COMPILE(node->a); COMPILE(node->b); code1(node->op); return true; } /* FrameNode */ bool LingoCompiler::visitFrameNode(FrameNode *node) { NodeStore store(node); COMPILE(node->arg); return true; } /* MovieNode */ bool LingoCompiler::visitMovieNode(MovieNode *node) { NodeStore store(node); COMPILE(node->arg); return true; } /* IntersectsNode */ bool LingoCompiler::visitIntersectsNode(IntersectsNode *node) { NodeStore store(node); COMPILE(node->sprite1); COMPILE(node->sprite2); code1(LC::c_intersects); return true; } /* WithinNode */ bool LingoCompiler::visitWithinNode(WithinNode *node) { NodeStore store(node); COMPILE(node->sprite1); COMPILE(node->sprite2); code1(LC::c_within); return true; } /* TheNode */ bool LingoCompiler::visitTheNode(TheNode *node) { NodeStore store(node); if (g_lingo->_theEntities.contains(*node->prop) && !g_lingo->_theEntities[*node->prop]->hasId) { code1(LC::c_intpush); codeInt(0); // Put dummy id code1(LC::c_theentitypush); codeInt(g_lingo->_theEntities[*node->prop]->entity); codeInt(0); // No field return true; } warning("BUILDBOT: LingoCompiler:visitTheNode: Unhandled the entity '%s'", node->prop->c_str()); return false; } /* TheOfNode */ bool LingoCompiler::visitTheOfNode(TheOfNode *node) { NodeStore store(node); switch (node->obj->type) { case kChunkExprNode: { int fieldId = getTheFieldID(kTheChunk, *node->prop, true); if (fieldId >= 0) { COMPILE_REF(node->obj); code1(LC::c_theentitypush); codeInt(kTheChunk); codeInt(fieldId); return true; } // fall back to generic object } break; case kFuncNode: { FuncNode *func = static_cast(node->obj); if (func->args->size() == 1) { if (func->name->equalsIgnoreCase("cast")) { int fieldId = getTheFieldID(kTheCast, *node->prop, true); if (fieldId >= 0) { COMPILE((*func->args)[0]); code1(LC::c_theentitypush); codeInt(kTheCast); codeInt(fieldId); return true; } // fall back to generic object } if (func->name->equalsIgnoreCase("field")) { int fieldId = getTheFieldID(kTheField, *node->prop, true); if (fieldId >= 0) { COMPILE((*func->args)[0]); code1(LC::c_theentitypush); codeInt(kTheField); codeInt(fieldId); return true; } // fall back to generic object } // window is an object and is handled by that case } } break; case kMenuNode: { MenuNode *menu = static_cast(node->obj); int fieldId = getTheFieldID(kTheMenu, *node->prop); if (fieldId < 0) return false; COMPILE(menu->arg); code1(LC::c_theentitypush); codeInt(kTheMenu); codeInt(fieldId); return true; } break; case kMenuItemNode: { MenuItemNode *menuItem = static_cast(node->obj); int fieldId = getTheFieldID(kTheMenuItem, *node->prop); if (fieldId < 0) return false; COMPILE(menuItem->arg1) COMPILE(menuItem->arg2); code1(LC::c_themenuentitypush); codeInt(kTheMenuItem); codeInt(fieldId); return true; } break; case kSoundNode: { SoundNode *sound = static_cast(node->obj); int fieldId = getTheFieldID(kTheSoundEntity, *node->prop); if (fieldId < 0) return false; COMPILE(sound->arg); code1(LC::c_theentitypush); codeInt(kTheSoundEntity); codeInt(fieldId); return true; } break; case kSpriteNode: { SpriteNode *sprite = static_cast(node->obj); int fieldId = getTheFieldID(kTheSprite, *node->prop); if (fieldId < 0) return false; COMPILE(sprite->arg); code1(LC::c_theentitypush); codeInt(kTheSprite); codeInt(fieldId); return true; } break; case kVarNode: { VarNode *var = static_cast(node->obj); if (node->prop->equalsIgnoreCase("number") && var->name->equalsIgnoreCase("castMembers")) { code1(LC::c_intpush); codeInt(0); // Put dummy id code1(LC::c_theentitypush); codeInt(kTheCastMembers); codeInt(kTheNumber); return true; } } break; default: break; } if (g_director->getVersion() >= 400) { COMPILE(node->obj); code1(LC::c_objectproppush); codeString(node->prop->c_str()); return true; } if (g_lingo->_builtinFuncs.contains(*node->prop)) { COMPILE(node->obj); codeFunc(*node->prop, 1); return true; } warning("BUILDBOT: LingoCompiler::visitTheOfNode: Unhandled the field %s", node->prop->c_str()); return false; } /* TheNumberOfNode */ bool LingoCompiler::visitTheNumberOfNode(TheNumberOfNode *node) { NodeStore store(node); switch (node->type) { case kNumberOfCastlibs: codeInt(0); // Put dummy id code1(LC::c_theentitypush); codeInt(kTheCastLibs); codeInt(kTheNumber); break; case kNumberOfChars: COMPILE(node->arg); codeFunc("numberOfChars", 1); break; case kNumberOfWords: COMPILE(node->arg); codeFunc("numberOfWords", 1); break; case kNumberOfItems: COMPILE(node->arg); codeFunc("numberOfItems", 1); break; case kNumberOfLines: COMPILE(node->arg); codeFunc("numberOfLines", 1); break; case kNumberOfMenuItems: { if (node->arg->type != kMenuNode) { warning("BUILDBOT: LingoCompiler::visitTheNumberOfNode: expected menu"); return false; } MenuNode *menu = static_cast(node->arg); COMPILE(menu->arg); code1(LC::c_theentitypush); codeInt(kTheMenuItems); codeInt(kTheNumber); } break; case kNumberOfMenus: codeInt(0); // Put dummy id code1(LC::c_theentitypush); codeInt(kTheMenus); codeInt(kTheNumber); break; case kNumberOfXtras: codeInt(0); // Put dummy id code1(LC::c_theentitypush); codeInt(kTheXtras); codeInt(kTheNumber); break; } return true; } /* TheLastNode */ bool LingoCompiler::visitTheLastNode(TheLastNode *node) { NodeStore store(node); code1(LC::c_intpush); codeInt(-30000); code1(LC::c_intpush); codeInt(0); if (_refMode) { COMPILE_REF(node->arg); } else { COMPILE(node->arg); } switch (node->type) { case kChunkChar: if (_refMode) { code1(LC::c_charToOfRef); } else { code1(LC::c_charToOf); } break; case kChunkWord: if (_refMode) { code1(LC::c_wordToOfRef); } else { code1(LC::c_wordToOf); } break; case kChunkItem: if (_refMode) { code1(LC::c_itemToOfRef); } else { code1(LC::c_itemToOf); } break; case kChunkLine: if (_refMode) { code1(LC::c_lineToOfRef); } else { code1(LC::c_lineToOf); } break; } return true; } /* TheDateTimeNode */ bool LingoCompiler::visitTheDateTimeNode(TheDateTimeNode *node) { NodeStore store(node); code1(LC::c_intpush); codeInt(0); // Put dummy id code1(LC::c_theentitypush); codeInt(node->entity); codeInt(node->field); return true; } /* MenuNode */ bool LingoCompiler::visitMenuNode(MenuNode *node) { NodeStore store(node); return true; } /* MenuItemNode */ bool LingoCompiler::visitMenuItemNode(MenuItemNode *node) { NodeStore store(node); return true; } /* SoundItem */ bool LingoCompiler::visitSoundNode(SoundNode *node) { NodeStore store(node); return true; } /* SpriteNode */ bool LingoCompiler::visitSpriteNode(SpriteNode *node) { NodeStore store(node); return true; } /* ChunkExprNode */ bool LingoCompiler::visitChunkExprNode(ChunkExprNode *node) { NodeStore store(node); COMPILE(node->start); if (node->end) { COMPILE(node->end); } else { code1(LC::c_intpush); codeInt(0); } if (_refMode) { COMPILE_REF(node->src); } else { COMPILE(node->src); } switch (node->type) { case kChunkChar: if (_refMode) { code1(LC::c_charToOfRef); } else { code1(LC::c_charToOf); } break; case kChunkWord: if (_refMode) { code1(LC::c_wordToOfRef); } else { code1(LC::c_wordToOf); } break; case kChunkItem: if (_refMode) { code1(LC::c_itemToOfRef); } else { code1(LC::c_itemToOf); } break; case kChunkLine: if (_refMode) { code1(LC::c_lineToOfRef); } else { code1(LC::c_lineToOf); } break; } return true; } } // End of namespace Director