1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/file.h"
24 #include "common/config-manager.h"
25
26 #include "graphics/macgui/macwindowmanager.h"
27
28 #include "director/director.h"
29 #include "director/cast.h"
30 #include "director/castmember.h"
31 #include "director/frame.h"
32 #include "director/movie.h"
33 #include "director/score.h"
34 #include "director/sprite.h"
35 #include "director/window.h"
36 #include "director/util.h"
37
38 #include "director/lingo/lingo.h"
39 #include "director/lingo/lingo-ast.h"
40 #include "director/lingo/lingo-code.h"
41 #include "director/lingo/lingo-codegen.h"
42 #include "director/lingo/lingo-gr.h"
43 #include "director/lingo/lingo-object.h"
44
45 namespace Director {
46
47 Lingo *g_lingo;
48
calcStringAlignment(const char * s)49 int calcStringAlignment(const char *s) {
50 return calcCodeAlignment(strlen(s) + 1);
51 }
52
calcCodeAlignment(int l)53 int calcCodeAlignment(int l) {
54 int instLen = sizeof(inst);
55 return (l + instLen - 1) / instLen;
56 }
57
Symbol()58 Symbol::Symbol() {
59 name = nullptr;
60 type = VOIDSYM;
61 u.s = nullptr;
62 refCount = new int;
63 *refCount = 1;
64 nargs = 0;
65 maxArgs = 0;
66 targetType = kNoneObj;
67 argNames = nullptr;
68 varNames = nullptr;
69 ctx = nullptr;
70 target = nullptr;
71 anonymous = false;
72 }
73
Symbol(const Symbol & s)74 Symbol::Symbol(const Symbol &s) {
75 name = s.name;
76 type = s.type;
77 u = s.u;
78 refCount = s.refCount;
79 *refCount += 1;
80 nargs = s.nargs;
81 maxArgs = s.maxArgs;
82 targetType = s.targetType;
83 argNames = s.argNames;
84 varNames = s.varNames;
85 ctx = s.ctx;
86 target = s.target;
87 anonymous = s.anonymous;
88 }
89
operator =(const Symbol & s)90 Symbol& Symbol::operator=(const Symbol &s) {
91 if (this == &s)
92 return *this;
93
94 reset();
95 name = s.name;
96 type = s.type;
97 u = s.u;
98 refCount = s.refCount;
99 *refCount += 1;
100 nargs = s.nargs;
101 maxArgs = s.maxArgs;
102 targetType = s.targetType;
103 argNames = s.argNames;
104 varNames = s.varNames;
105 ctx = s.ctx;
106 target = s.target;
107 anonymous = s.anonymous;
108
109 return *this;
110 }
111
reset()112 void Symbol::reset() {
113 *refCount -= 1;
114 // Coverity thinks that we always free memory, as it assumes
115 // (correctly) that there are cases when refCount == 0
116 // Thus, DO NOT COMPILE, trick it and shut tons of false positives
117 #ifndef __COVERITY__
118 if (*refCount <= 0) {
119 if (name)
120 delete name;
121
122 if (type == HANDLER)
123 delete u.defn;
124
125 if (argNames)
126 delete argNames;
127 if (varNames)
128 delete varNames;
129 delete refCount;
130 }
131 #endif
132 }
133
~Symbol()134 Symbol::~Symbol() {
135 reset();
136 }
137
PCell()138 PCell::PCell() {
139 }
140
PCell(const Datum & prop,const Datum & val)141 PCell::PCell(const Datum &prop, const Datum &val) {
142 p = prop;
143 v = val;
144 }
145
Lingo(DirectorEngine * vm)146 Lingo::Lingo(DirectorEngine *vm) : _vm(vm) {
147 g_lingo = this;
148
149 _currentScript = 0;
150 _currentScriptContext = nullptr;
151
152 _currentChannelId = -1;
153 _globalCounter = 0;
154 _pc = 0;
155 _freezeContext = false;
156 _abort = false;
157 _expectError = false;
158 _caughtError = false;
159
160 _floatPrecision = 4;
161 _floatPrecisionFormat = "%.4f";
162
163 _localvars = NULL;
164
165 //kTheEntities
166 _itemDelimiter = ',';
167
168 // events
169 _passEvent = false;
170 _perFrameHook = Datum();
171
172 _windowList.type = ARRAY;
173 _windowList.u.farr = new FArray;
174
175 _compiler = new LingoCompiler;
176
177 initEventHandlerTypes();
178 initCharNormalizations();
179
180 initBuiltIns();
181 initFuncs();
182 initBytecode();
183 initTheEntities();
184 initMethods();
185 initXLibs();
186
187 warning("Lingo Inited");
188 }
189
~Lingo()190 Lingo::~Lingo() {
191 resetLingo();
192 cleanupFuncs();
193 cleanupMethods();
194 delete _compiler;
195 }
196
reloadBuiltIns()197 void Lingo::reloadBuiltIns() {
198 debug("Reloading builtins");
199 cleanupBuiltIns();
200 cleanUpTheEntities();
201 cleanupMethods();
202 cleanupXLibs();
203 initBuiltIns();
204 initTheEntities();
205 initMethods();
206 initXLibs();
207 reloadOpenXLibs();
208 }
209
~LingoArchive()210 LingoArchive::~LingoArchive() {
211 for (int i = 0; i <= kMaxScriptType; i++) {
212 for (ScriptContextHash::iterator it = scriptContexts[i].begin(); it != scriptContexts[i].end(); ++it) {
213 *it->_value->_refCount -= 1;
214 if (*it->_value->_refCount <= 0)
215 delete it->_value;
216 }
217 }
218 }
219
getScriptContext(ScriptType type,uint16 id)220 ScriptContext *LingoArchive::getScriptContext(ScriptType type, uint16 id) {
221 if (!scriptContexts[type].contains(id)) {
222 return nullptr;
223 }
224 return scriptContexts[type][id];
225 }
226
getName(uint16 id)227 Common::String LingoArchive::getName(uint16 id) {
228 Common::String result;
229 if (id >= names.size()) {
230 warning("Name id %d not in list", id);
231 return result;
232 }
233 result = names[id];
234 return result;
235 }
236
getHandler(const Common::String & name)237 Symbol Lingo::getHandler(const Common::String &name) {
238 Symbol sym;
239
240 // local functions
241 if (_currentScriptContext && _currentScriptContext->_functionHandlers.contains(name))
242 return _currentScriptContext->_functionHandlers[name];
243
244 sym = g_director->getCurrentMovie()->getHandler(name);
245 if (sym.type != VOIDSYM)
246 return sym;
247
248 sym.type = VOIDSYM;
249 sym.name = new Common::String(name);
250 return sym;
251 }
252
addCode(const Common::U32String & code,ScriptType type,uint16 id,const char * scriptName)253 void LingoArchive::addCode(const Common::U32String &code, ScriptType type, uint16 id, const char *scriptName) {
254 debugC(1, kDebugCompile, "Add code for type %s(%d) with id %d in '%s%s'\n"
255 "***********\n%s\n\n***********", scriptType2str(type), type, id, utf8ToPrintable(g_director->getCurrentPath()).c_str(), utf8ToPrintable(cast->getMacName()).c_str(), code.encode().c_str());
256
257 if (getScriptContext(type, id)) {
258 // Replace the pre-existing context but warn about it.
259 // For cases where replacing the script context is expected (e.g. 'when' event handlers)
260 // use replaceCode instead of addCode.
261 warning("Script already defined for type %d, id %d", type, id);
262 removeCode(type, id);
263 }
264
265 Common::String contextName;
266 if (scriptName && strlen(scriptName) > 0)
267 contextName = Common::String(scriptName);
268 else
269 contextName = Common::String::format("%d", id);
270
271 ScriptContext *sc = g_lingo->_compiler->compileLingo(code, this, type, CastMemberID(id, cast->_castLibID), contextName);
272 if (sc) {
273 scriptContexts[type][id] = sc;
274 *sc->_refCount += 1;
275 }
276 }
277
removeCode(ScriptType type,uint16 id)278 void LingoArchive::removeCode(ScriptType type, uint16 id) {
279 ScriptContext *ctx = getScriptContext(type, id);
280 if (!ctx)
281 return;
282
283 *ctx->_refCount -= 1;
284 if (*ctx->_refCount <= 0) {
285 delete ctx;
286 }
287 scriptContexts[type].erase(id);
288 }
289
replaceCode(const Common::U32String & code,ScriptType type,uint16 id,const char * scriptName)290 void LingoArchive::replaceCode(const Common::U32String &code, ScriptType type, uint16 id, const char *scriptName) {
291 removeCode(type, id);
292 addCode(code, type, id, scriptName);
293 }
294
printStack(const char * s,uint pc)295 void Lingo::printStack(const char *s, uint pc) {
296 Common::String stack(s);
297
298 for (uint i = 0; i < _stack.size(); i++) {
299 Datum d = _stack[i];
300 stack += Common::String::format("<%s> ", d.asString(true).c_str());
301 }
302 debugC(5, kDebugLingoExec, "[%3d]: %s", pc, stack.c_str());
303 }
304
printCallStack(uint pc)305 void Lingo::printCallStack(uint pc) {
306 Common::Array<CFrame *> &callstack = _vm->getCurrentWindow()->_callstack;
307 if (callstack.size() == 0) {
308 debugC(2, kDebugLingoExec, "\nEnd of execution");
309 return;
310 }
311 debugC(2, kDebugLingoExec, "\nCall stack:");
312 for (int i = 0; i < (int)callstack.size(); i++) {
313 CFrame *frame = callstack[i];
314 uint framePc = pc;
315 if (i < (int)callstack.size() - 1)
316 framePc = callstack[i + 1]->retPC;
317
318 if (frame->sp.type != VOIDSYM) {
319 debugC(2, kDebugLingoExec, "#%d %s:%d", i + 1,
320 callstack[i]->sp.name->c_str(),
321 framePc
322 );
323 } else {
324 debugC(2, kDebugLingoExec, "#%d [unknown]:%d", i + 1,
325 framePc
326 );
327 }
328 }
329 }
330
decodeInstruction(ScriptData * sd,uint pc,uint * newPc)331 Common::String Lingo::decodeInstruction(ScriptData *sd, uint pc, uint *newPc) {
332 Symbol sym;
333 Common::String res;
334
335 sym.u.func = (*sd)[pc++];
336 if (_functions.contains((void *)sym.u.s)) {
337 res = _functions[(void *)sym.u.s]->name;
338 const char *pars = _functions[(void *)sym.u.s]->proto;
339 inst i;
340 uint start = pc;
341
342 while (*pars) {
343 switch (*pars++) {
344 case 'i':
345 {
346 i = (*sd)[pc++];
347 int v = READ_UINT32(&i);
348
349 res += Common::String::format(" %d", v);
350 break;
351 }
352 case 'f':
353 {
354 Datum d;
355 i = (*sd)[pc++];
356 d.u.f = *(double *)(&i);
357
358 res += Common::String::format(" %f", d.u.f);
359 break;
360 }
361 case 'o':
362 {
363 i = (*sd)[pc++];
364 int v = READ_UINT32(&i);
365
366 res += Common::String::format(" [%5d]", v + start - 1);
367 break;
368 }
369 case 's':
370 {
371 char *s = (char *)&(*sd)[pc];
372 pc += calcStringAlignment(s);
373
374 res += Common::String::format(" \"%s\"", s);
375 break;
376 }
377 case 'E':
378 {
379 i = (*sd)[pc++];
380 int v = READ_UINT32(&i);
381
382 res += Common::String::format(" %s", entity2str(v));
383 break;
384 }
385 case 'F':
386 {
387 i = (*sd)[pc++];
388 int v = READ_UINT32(&i);
389
390 res += Common::String::format(" %s", field2str(v));
391 break;
392 }
393 default:
394 warning("decodeInstruction: Unknown parameter type: %c", pars[-1]);
395 }
396
397 if (*pars)
398 res += ',';
399 }
400 } else {
401 res = "<unknown>";
402 }
403
404 if (newPc)
405 *newPc = pc;
406
407 return res;
408 }
409
execute()410 void Lingo::execute() {
411 uint localCounter = 0;
412
413 while (!_abort && !_freezeContext && (*_currentScript)[_pc] != STOP) {
414 if (_globalCounter > 1000 && debugChannelSet(-1, kDebugFewFramesOnly)) {
415 warning("Lingo::execute(): Stopping due to debug few frames only");
416 _vm->getCurrentMovie()->getScore()->_playState = kPlayStopped;
417 break;
418 }
419
420 // process events every so often
421 if (localCounter > 0 && localCounter % 100 == 0) {
422 _vm->processEvents();
423 if (_vm->getCurrentMovie()->getScore()->_playState == kPlayStopped)
424 break;
425 }
426
427 Common::String instr = decodeInstruction(_currentScript, _pc);
428 uint current = _pc;
429
430 if (debugChannelSet(5, kDebugLingoExec))
431 printStack("Stack before: ", current);
432
433 if (debugChannelSet(9, kDebugLingoExec)) {
434 debug("Vars before");
435 printAllVars();
436 if (_currentMe.type == OBJECT)
437 debug("me: %s", _currentMe.asString(true).c_str());
438 }
439
440 debugC(3, kDebugLingoExec, "[%3d]: %s", current, instr.c_str());
441
442 _pc++;
443 (*((*_currentScript)[_pc - 1]))();
444
445 if (debugChannelSet(5, kDebugLingoExec))
446 printStack("Stack after: ", current);
447
448 if (debugChannelSet(9, kDebugLingoExec)) {
449 debug("Vars after");
450 printAllVars();
451 }
452
453 _globalCounter++;
454 localCounter++;
455
456 if (!_abort && _pc >= (*_currentScript).size()) {
457 warning("Lingo::execute(): Bad PC (%d)", _pc);
458 break;
459 }
460 }
461
462 if (_abort || _vm->getCurrentMovie()->getScore()->_playState == kPlayStopped) {
463 // Clean up call stack
464 while (_vm->getCurrentWindow()->_callstack.size()) {
465 popContext(true);
466 }
467 }
468 _abort = false;
469
470 if (_freezeContext) {
471 debugC(1, kDebugLingoExec, "Lingo::execute(): Context is frozen, pausing execution");
472 }
473 }
474
executeScript(ScriptType type,CastMemberID id)475 void Lingo::executeScript(ScriptType type, CastMemberID id) {
476 Movie *movie = _vm->getCurrentMovie();
477 if (!movie) {
478 warning("Request to execute script with no movie");
479 return;
480 }
481
482 ScriptContext *sc = movie->getScriptContext(type, id);
483
484 if (!sc) {
485 debugC(3, kDebugLingoExec, "Request to execute non-existent script type %d id %d of castLib %d", type, id.member, id.castLib);
486 return;
487 }
488
489 if (!sc->_eventHandlers.contains(kEventGeneric)) {
490 debugC(3, kDebugLingoExec, "Request to execute script type %d id %d of castLib %d with no scopeless lingo", type, id.member, id.castLib);
491 return;
492 }
493
494 debugC(1, kDebugLingoExec, "Executing script type: %s, id: %d, castLib %d", scriptType2str(type), id.member, id.castLib);
495
496 Symbol sym = sc->_eventHandlers[kEventGeneric];
497 LC::call(sym, 0, false);
498 execute();
499 }
500
executeHandler(const Common::String & name)501 void Lingo::executeHandler(const Common::String &name) {
502 debugC(1, kDebugLingoExec, "Executing script handler : %s", name.c_str());
503 Symbol sym = getHandler(name);
504 LC::call(sym, 0, false);
505 execute();
506 }
507
lingoError(const char * s,...)508 void Lingo::lingoError(const char *s, ...) {
509 char buf[1024];
510 va_list va;
511
512 va_start(va, s);
513 vsnprintf(buf, 1024, s, va);
514 va_end(va);
515
516 if (_expectError) {
517 warning("Caught Lingo error: %s", buf);
518 _caughtError = true;
519 } else {
520 warning("BUILDBOT: Uncaught Lingo error: %s", buf);
521 _abort = true;
522 }
523 }
524
resetLingo()525 void Lingo::resetLingo() {
526 debugC(3, kDebugLingoExec, "Resetting Lingo!");
527
528 g_director->_wm->removeMenu();
529
530 // TODO
531 //
532 // reset the following:
533 // the keyDownScript
534 // the mouseUpScript
535 // the mouseDownScript
536 // the beepOn
537 // the constraint properties
538 // the cursor
539 // the immediate sprite properties
540 // the puppetSprite
541 // cursor commands
542 //
543 // NOTE:
544 // timeoutScript is not reset
545 }
546
getAlignedType(const Datum & d1,const Datum & d2,bool numsOnly)547 int Lingo::getAlignedType(const Datum &d1, const Datum &d2, bool numsOnly) {
548 int opType = VOID;
549
550 int d1Type = d1.type;
551 int d2Type = d2.type;
552
553 if (d1Type == d2Type && (!numsOnly || d1Type == INT || d1Type == FLOAT))
554 return d1Type;
555
556 if (d1Type == STRING) {
557 Common::String src = d1.asString();
558 if (!src.empty()) {
559 char *endPtr = 0;
560 strtod(src.c_str(), &endPtr);
561 if (*endPtr == 0) {
562 d1Type = FLOAT;
563 }
564 }
565 }
566 if (d2Type == STRING) {
567 Common::String src = d2.asString();
568 if (!src.empty()) {
569 char *endPtr = 0;
570 strtod(src.c_str(), &endPtr);
571 if (*endPtr == 0) {
572 d2Type = FLOAT;
573 }
574 }
575 }
576
577 // VOID equals to 0
578 if (d1Type == VOID)
579 d1Type = INT;
580 if (d2Type == VOID)
581 d2Type = INT;
582
583 if (d1Type == FLOAT || d2Type == FLOAT) {
584 opType = FLOAT;
585 } else if (d1Type == INT && d2Type == INT) {
586 opType = INT;
587 }
588
589 return opType;
590 }
591
Datum()592 Datum::Datum() {
593 u.s = nullptr;
594 type = VOID;
595 refCount = new int;
596 *refCount = 1;
597 }
598
Datum(const Datum & d)599 Datum::Datum(const Datum &d) {
600 type = d.type;
601 u = d.u;
602 refCount = d.refCount;
603 *refCount += 1;
604 }
605
operator =(const Datum & d)606 Datum& Datum::operator=(const Datum &d) {
607 if (this != &d && refCount != d.refCount) {
608 reset();
609 type = d.type;
610 u = d.u;
611 refCount = d.refCount;
612 *refCount += 1;
613 }
614 return *this;
615 }
616
Datum(int val)617 Datum::Datum(int val) {
618 u.i = val;
619 type = INT;
620 refCount = new int;
621 *refCount = 1;
622 }
623
Datum(double val)624 Datum::Datum(double val) {
625 u.f = val;
626 type = FLOAT;
627 refCount = new int;
628 *refCount = 1;
629 }
630
Datum(const Common::String & val)631 Datum::Datum(const Common::String &val) {
632 u.s = new Common::String(val);
633 type = STRING;
634 refCount = new int;
635 *refCount = 1;
636 }
637
Datum(AbstractObject * val)638 Datum::Datum(AbstractObject *val) {
639 u.obj = val;
640 if (val) {
641 type = OBJECT;
642 refCount = val->getRefCount();
643 *refCount += 1;
644 } else {
645 type = VOID;
646 refCount = new int;
647 *refCount = 1;
648 }
649 }
650
Datum(const CastMemberID & val)651 Datum::Datum(const CastMemberID &val) {
652 u.cast = new CastMemberID(val);
653 type = CASTREF;
654 refCount = new int;
655 *refCount = 1;
656 }
657
Datum(const Common::Rect & rect)658 Datum::Datum(const Common::Rect &rect) {
659 type = RECT;
660 u.farr = new FArray;
661 u.farr->arr.push_back(Datum(rect.left));
662 u.farr->arr.push_back(Datum(rect.top));
663 u.farr->arr.push_back(Datum(rect.right));
664 u.farr->arr.push_back(Datum(rect.bottom));
665 }
666
reset()667 void Datum::reset() {
668 if (!refCount)
669 return;
670
671 *refCount -= 1;
672 // Coverity thinks that we always free memory, as it assumes
673 // (correctly) that there are cases when refCount == 0
674 // Thus, DO NOT COMPILE, trick it and shut tons of false positives
675 #ifndef __COVERITY__
676 if (*refCount <= 0) {
677 switch (type) {
678 case VARREF:
679 case GLOBALREF:
680 case LOCALREF:
681 case PROPREF:
682 case STRING:
683 case SYMBOL:
684 delete u.s;
685 break;
686 case ARRAY:
687 case POINT:
688 case RECT:
689 delete u.farr;
690 break;
691 case PARRAY:
692 delete u.parr;
693 break;
694 case OBJECT:
695 if (u.obj->getObjType() == kWindowObj) {
696 Window *window = static_cast<Window *>(u.obj);
697 g_director->_wm->removeWindow(window);
698 g_director->_wm->removeMarked();
699 } else {
700 delete u.obj;
701 }
702 break;
703 case CHUNKREF:
704 delete u.cref;
705 break;
706 case CASTREF:
707 case FIELDREF:
708 delete u.cast;
709 break;
710 default:
711 break;
712 }
713 if (type != OBJECT) // object owns refCount
714 delete refCount;
715 }
716 #endif
717 }
718
eval() const719 Datum Datum::eval() const {
720 if (isRef()) {
721 return g_lingo->varFetch(*this);
722 }
723
724 return *this;
725 }
726
asInt() const727 int Datum::asInt() const {
728 int res = 0;
729
730 switch (type) {
731 case STRING:
732 {
733 Common::String src = asString();
734 char *endPtr = 0;
735 int result = strtol(src.c_str(), &endPtr, 10);
736 if (*endPtr == 0) {
737 res = result;
738 } else {
739 warning("Invalid int '%s'", src.c_str());
740 }
741 }
742 break;
743 case VOID:
744 // no-op
745 break;
746 case INT:
747 res = u.i;
748 break;
749 case FLOAT:
750 if (g_director->getVersion() < 400) {
751 res = round(u.f);
752 } else {
753 res = (int)u.f;
754 }
755 break;
756 default:
757 warning("Incorrect operation asInt() for type: %s", type2str());
758 }
759
760 return res;
761 }
762
asFloat() const763 double Datum::asFloat() const {
764 double res = 0.0;
765
766 switch (type) {
767 case STRING: {
768 Common::String src = asString();
769 char *endPtr = 0;
770 double result = strtod(src.c_str(), &endPtr);
771 if (*endPtr == 0) {
772 res = result;
773 } else {
774 warning("Invalid float '%s'", src.c_str());
775 }
776 }
777 break;
778 case VOID:
779 // no-op
780 break;
781 case INT:
782 res = (double)u.i;
783 break;
784 case FLOAT:
785 res = u.f;
786 break;
787 default:
788 warning("Incorrect operation makeFloat() for type: %s", type2str());
789 }
790
791 return res;
792 }
793
asString(bool printonly) const794 Common::String Datum::asString(bool printonly) const {
795 Common::String s;
796 switch (type) {
797 case INT:
798 s = Common::String::format("%d", u.i);
799 break;
800 case ARGC:
801 s = Common::String::format("argc: %d", u.i);
802 break;
803 case ARGCNORET:
804 s = Common::String::format("argcnoret: %d", u.i);
805 break;
806 case FLOAT:
807 s = Common::String::format(g_lingo->_floatPrecisionFormat.c_str(), u.f);
808 if (printonly)
809 s += "f"; // 0.0f
810 break;
811 case STRING:
812 if (!printonly) {
813 s = *u.s;
814 } else {
815 s = Common::String::format("\"%s\"", u.s->c_str());
816 }
817 break;
818 case SYMBOL:
819 if (!printonly) {
820 s = *u.s;
821 } else {
822 s = Common::String::format("#%s", u.s->c_str());
823 }
824 break;
825 case OBJECT:
826 if (!printonly) {
827 s = Common::String::format("#%s", u.obj->getName().c_str());
828 } else {
829 s = u.obj->asString();
830 }
831 break;
832 case VOID:
833 if (!printonly) {
834 s = "";
835 } else {
836 if (g_director->getVersion() < 400) {
837 s = "<NoValue>";
838 } else {
839 s = "<Void>";
840 }
841 }
842 break;
843 case VARREF:
844 s = Common::String::format("var: #%s", u.s->c_str());
845 break;
846 case GLOBALREF:
847 s = Common::String::format("global: #%s", u.s->c_str());
848 break;
849 case LOCALREF:
850 s = Common::String::format("local: #%s", u.s->c_str());
851 break;
852 case PROPREF:
853 s = Common::String::format("property: #%s", u.s->c_str());
854 break;
855 case CASTREF:
856 s = Common::String::format("member %d of castLib %d", u.cast->member, u.cast->castLib);
857 break;
858 case FIELDREF:
859 s = Common::String::format("field %d of castLib %d", u.cast->member, u.cast->castLib);
860 break;
861 case CHUNKREF:
862 {
863 Common::String chunkType;
864 switch (u.cref->type) {
865 case kChunkChar:
866 chunkType = "char";
867 break;
868 case kChunkWord:
869 chunkType = "word";
870 break;
871 case kChunkItem:
872 chunkType = "item";
873 break;
874 case kChunkLine:
875 chunkType = "line";
876 break;
877 }
878 Common::String src = u.cref->source.asString(true);
879 Common::String chunk = eval().asString(true);
880 s += Common::String::format("chunk: %s %d to %d of %s (%s)", chunkType.c_str(), u.cref->startChunk, u.cref->endChunk, src.c_str(), chunk.c_str());
881 }
882 break;
883 case ARRAY:
884 s += "[";
885
886 for (uint i = 0; i < u.farr->arr.size(); i++) {
887 if (i > 0)
888 s += ", ";
889 Datum d = u.farr->arr[i];
890 s += d.asString(printonly);
891 }
892
893 s += "]";
894 break;
895 case PARRAY:
896 s = "[";
897 if (u.parr->arr.size() == 0)
898 s += ":";
899 for (uint i = 0; i < u.parr->arr.size(); i++) {
900 if (i > 0)
901 s += ", ";
902 Datum p = u.parr->arr[i].p;
903 Datum v = u.parr->arr[i].v;
904 s += Common::String::format("%s:%s", p.asString(printonly).c_str(), v.asString(printonly).c_str());
905 }
906
907 s += "]";
908 break;
909 case POINT:
910 s = "point(";
911 for (uint i = 0; i < u.farr->arr.size(); i++) {
912 if (i > 0)
913 s += ", ";
914 s += Common::String::format("%d", u.farr->arr[i].asInt());
915 }
916 s += ")";
917
918 break;
919 case RECT:
920 s = "rect(";
921 for (uint i = 0; i < u.farr->arr.size(); i++) {
922 if (i > 0)
923 s += ", ";
924 s += Common::String::format("%d", u.farr->arr[i].asInt());
925 }
926
927 s += ")";
928 break;
929 default:
930 warning("Incorrect operation asString() for type: %s", type2str());
931 }
932
933 return s;
934 }
935
asMemberID() const936 CastMemberID Datum::asMemberID() const {
937 if (type == CASTREF || type == FIELDREF)
938 return *u.cast;
939
940 return g_lingo->resolveCastMember(*this, 0);
941 }
942
asPoint() const943 Common::Point Datum::asPoint() const {
944 if (type != POINT) {
945 warning("Incorrect operation asPoint() for type: %s", type2str());
946 return Common::Point(0, 0);
947 }
948
949 return Common::Point(u.farr->arr[0].asInt(), u.farr->arr[1].asInt());
950 }
951
isRef() const952 bool Datum::isRef() const {
953 return (isVarRef() || isCastRef() || type == CHUNKREF);
954 }
955
isVarRef() const956 bool Datum::isVarRef() const {
957 return (type == VARREF || type == GLOBALREF || type == LOCALREF || type == PROPREF);
958 }
959
isCastRef() const960 bool Datum::isCastRef() const {
961 return (type == CASTREF || type == FIELDREF);
962 }
963
type2str(bool isk) const964 const char *Datum::type2str(bool isk) const {
965 static char res[20];
966
967 switch (isk ? u.i : type) {
968 case INT:
969 return isk ? "#integer" : "INT";
970 case FLOAT:
971 return isk ? "#float" : "FLOAT";
972 case STRING:
973 return isk ? "#string" : "STRING";
974 case CASTREF:
975 return "CASTREF";
976 case VOID:
977 return isk ? "#void" : "VOID";
978 case POINT:
979 return isk ? "#point" : "POINT";
980 case SYMBOL:
981 return isk ? "#symbol" : "SYMBOL";
982 case OBJECT:
983 return isk ? "#object" : "OBJECT";
984 case FIELDREF:
985 return "FIELDREF";
986 case CHUNKREF:
987 return "CHUNKREF";
988 case VARREF:
989 return "VARREF";
990 case GLOBALREF:
991 return "GLOBALREF";
992 case LOCALREF:
993 return "LOCALREF";
994 case PROPREF:
995 return "PROPREF";
996 default:
997 snprintf(res, 20, "-- (%d) --", type);
998 return res;
999 }
1000 }
1001
equalTo(Datum & d,bool ignoreCase) const1002 int Datum::equalTo(Datum &d, bool ignoreCase) const {
1003 int alignType = g_lingo->getAlignedType(*this, d, false);
1004
1005 switch (alignType) {
1006 case FLOAT:
1007 return asFloat() == d.asFloat();
1008 case INT:
1009 return asInt() == d.asInt();
1010 case STRING:
1011 case SYMBOL:
1012 if (ignoreCase) {
1013 return g_lingo->normalizeString(asString()).equals(g_lingo->normalizeString(d.asString()));
1014 } else {
1015 return asString().equals(d.asString());
1016 }
1017 case OBJECT:
1018 return u.obj == d.u.obj;
1019 case CASTREF:
1020 return *u.cast == *d.u.cast;
1021 default:
1022 break;
1023 }
1024 return 0;
1025 }
1026
compareTo(Datum & d) const1027 int Datum::compareTo(Datum &d) const {
1028 int alignType = g_lingo->getAlignedType(*this, d, false);
1029
1030 if (alignType == FLOAT) {
1031 double f1 = asFloat();
1032 double f2 = d.asFloat();
1033 if (f1 < f2) {
1034 return -1;
1035 } else if (f1 == f2) {
1036 return 0;
1037 } else {
1038 return 1;
1039 }
1040 } else if (alignType == INT) {
1041 double i1 = asInt();
1042 double i2 = d.asInt();
1043 if (i1 < i2) {
1044 return -1;
1045 } else if (i1 == i2) {
1046 return 0;
1047 } else {
1048 return 1;
1049 }
1050 } else if (alignType == STRING) {
1051 return compareStrings(asString(), d.asString());
1052 } else {
1053 warning("Invalid comparison between types %s and %s", type2str(), d.type2str());
1054 return 0;
1055 }
1056 }
1057
runTests()1058 void Lingo::runTests() {
1059 Common::File inFile;
1060 Common::ArchiveMemberList fsList;
1061 SearchMan.listMatchingMembers(fsList, "*.lingo");
1062 Common::StringArray fileList;
1063
1064 LingoArchive *mainArchive = g_director->getCurrentMovie()->getMainLingoArch();
1065
1066 Common::String startMovie = _vm->getStartMovie().startMovie;
1067 if (startMovie.size() > 0) {
1068 fileList.push_back(startMovie);
1069 } else {
1070 for (Common::ArchiveMemberList::iterator it = fsList.begin(); it != fsList.end(); ++it)
1071 fileList.push_back((*it)->getName());
1072 }
1073
1074 Common::sort(fileList.begin(), fileList.end());
1075
1076 int counter = 1;
1077
1078 for (uint i = 0; i < fileList.size(); i++) {
1079 Common::SeekableReadStream *const stream = SearchMan.createReadStreamForMember(fileList[i]);
1080 if (stream) {
1081 uint size = stream->size();
1082
1083 char *script = (char *)calloc(size + 1, 1);
1084
1085 stream->read(script, size);
1086
1087 debug(">> Compiling file %s of size %d, id: %d", fileList[i].c_str(), size, counter);
1088
1089 mainArchive->addCode(Common::U32String(script, Common::kMacRoman), kTestScript, counter);
1090
1091 if (!debugChannelSet(-1, kDebugCompileOnly)) {
1092 if (!_compiler->_hadError)
1093 executeScript(kTestScript, CastMemberID(counter, 0));
1094 else
1095 debug(">> Skipping execution");
1096 }
1097
1098 free(script);
1099
1100 counter++;
1101 }
1102
1103 inFile.close();
1104 }
1105 }
1106
executeImmediateScripts(Frame * frame)1107 void Lingo::executeImmediateScripts(Frame *frame) {
1108 for (uint16 i = 0; i <= _vm->getCurrentMovie()->getScore()->_numChannelsDisplayed; i++) {
1109 if (_vm->getCurrentMovie()->getScore()->_immediateActions.contains(frame->_sprites[i]->_scriptId.member)) {
1110 // From D5 only explicit event handlers are processed
1111 // Before that you could specify commands which will be executed on mouse up
1112 if (_vm->getVersion() < 500)
1113 g_lingo->processEvent(kEventGeneric, kScoreScript, frame->_sprites[i]->_scriptId, i);
1114 else
1115 g_lingo->processEvent(kEventMouseUp, kScoreScript, frame->_sprites[i]->_scriptId, i);
1116 }
1117 }
1118 }
1119
executePerFrameHook(int frame,int subframe)1120 void Lingo::executePerFrameHook(int frame, int subframe) {
1121 if (_perFrameHook.type == OBJECT) {
1122 Symbol method = _perFrameHook.u.obj->getMethod("mAtFrame");
1123 if (method.type != VOIDSYM) {
1124 debugC(1, kDebugLingoExec, "Executing perFrameHook : <%s>(mAtFrame, %d, %d)", _perFrameHook.asString(true).c_str(), frame, subframe);
1125 push(_perFrameHook);
1126 push(frame);
1127 push(subframe);
1128 LC::call(method, 3, false);
1129 execute();
1130 }
1131 }
1132 }
1133
cleanLocalVars()1134 void Lingo::cleanLocalVars() {
1135 // Clean up current scope local variables and clean up memory
1136 debugC(3, kDebugLingoExec, "cleanLocalVars: have %d vars", _localvars->size());
1137
1138 g_lingo->_localvars->clear();
1139 delete g_lingo->_localvars;
1140
1141 g_lingo->_localvars = nullptr;
1142 }
1143
printAllVars()1144 void Lingo::printAllVars() {
1145 debugN(" Local vars: ");
1146 if (_localvars) {
1147 for (DatumHash::iterator i = _localvars->begin(); i != _localvars->end(); ++i) {
1148 debugN("%s, ", (*i)._key.c_str());
1149 }
1150 } else {
1151 debugN("(no local vars)");
1152 }
1153 debugN("\n");
1154
1155 if (_currentMe.type == OBJECT && _currentMe.u.obj->getObjType() & (kFactoryObj | kScriptObj)) {
1156 ScriptContext *script = static_cast<ScriptContext *>(_currentMe.u.obj);
1157 debugN(" Instance/property vars: ");
1158 for (DatumHash::iterator i = script->_properties.begin(); i != script->_properties.end(); ++i) {
1159 debugN("%s, ", (*i)._key.c_str());
1160 }
1161 debugN("\n");
1162 }
1163
1164 debugN(" Global vars: ");
1165 for (DatumHash::iterator i = _globalvars.begin(); i != _globalvars.end(); ++i) {
1166 debugN("%s, ", (*i)._key.c_str());
1167 }
1168 debugN("\n");
1169 }
1170
getInt(uint pc)1171 int Lingo::getInt(uint pc) {
1172 return (int)READ_UINT32(&((*_currentScript)[pc]));
1173 }
1174
varAssign(const Datum & var,const Datum & value)1175 void Lingo::varAssign(const Datum &var, const Datum &value) {
1176 switch (var.type) {
1177 case VARREF:
1178 {
1179 Common::String name = *var.u.s;
1180 if (_localvars && _localvars->contains(name)) {
1181 (*_localvars)[name] = value;
1182 return;
1183 }
1184 if (_currentMe.type == OBJECT && _currentMe.u.obj->hasProp(name)) {
1185 _currentMe.u.obj->setProp(name, value);
1186 return;
1187 }
1188 _globalvars[name] = value;
1189 }
1190 break;
1191 case GLOBALREF:
1192 // Global variables declared by `global varname` within a handler are not listed anywhere
1193 // in Lscr, unlike globals declared outside of a handler and every other variable type.
1194 // So while we require other variable types to be initialized before assigning to them,
1195 // let's not enforce that for globals.
1196 _globalvars[*var.u.s] = value;
1197 break;
1198 case LOCALREF:
1199 {
1200 Common::String name = *var.u.s;
1201 if (_localvars && _localvars->contains(name)) {
1202 (*_localvars)[name] = value;
1203 } else {
1204 warning("varAssign: local variable %s not defined", name.c_str());
1205 }
1206 }
1207 break;
1208 case PROPREF:
1209 {
1210 Common::String name = *var.u.s;
1211 if (_currentMe.type == OBJECT && _currentMe.u.obj->hasProp(name)) {
1212 _currentMe.u.obj->setProp(name, value);
1213 } else {
1214 warning("varAssign: property %s not defined", name.c_str());
1215 }
1216 }
1217 break;
1218 case FIELDREF:
1219 case CASTREF:
1220 {
1221 Movie *movie = g_director->getCurrentMovie();
1222 if (!movie) {
1223 warning("varAssign: Assigning to a reference to an empty movie");
1224 return;
1225 }
1226 CastMember *member = movie->getCastMember(*var.u.cast);
1227 if (!member) {
1228 warning("varAssign: Unknown %s", var.u.cast->asString().c_str());
1229 return;
1230 }
1231 switch (member->_type) {
1232 case kCastText:
1233 ((TextCastMember *)member)->setText(value.asString());
1234 break;
1235 default:
1236 warning("varAssign: Unhandled cast type %d", member->_type);
1237 break;
1238 }
1239 }
1240 break;
1241 case CHUNKREF:
1242 {
1243 Common::U32String src = evalChunkRef(var.u.cref->source);
1244 Common::U32String res;
1245 if (var.u.cref->start >= 0) {
1246 res = src.substr(0, var.u.cref->start) + value.asString().decode(Common::kUtf8) + src.substr(var.u.cref->end);
1247 } else {
1248 // non-existent chunk - insert more chars, items, or lines
1249 res = src;
1250 int numberOfChunks = LC::lastChunk(var.u.cref->type, var.u.cref->source).u.cref->startChunk;
1251 switch (var.u.cref->type) {
1252 case kChunkChar:
1253 while (numberOfChunks < var.u.cref->startChunk - 1) {
1254 res += ' ';
1255 numberOfChunks++;
1256 }
1257 break;
1258 case kChunkWord:
1259 break;
1260 case kChunkItem:
1261 while (numberOfChunks < var.u.cref->startChunk ) {
1262 res += _itemDelimiter;
1263 numberOfChunks++;
1264 }
1265 break;
1266 case kChunkLine:
1267 while (numberOfChunks < var.u.cref->startChunk ) {
1268 res += '\r';
1269 numberOfChunks++;
1270 }
1271 break;
1272 }
1273 res += value.asString();
1274 }
1275 varAssign(var.u.cref->source, res.encode(Common::kUtf8));
1276 }
1277 break;
1278 default:
1279 warning("varAssign: assignment to non-variable");
1280 break;
1281 }
1282 }
1283
varFetch(const Datum & var,bool silent)1284 Datum Lingo::varFetch(const Datum &var, bool silent) {
1285 Datum result;
1286
1287 switch (var.type) {
1288 case VARREF:
1289 {
1290 Datum d;
1291 Common::String name = *var.u.s;
1292
1293 if (_localvars && _localvars->contains(name)) {
1294 return (*_localvars)[name];
1295 }
1296 if (_currentMe.type == OBJECT && _currentMe.u.obj->hasProp(name)) {
1297 return _currentMe.u.obj->getProp(name);
1298 }
1299 if (_globalvars.contains(name)) {
1300 return _globalvars[name];
1301 }
1302
1303 if (!silent)
1304 warning("varFetch: variable %s not found", name.c_str());
1305 return result;
1306 }
1307 break;
1308 case GLOBALREF:
1309 {
1310 Common::String name = *var.u.s;
1311 if (_globalvars.contains(name)) {
1312 return _globalvars[name];
1313 }
1314 warning("varFetch: global variable %s not defined", name.c_str());
1315 return result;
1316 }
1317 break;
1318 case LOCALREF:
1319 {
1320 Common::String name = *var.u.s;
1321 if (_localvars && _localvars->contains(name)) {
1322 return (*_localvars)[name];
1323 }
1324 warning("varFetch: local variable %s not defined", name.c_str());
1325 return result;
1326 }
1327 break;
1328 case PROPREF:
1329 {
1330 Common::String name = *var.u.s;
1331 if (_currentMe.type == OBJECT && _currentMe.u.obj->hasProp(name)) {
1332 return _currentMe.u.obj->getProp(name);
1333 }
1334 warning("varFetch: property %s not defined", name.c_str());
1335 return result;
1336 }
1337 break;
1338 case FIELDREF:
1339 case CASTREF:
1340 case CHUNKREF:
1341 {
1342 result.type = STRING;
1343 result.u.s = new Common::String(evalChunkRef(var), Common::kUtf8);
1344 }
1345 break;
1346 default:
1347 warning("varFetch: fetch from non-variable");
1348 break;
1349 }
1350
1351 return result;
1352 }
1353
evalChunkRef(const Datum & var)1354 Common::U32String Lingo::evalChunkRef(const Datum &var) {
1355 Common::U32String result;
1356
1357 switch (var.type) {
1358 case VARREF:
1359 case GLOBALREF:
1360 case LOCALREF:
1361 case PROPREF:
1362 result = varFetch(var).asString().decode(Common::kUtf8);
1363 break;
1364 case FIELDREF:
1365 case CASTREF:
1366 {
1367 Movie *movie = g_director->getCurrentMovie();
1368 if (!movie) {
1369 warning("evalChunkRef: Assigning to a reference to an empty movie");
1370 return result;
1371 }
1372 CastMember *member = movie->getCastMember(*var.u.cast);
1373 if (!member) {
1374 warning("evalChunkRef: Unknown %s", var.u.cast->asString().c_str());
1375 return result;
1376 }
1377 switch (member->_type) {
1378 case kCastText:
1379 result = ((TextCastMember *)member)->getText();
1380 break;
1381 default:
1382 warning("evalChunkRef: Unhandled cast type %d", member->_type);
1383 break;
1384 }
1385 }
1386 break;
1387 case CHUNKREF:
1388 {
1389 Common::U32String src = evalChunkRef(var.u.cref->source);
1390 if (var.u.cref->start >= 0) {
1391 result = src.substr(var.u.cref->start, var.u.cref->end - var.u.cref->start);
1392 }
1393 }
1394 break;
1395 default:
1396 result = var.asString().decode(Common::kUtf8);
1397 break;
1398 }
1399
1400 return result;
1401 }
1402
resolveCastMember(const Datum & memberID,const Datum & castLib)1403 CastMemberID Lingo::resolveCastMember(const Datum &memberID, const Datum &castLib) {
1404 Movie *movie = g_director->getCurrentMovie();
1405 if (!movie) {
1406 warning("Lingo::resolveCastMember: No movie");
1407 return CastMemberID(-1, castLib.asInt());
1408 }
1409
1410 switch (memberID.type) {
1411 case STRING:
1412 {
1413 CastMember *member = movie->getCastMemberByName(memberID.asString(), castLib.asInt());
1414 if (member)
1415 return CastMemberID(member->getID(), castLib.asInt());
1416
1417 warning("Lingo::resolveCastMember: reference to non-existent cast member: %s", memberID.asString().c_str());
1418 return CastMemberID(-1, castLib.asInt());
1419 }
1420 break;
1421 case INT:
1422 case FLOAT:
1423 return CastMemberID(memberID.asInt(), castLib.asInt());
1424 break;
1425 case VOID:
1426 warning("Lingo::resolveCastMember: reference to VOID member ID");
1427 break;
1428 default:
1429 error("Lingo::resolveCastMember: unsupported member ID type %s", memberID.type2str());
1430 }
1431
1432 return CastMemberID(-1, castLib.asInt());
1433 }
1434
1435 } // End of namespace Director
1436