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