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 "glk/alan3/debug.h"
24 #include "glk/alan3/alan3.h"
25 #include "glk/alan3/class.h"
26 #include "glk/alan3/sysdep.h"
27 #include "glk/alan3/alan_version.h"
28 #include "glk/alan3/compatibility.h"
29 #include "glk/alan3/current.h"
30 #include "glk/alan3/event.h"
31 #include "glk/alan3/exe.h"
32 #include "glk/alan3/glkio.h"
33 #include "glk/alan3/instance.h"
34 #include "glk/alan3/inter.h"
35 #include "glk/alan3/lists.h"
36 #include "glk/alan3/memory.h"
37 #include "glk/alan3/options.h"
38 #include "glk/alan3/output.h"
39 #include "glk/alan3/sysdep.h"
40 #include "glk/alan3/utils.h"
41 #include "glk/streams.h"
42 
43 namespace Glk {
44 namespace Alan3 {
45 
46 #define BREAKPOINTMAX 50
47 
48 
49 /* PUBLIC: */
50 int breakpointCount = 0;
51 Breakpoint breakpoint[BREAKPOINTMAX];
52 
53 #define debugPrefix "adbg: "
54 
55 /*----------------------------------------------------------------------*/
showAttributes(AttributeEntry * attrib)56 static void showAttributes(AttributeEntry *attrib) {
57 	AttributeEntry *at;
58 	int i;
59 	char str[80];
60 
61 	if (attrib == 0)
62 		return;
63 
64 	i = 1;
65 	for (at = attrib; !isEndOfArray(at); at++) {
66 		sprintf(str, "$i$t%s[%d] = %d", (char *) pointerTo(at->id), at->code, (int)at->value);
67 
68 		output(str);
69 		i++;
70 	}
71 }
72 
73 
74 /*----------------------------------------------------------------------*/
showContents(CONTEXT,int cnt)75 static void showContents(CONTEXT, int cnt) {
76 	uint i;
77 	char str[80];
78 	Abool found = FALSE;
79 
80 	output("$iContains:");
81 	for (i = 1; i <= header->instanceMax; i++) {
82 		if (isIn(i, cnt, DIRECT)) { /* Yes, it's directly in this container */
83 			if (!found)
84 				found = TRUE;
85 			output("$i$t");
86 			say(context, i);
87 			sprintf(str, "[%d] ", i);
88 			output(str);
89 		}
90 	}
91 	if (!found)
92 		output("nothing");
93 }
94 
95 
96 /*----------------------------------------------------------------------*/
idOfInstance(CONTEXT,int instance)97 static char *idOfInstance(CONTEXT, int instance) {
98 	int base = header->instanceTableAddress +
99 	           header->instanceMax * sizeof(InstanceEntry) / sizeof(Aword) + 1;
100 	return (char *)&memory[memory[base + instance - 1]];
101 }
102 
103 
104 /*----------------------------------------------------------------------*/
sayInstanceNumberAndName(CONTEXT,int ins)105 static void sayInstanceNumberAndName(CONTEXT, int ins) {
106 	char buf[1000];
107 
108 	sprintf(buf, "[%d] %s (\"$$", ins, idOfInstance(context, ins));
109 	output(buf);
110 	say(context, ins);
111 	output("$$\")");
112 }
113 
114 
115 /*----------------------------------------------------------------------*/
sayLocationOfInstance(CONTEXT,int ins,const char * prefix)116 static void sayLocationOfInstance(CONTEXT, int ins, const char *prefix) {
117 	if (admin[ins].location == 0)
118 		return;
119 	else {
120 		output(prefix);
121 		if (isALocation(admin[ins].location)) {
122 			output("at");
123 			CALL1(sayInstanceNumberAndName, admin[ins].location)
124 			CALL2(sayLocationOfInstance, admin[ins].location, prefix)
125 		} else if (isAContainer(admin[ins].location)) {
126 			if (isAObject(admin[ins].location))
127 				output("in");
128 			else if (isAActor(admin[ins].location))
129 				output("carried by");
130 			CALL1(sayInstanceNumberAndName, admin[ins].location)
131 			CALL2(sayLocationOfInstance, admin[ins].location, prefix)
132 		} else {
133 			output("Illegal location!");
134 		}
135 	}
136 }
137 
138 /*----------------------------------------------------------------------*/
listInstance(CONTEXT,int ins)139 static void listInstance(CONTEXT, int ins) {
140 	output("$i");
141 	CALL1(sayInstanceNumberAndName, ins)
142 	if (instances[ins].container)
143 		output("(container)");
144 	CALL2(sayLocationOfInstance, ins, ", ")
145 }
146 
147 
148 /*----------------------------------------------------------------------*/
listInstances(CONTEXT,char * pattern)149 static void listInstances(CONTEXT, char *pattern) {
150 	uint ins;
151 	bool found = FALSE;
152 
153 	for (ins = 1; ins <= header->instanceMax; ins++) {
154 		if (pattern == NULL || (pattern != NULL && match(pattern, idOfInstance(context, ins)))) {
155 			if (!found) {
156 				output("Instances:");
157 				found = TRUE;
158 			}
159 			CALL1(listInstance, ins)
160 		}
161 	}
162 	if (pattern != NULL && !found)
163 		output("No instances matched the pattern.");
164 }
165 
166 /*----------------------------------------------------------------------*/
showInstance(CONTEXT,int ins)167 static void showInstance(CONTEXT, int ins) {
168 	char str[80];
169 
170 	if (ins > (int)header->instanceMax || ins < 1) {
171 		sprintf(str, "Instance index %d is out of range.", ins);
172 		output(str);
173 		return;
174 	}
175 
176 	output("The");
177 	CALL1(sayInstanceNumberAndName, ins)
178 	if (instances[ins].parent) {
179 		sprintf(str, "Isa %s[%d]", idOfClass(instances[ins].parent), instances[ins].parent);
180 		output(str);
181 	}
182 
183 	if (!isA(ins, header->locationClassId) || (isA(ins, header->locationClassId) && admin[ins].location != 0)) {
184 		sprintf(str, "$iLocation:");
185 		output(str);
186 		needSpace = TRUE;
187 		CALL2(sayLocationOfInstance, ins, "")
188 	}
189 
190 	output("$iAttributes:");
191 	showAttributes(admin[ins].attributes);
192 
193 	if (instances[ins].container)
194 		CALL1(showContents, ins)
195 
196 	if (isA(ins, header->actorClassId)) {
197 		if (admin[ins].script == 0)
198 			output("$iIs idle");
199 		else {
200 			sprintf(str, "$iExecuting script: %d, Step: %d", admin[ins].script, admin[ins].step);
201 			output(str);
202 		}
203 	}
204 }
205 
206 
207 /*----------------------------------------------------------------------*/
listObjects(CONTEXT)208 static void listObjects(CONTEXT) {
209 	uint obj;
210 
211 	output("Objects:");
212 	for (obj = 1; obj <= header->instanceMax; obj++)
213 		if (isAObject(obj))
214 			CALL1(listInstance, obj)
215 }
216 
217 
218 /*----------------------------------------------------------------------*/
showObject(CONTEXT,int obj)219 static void showObject(CONTEXT, int obj) {
220 	char str[80];
221 
222 
223 	if (!isAObject(obj)) {
224 		sprintf(str, "Instance %d is not an object", obj);
225 		output(str);
226 		return;
227 	}
228 
229 	CALL1(showInstance, obj)
230 }
231 
232 /*----------------------------------------------------------------------*/
sourceFileNumber(char * fileName)233 static int sourceFileNumber(char *fileName) {
234 	SourceFileEntry *entries = (SourceFileEntry *)pointerTo(header->sourceFileTable);
235 	int n;
236 
237 	for (n = 0; * (Aword *)&entries[n] != EOD; n++) {
238 		char *entryName;
239 		entryName = getStringFromFile(entries[n].fpos, entries[n].len);
240 		if (strcmp(entryName, fileName) == 0) return n;
241 		entryName = baseNameStart(entryName);
242 		if (strcmp(entryName, fileName) == 0) return n;
243 	}
244 	return -1;
245 }
246 
247 
248 
249 /*----------------------------------------------------------------------*/
printClassName(int c)250 static void printClassName(int c) {
251 	output(idOfClass(c));
252 }
253 
254 
255 /*----------------------------------------------------------------------*/
showClassInheritance(int c)256 static void showClassInheritance(int c) {
257 	char str[80];
258 
259 	if (classes[c].parent != 0) {
260 		output(", Isa");
261 		printClassName(classes[c].parent);
262 		sprintf(str, "[%d]", classes[c].parent);
263 		output(str);
264 	}
265 }
266 
267 
268 /*----------------------------------------------------------------------*/
showClass(int cla)269 static void showClass(int cla) {
270 	char str[80];
271 
272 	if (cla < 1) {
273 		sprintf(str, "Class index %d is out of range.", cla);
274 		output(str);
275 		return;
276 	}
277 
278 	output("$t");
279 	printClassName(cla);
280 	sprintf(str, "[%d]", cla);
281 	output(str);
282 	showClassInheritance(cla);
283 }
284 
285 
286 /*----------------------------------------------------------------------*/
listClass(int c)287 static void listClass(int c) {
288 	char str[80];
289 
290 	sprintf(str, "%3d: ", c);
291 	output(str);
292 	printClassName(c);
293 	showClassInheritance(c);
294 }
295 
296 
297 /*----------------------------------------------------------------------*/
showClassHierarchy(int thisItem,int depth)298 static void showClassHierarchy(int thisItem, int depth) {
299 	int i;
300 	uint child;
301 
302 	output("$i");
303 	for (i = 0; i < depth; i++)
304 		output("$t");
305 
306 	listClass(thisItem);
307 	for (child = 1; child <= header->classMax; child++) {
308 		if (classes[child].parent == thisItem) {
309 			showClassHierarchy(child, depth + 1);
310 		}
311 	}
312 }
313 
314 
315 /*----------------------------------------------------------------------*/
listLocations(CONTEXT)316 static void listLocations(CONTEXT) {
317 	uint loc;
318 
319 	output("Locations:");
320 	for (loc = 1; loc <= header->instanceMax; loc++)
321 		if (isALocation(loc))
322 			listInstance(context, loc);
323 }
324 
325 
326 /*----------------------------------------------------------------------*/
showLocation(CONTEXT,int loc)327 static void showLocation(CONTEXT, int loc) {
328 	char str[80];
329 
330 
331 	if (!isALocation(loc)) {
332 		sprintf(str, "Instance %d is not a location.", loc);
333 		output(str);
334 		return;
335 	}
336 
337 	output("The ");
338 	CALL1(say, loc)
339 	sprintf(str, "(%d) Isa location :", loc);
340 	output(str);
341 
342 	output("$iAttributes =");
343 	showAttributes(admin[loc].attributes);
344 }
345 
346 
347 /*----------------------------------------------------------------------*/
listActors(CONTEXT)348 static void listActors(CONTEXT) {
349 	uint act;
350 
351 	output("Actors:");
352 	for (act = 1; act <= header->instanceMax; act++)
353 		if (isAActor(act))
354 			CALL1(listInstance, act)
355 }
356 
357 
358 /*----------------------------------------------------------------------*/
showActor(CONTEXT,int act)359 static void showActor(CONTEXT, int act) {
360 	char str[80];
361 
362 	if (!isAActor(act)) {
363 		sprintf(str, "Instance %d is not an actor.", act);
364 		output(str);
365 		return;
366 	}
367 
368 	CALL1(showInstance, act)
369 }
370 
371 
372 /*----------------------------------------------------------------------*/
showEvents(CONTEXT)373 static void showEvents(CONTEXT) {
374 	uint event;
375 	int i;
376 	char str[80];
377 	bool scheduled;
378 
379 	output("Events:");
380 	for (event = 1; event <= header->eventMax; event++) {
381 		sprintf(str, "$i%d [%s]:", event, (char *)pointerTo(events[event].id));
382 
383 		output(str);
384 		scheduled = FALSE;
385 		for (i = 0; i < eventQueueTop; i++)
386 			if ((scheduled = (eventQueue[i].event == (int)event)))
387 				break;
388 		if (scheduled) {
389 			sprintf(str, "Scheduled for +%d, at ", eventQueue[i].after);
390 			output(str);
391 			CALL1(say, eventQueue[i].where)
392 		} else
393 			output("Not scheduled.");
394 	}
395 }
396 
397 
398 /*======================================================================*/
sourceFileName(int fileNumber)399 char *sourceFileName(int fileNumber) {
400 	SourceFileEntry *entries = (SourceFileEntry *)pointerTo(header->sourceFileTable);
401 
402 	return getStringFromFile(entries[fileNumber].fpos, entries[fileNumber].len);
403 }
404 
405 
406 /*======================================================================*/
readLine(Common::SeekableReadStream * rs,char * line,int maxLen)407 bool readLine(Common::SeekableReadStream *rs, char *line, int maxLen) {
408 	if (rs->pos() < rs->size()) {
409 		line[maxLen - 1] = '\0';
410 
411 		char c;
412 		do {
413 			c = rs->readByte();
414 			*line++ = c;
415 		} while (--maxLen > 1);
416 	}
417 
418 	return rs->pos() < rs->size();
419 }
420 
421 
422 /*======================================================================*/
readSourceLine(int file,int line)423 char *readSourceLine(int file, int line) {
424 	int count;
425 #define SOURCELINELENGTH 1000
426 	static char buffer[SOURCELINELENGTH];
427 
428 	frefid_t sourceFileRef = g_vm->glk_fileref_create_by_name(fileusage_TextMode, sourceFileName(file), 0);
429 	strid_t sourceFile = g_vm->glk_stream_open_file(sourceFileRef, filemode_Read, 0);
430 
431 	if (sourceFile != NULL) {
432 		for (count = 0; count < line; count++) {
433 			if (!readLine(*sourceFile, buffer, SOURCELINELENGTH))
434 				return NULL;
435 
436 			// If not read the whole line, or no newline, try to read again
437 			while (strchr(buffer, '\n') == NULL) {
438 				if (!readLine(*sourceFile, buffer, SOURCELINELENGTH))
439 					break;
440 			}
441 		}
442 
443 		delete sourceFile;
444 		return buffer;
445 	}
446 
447 	return NULL;
448 }
449 
450 /*======================================================================*/
showSourceLine(int fileNumber,int line)451 void showSourceLine(int fileNumber, int line) {
452 	char *buffer = readSourceLine(fileNumber, line);
453 	if (buffer != NULL) {
454 		if (buffer[strlen(buffer) - 1] == '\n')
455 			buffer[strlen(buffer) - 1] = '\0';
456 		printf("<%05d>: %s", line, buffer);
457 	}
458 }
459 
460 
461 /*----------------------------------------------------------------------*/
listFiles()462 static void listFiles() {
463 	SourceFileEntry *entry;
464 	int i = 0;
465 	for (entry = (SourceFileEntry *)pointerTo(header->sourceFileTable); * ((Aword *)entry) != EOD; entry++) {
466 		printf("  %2d : %s\n", i, sourceFileName(i));
467 		i++;
468 	}
469 }
470 
471 
472 /*----------------------------------------------------------------------*/
findSourceLineIndex(SourceLineEntry * entry,int file,int line)473 static int findSourceLineIndex(SourceLineEntry *entry, int file, int line) {
474 	/* Will return index to the closest line available */
475 	int i = 0;
476 
477 	while (!isEndOfArray(&entry[i]) && entry[i].file != file)
478 		i++;
479 	while (!isEndOfArray(&entry[i]) && entry[i].file == file  && entry[i].line < line)
480 		i++;
481 	if (isEndOfArray(entry) || entry[i].file != file)
482 		return i - 1;
483 	else
484 		return i;
485 }
486 
487 
488 /*----------------------------------------------------------------------*/
listBreakpoints()489 static void listBreakpoints() {
490 	int i;
491 	bool found = FALSE;
492 
493 	for (i = 0; i < BREAKPOINTMAX; i++)
494 		if (breakpoint[i].line != 0) {
495 			if (!found)
496 				printf("Breakpoints set:\n");
497 			found = TRUE;
498 			printf("    %s:%d\n", sourceFileName(breakpoint[i].file), breakpoint[i].line);
499 		}
500 	if (!found)
501 		printf("No breakpoints set\n");
502 }
503 
504 
505 /*======================================================================*/
breakpointIndex(int file,int line)506 int breakpointIndex(int file, int line) {
507 	int i;
508 
509 	for (i = 0; i < BREAKPOINTMAX; i++)
510 		if (breakpoint[i].line == line && breakpoint[i].file == file)
511 			return i;
512 	return -1;
513 }
514 
515 
516 /*----------------------------------------------------------------------*/
availableBreakpointSlot()517 static int availableBreakpointSlot() {
518 	int i;
519 
520 	for (i = 0; i < BREAKPOINTMAX; i++)
521 		if (breakpoint[i].line == 0)
522 			return i;
523 	return -1;
524 }
525 
526 
527 /*----------------------------------------------------------------------*/
setBreakpoint(int file,int line)528 static void setBreakpoint(int file, int line) {
529 	int i = breakpointIndex(file, line);
530 
531 	if (i != -1)
532 		printf("Breakpoint already set at %s:%d\n", sourceFileName(file), line);
533 	else {
534 		i = availableBreakpointSlot();
535 		if (i == -1)
536 			printf("No room for more breakpoints. Delete one first.\n");
537 		else {
538 			int lineIndex = findSourceLineIndex((SourceLineEntry *)pointerTo(header->sourceLineTable), file, line);
539 			SourceLineEntry *entry = (SourceLineEntry *)pointerTo(header->sourceLineTable);
540 			char leadingText[100] = "Breakpoint";
541 			if (entry[lineIndex].file == (Aint)EOD) {
542 				printf("Line %d not available\n", line);
543 			} else {
544 				if (entry[lineIndex].line != line)
545 					sprintf(leadingText, "Line %d not available, breakpoint instead", line);
546 				breakpoint[i].file = entry[lineIndex].file;
547 				breakpoint[i].line = entry[lineIndex].line;
548 				printf("%s set at %s:%d\n", leadingText, sourceFileName(entry[lineIndex].file), entry[lineIndex].line);
549 				showSourceLine(entry[lineIndex].file, entry[lineIndex].line);
550 				printf("\n");
551 			}
552 		}
553 	}
554 }
555 
556 
557 /*----------------------------------------------------------------------*/
deleteBreakpoint(int line,int file)558 static void deleteBreakpoint(int line, int file) {
559 	int i = breakpointIndex(file, line);
560 
561 	if (i == -1)
562 		printf("No breakpoint set at %s:%d\n", sourceFileName(file), line);
563 	else {
564 		breakpoint[i].line = 0;
565 		printf("Breakpoint at %s:%d deleted\n", sourceFileName(file), line);
566 	}
567 }
568 
569 
570 
571 static bool saved_traceSection, saved_traceInstruction, saved_capitilize, saved_tracePush, saved_traceStack, saved_traceSource;
572 static int loc;
573 
574 /*======================================================================*/
saveInfo(void)575 void saveInfo(void) {
576 	/* Save some important things */
577 	saved_capitilize = capitalize;
578 	capitalize = FALSE;
579 	saved_traceSection = traceSectionOption;
580 	traceSectionOption = FALSE;
581 	saved_traceSource = traceSourceOption;
582 	traceSourceOption = FALSE;
583 	saved_traceInstruction = traceInstructionOption;
584 	traceInstructionOption = FALSE;
585 	saved_tracePush = tracePushOption;
586 	tracePushOption = FALSE;
587 	saved_traceStack = traceStackOption;
588 	traceStackOption = FALSE;
589 	loc = current.location;
590 	current.location = where(HERO, DIRECT);
591 }
592 
593 
594 /*======================================================================*/
restoreInfo(void)595 void restoreInfo(void) {
596 	/* Restore! */
597 	capitalize = saved_capitilize;
598 	traceSectionOption = saved_traceSection;
599 	traceInstructionOption = saved_traceInstruction;
600 	traceSourceOption = saved_traceSource;
601 	tracePushOption = saved_tracePush;
602 	traceStackOption = saved_traceStack;
603 	current.location = loc;
604 }
605 
606 #define HELP_COMMAND 'H'
607 #define QUIT_COMMAND 'Q'
608 #define EXIT_COMMAND 'X'
609 #define GO_COMMAND 'G'
610 #define FILES_COMMAND 'F'
611 #define INSTANCES_COMMAND 'I'
612 #define CLASSES_COMMAND 'C'
613 #define OBJECTS_COMMAND 'O'
614 #define ACTORS_COMMAND 'A'
615 #define LOCATIONS_COMMAND 'L'
616 #define EVENTS_COMMAND 'E'
617 #define BREAK_COMMAND 'B'
618 #define DELETE_COMMAND 'D'
619 #define TRACE_COMMAND 'R'
620 #define SECTION_TRACE_COMMAND 'T'
621 #define INSTRUCTION_TRACE_COMMAND 'S'
622 #define NEXT_COMMAND 'N'
623 #define UNKNOWN_COMMAND '?'
624 #define AMBIGUOUS_COMMAND '-'
625 #define TRACE_SOURCE_COMMAND 's'
626 #define TRACE_SECTION_COMMAND 'e'
627 #define TRACE_INSTRUCTION_COMMAND 'i'
628 #define TRACE_PUSH_COMMAND 'p'
629 #define TRACE_STACK_COMMAND 't'
630 
631 typedef struct DebugParseEntry {
632 	const char *command;
633 	const char *parameter;
634 	char code;
635 	const char *helpText;
636 } DebugParseEntry;
637 
638 static const DebugParseEntry commandEntries[] = {
639 	{"help", "", HELP_COMMAND, "this help"},
640 	{"?", "", HELP_COMMAND, "d:o"},
641 	{"break", "[[file:]n]", BREAK_COMMAND, "set breakpoint at source line [n] (optionally in [file])"},
642 	{"delete", "[[file:]n]", DELETE_COMMAND, "delete breakpoint at source line [n] (optionally in [file])"},
643 	{"files", "", FILES_COMMAND, "list source files"},
644 	{"events", "", EVENTS_COMMAND, "list events"},
645 	{"classes", "", CLASSES_COMMAND, "list class hierarchy"},
646 	{"instances", "[n]", INSTANCES_COMMAND, "list instance(s), all, wildcard, number or name"},
647 	{"objects", "[n]", OBJECTS_COMMAND, "list instance(s) that are objects"},
648 	{"actors", "[n]", ACTORS_COMMAND, "list instance(s) that are actors"},
649 	{"locations", "[n]", LOCATIONS_COMMAND, "list instances that are locations"},
650 	{"trace", "('source'|'section'|'instruction'|'push'|'stack')", TRACE_COMMAND, "toggle various traces"},
651 	{"next", "", NEXT_COMMAND, "run game and stop at next source line"},
652 	{"go", "", GO_COMMAND, "go another player turn"},
653 	{"exit", "", EXIT_COMMAND, "exit to game, enter 'debug' to get back"},
654 	{"x", "", EXIT_COMMAND, "d:o"},
655 	{"quit", "", QUIT_COMMAND, "quit game"},
656 	{NULL, NULL, '\0', NULL}
657 };
658 
659 static const DebugParseEntry traceSubcommand[] = {
660 	{"source", "", TRACE_SOURCE_COMMAND, ""},
661 	{"section", "", TRACE_SECTION_COMMAND, ""},
662 	{"instructions", "", TRACE_INSTRUCTION_COMMAND, ""},
663 	{"pushs", "", TRACE_PUSH_COMMAND, ""},
664 	{"stacks", "", TRACE_STACK_COMMAND, ""},
665 	{NULL, NULL, '\0', NULL}
666 };
667 
668 
spaces(int length)669 static char *spaces(int length) {
670 	static char buf[200];
671 	int i;
672 
673 	for (i = 0; i < length; i++)
674 		buf[i] = ' ';
675 	buf[i] = '\0';
676 	return buf;
677 }
678 
679 
680 /*----------------------------------------------------------------------*/
padding(const DebugParseEntry * entry,int maxLength)681 static char *padding(const DebugParseEntry *entry, int maxLength) {
682 	return spaces(maxLength - strlen(entry->command) - strlen(entry->parameter));
683 }
684 
685 
686 /*----------------------------------------------------------------------*/
handleHelpCommand()687 static void handleHelpCommand() {
688 	if (!regressionTestOption)
689 		output(alan.longHeader);
690 
691 	const DebugParseEntry *entry = commandEntries;
692 
693 	int maxLength = 0;
694 	for (entry = commandEntries; entry->command != NULL; entry++) {
695 		if (strlen(entry->command) + strlen(entry->parameter) > (uint)maxLength)
696 			maxLength = strlen(entry->command) + strlen(entry->parameter);
697 	}
698 
699 	output("$nADBG Commands (can be abbreviated):");
700 	for (entry = commandEntries; entry->command != NULL; entry++) {
701 		char buf[200];
702 		sprintf(buf, "$i%s %s %s$n$t$t-- %s", entry->command, entry->parameter, padding(entry, maxLength), entry->helpText);
703 		output(buf);
704 	}
705 }
706 
707 
708 /*----------------------------------------------------------------------*/
findEntry(char * command,const DebugParseEntry * entry)709 static const DebugParseEntry *findEntry(char *command, const DebugParseEntry *entry) {
710 	while (entry->command != NULL) {
711 		if (scumm_strnicmp(command, entry->command, strlen(command)) == 0)
712 			return entry;
713 		entry++;
714 	}
715 	return NULL;
716 }
717 
718 
719 /*----------------------------------------------------------------------*/
parseDebugCommand(char * command)720 static char parseDebugCommand(char *command) {
721 	const DebugParseEntry *entry = findEntry(command, commandEntries);
722 	if (entry != NULL) {
723 		if (strlen(command) < strlen(entry->command)) {
724 			/* See if there are any more partial matches */
725 			if (findEntry(command, entry + 1) != NULL)
726 				/* TODO: we should list the possible matches somehow */
727 				return AMBIGUOUS_COMMAND;
728 		}
729 		return entry->code;
730 	} else
731 		return UNKNOWN_COMMAND;
732 }
733 
734 
735 /*----------------------------------------------------------------------*/
readCommand(CONTEXT,char buf[],size_t maxLen)736 static void readCommand(CONTEXT, char buf[], size_t maxLen) {
737 	char c;
738 	bool flag;
739 
740 	capitalize = FALSE;
741 	if (anyOutput) newline();
742 	do {
743 		output("adbg> ");
744 
745 		FUNC2(g_io->readLine, flag, buf, maxLen)
746 		if (!flag) {
747 			newline();
748 			CALL0(quitGame)
749 		}
750 		lin = 1;
751 		c = buf[0];
752 	} while (c == '\0');
753 }
754 
755 
756 /*----------------------------------------------------------------------*/
displaySourceLocation(int line,int fileNumber)757 static void displaySourceLocation(int line, int fileNumber) {
758 	const char *cause;
759 	if (anyOutput) newline();
760 	if (breakpointIndex(fileNumber, line) != -1)
761 		cause = "Breakpoint hit at";
762 	else
763 		cause = "Stepping to";
764 	printf("%s %s %s:%d\n", debugPrefix, cause, sourceFileName(fileNumber), line);
765 	showSourceLine(fileNumber, line);
766 	printf("\n");
767 	anyOutput = FALSE;
768 }
769 
770 
771 /*----------------------------------------------------------------------*/
toggleSectionTrace()772 static void toggleSectionTrace() {
773 	if ((saved_traceSection = !saved_traceSection))
774 		printf("Section trace on.");
775 	else
776 		printf("Section trace off.");
777 }
778 
779 /*----------------------------------------------------------------------*/
toggleInstructionTrace()780 static void toggleInstructionTrace() {
781 	if ((saved_traceInstruction = !saved_traceInstruction))
782 		printf("Single instruction trace on.");
783 	else
784 		printf("Single instruction trace off.");
785 }
786 
787 /*----------------------------------------------------------------------*/
toggleSourceTrace()788 static void toggleSourceTrace() {
789 	if ((saved_traceSource = !saved_traceSource))
790 		printf("Source code trace on.");
791 	else
792 		printf("Source code trace off.");
793 }
794 
795 
796 /*----------------------------------------------------------------------*/
togglePushTrace()797 static void togglePushTrace() {
798 	if ((saved_tracePush = !saved_tracePush))
799 		printf("Stack Push trace on.");
800 	else
801 		printf("Stack Push trace off.");
802 }
803 
804 
805 /*----------------------------------------------------------------------*/
toggleStackTrace()806 static void toggleStackTrace() {
807 	if ((saved_traceStack = !saved_traceStack))
808 		printf("Full stack trace on.");
809 	else
810 		printf("Full stack trace off.");
811 }
812 
813 
814 /*----------------------------------------------------------------------*/
parseTraceCommand()815 static int parseTraceCommand() {
816 	char *subcommand = strtok(NULL, "");
817 	const DebugParseEntry *entry;
818 	if (subcommand == 0)
819 		return UNKNOWN_COMMAND;
820 	else {
821 		entry = findEntry(subcommand, traceSubcommand);
822 		if (entry != NULL) {
823 			if (strlen(subcommand) < strlen(entry->command)) {
824 				if (findEntry(subcommand, entry + 1) != NULL)
825 					return AMBIGUOUS_COMMAND;
826 			}
827 			return entry->code;
828 		} else
829 			return UNKNOWN_COMMAND;
830 	}
831 }
832 
833 
834 /*----------------------------------------------------------------------*/
printTraceState(bool state)835 static const char *printTraceState(bool state) {
836 	if (state)
837 		return "on  - Traces";
838 	else
839 		return "off - Doesn't trace";
840 }
841 
842 /*----------------------------------------------------------------------*/
printTrace(void)843 static void printTrace(void) {
844 	printf("Trace section     : %s entry to every section (check, description, event, actor, ...)\n", printTraceState(saved_traceSection));
845 	printf("Trace source      : %s every source line executed\n", printTraceState(saved_traceSource));
846 	printf("Trace instruction : %s every Amachine instruction executed\n", printTraceState(saved_traceInstruction));
847 	printf("Trace push        : %s every push onto the Amachine stack\n", printTraceState(saved_tracePush));
848 	printf("Trace stack       : %s the complete stack every time\n", printTraceState(saved_traceStack));
849 }
850 
851 
852 /*----------------------------------------------------------------------*/
handleTraceCommand()853 static void handleTraceCommand() {
854 	char subcommand = parseTraceCommand();
855 
856 	switch (subcommand) {
857 	case TRACE_SECTION_COMMAND:
858 		toggleSectionTrace();
859 		break;
860 	case TRACE_SOURCE_COMMAND:
861 		toggleSourceTrace();
862 		break;
863 	case TRACE_INSTRUCTION_COMMAND:
864 		toggleInstructionTrace();
865 		break;
866 	case TRACE_PUSH_COMMAND:
867 		togglePushTrace();
868 		break;
869 	case TRACE_STACK_COMMAND:
870 		toggleStackTrace();
871 		break;
872 	case AMBIGUOUS_COMMAND:
873 		output("Ambiguous Trace subcommand abbreviation. ? for help.");
874 		break;
875 	default:
876 		printTrace();
877 	}
878 }
879 
880 
881 /*----------------------------------------------------------------------*/
handleBreakCommand(int fileNumber)882 static void handleBreakCommand(int fileNumber) {
883 	char *parameter = strtok(NULL, ":");
884 	if (parameter != NULL && Common::isAlpha((int)parameter[0])) {
885 		fileNumber = sourceFileNumber(parameter);
886 		if (fileNumber == -1) {
887 			printf("No such file: '%s'\n", parameter);
888 			return;
889 		}
890 		parameter = strtok(NULL, "");
891 	}
892 	if (parameter == NULL)
893 		listBreakpoints();
894 	else
895 		setBreakpoint(fileNumber, atoi(parameter));
896 }
897 
898 
899 /*----------------------------------------------------------------------*/
handleDeleteCommand(bool calledFromBreakpoint,int line,int fileNumber)900 static void handleDeleteCommand(bool calledFromBreakpoint, int line, int fileNumber) {
901 	char *parameter = strtok(NULL, "");
902 	if (parameter == NULL) {
903 		if (calledFromBreakpoint)
904 			deleteBreakpoint(line, fileNumber);
905 		else
906 			printf("No current breakpoint to delete\n");
907 	} else
908 		deleteBreakpoint(atoi(parameter), fileNumber);
909 }
910 
911 
912 /*----------------------------------------------------------------------*/
handleNextCommand(bool calledFromBreakpoint)913 static void handleNextCommand(bool calledFromBreakpoint) {
914 	stopAtNextLine = TRUE;
915 	debugOption = FALSE;
916 	if (!calledFromBreakpoint)
917 		current.sourceLine = 0;
918 	restoreInfo();
919 }
920 
921 
922 /*----------------------------------------------------------------------*/
handleLocationsCommand(CONTEXT)923 static void handleLocationsCommand(CONTEXT) {
924 	char *parameter = strtok(NULL, "");
925 	if (parameter == 0)
926 		listLocations(context);
927 	else
928 		showLocation(context, atoi(parameter));
929 }
930 
931 
932 /*----------------------------------------------------------------------*/
handleActorsCommand(CONTEXT)933 static void handleActorsCommand(CONTEXT) {
934 	char *parameter = strtok(NULL, "");
935 	if (parameter == NULL)
936 		listActors(context);
937 	else
938 		showActor(context, atoi(parameter));
939 }
940 
941 
942 /*----------------------------------------------------------------------*/
handleClassesCommand(CONTEXT)943 static void handleClassesCommand(CONTEXT) {
944 	char *parameter = strtok(NULL, "");
945 	if (parameter == NULL || strchr(parameter, '*') != 0) {
946 		output("Classes:");
947 		showClassHierarchy(1, 0);
948 		listInstances(context, parameter);
949 	} else if (Common::isDigit((int)parameter[0]))
950 		showClass(atoi(parameter));
951 	else {
952 		printf("You have to give a class index to display. You can't use names (yet).");
953 	}
954 }
955 
956 
957 /*----------------------------------------------------------------------*/
handleObjectsCommand(CONTEXT)958 static void handleObjectsCommand(CONTEXT) {
959 	char *parameter = strtok(NULL, "");
960 	if (parameter == NULL)
961 		listObjects(context);
962 	else
963 		showObject(context, atoi(parameter));
964 }
965 
966 
967 /*----------------------------------------------------------------------*/
handleInstancesCommand(CONTEXT)968 static void handleInstancesCommand(CONTEXT) {
969 	char *parameter = strtok(NULL, "");
970 	uint i;
971 
972 	if (parameter == NULL || strchr(parameter, '*') != 0)
973 		listInstances(context, parameter);
974 	else if (Common::isDigit((int)parameter[0]))
975 		showInstance(context, atoi(parameter));
976 	else {
977 		for (i = 1; i < header->instanceMax; i++)
978 			if (strcmp(parameter, idOfInstance(context, i)) == 0) {
979 				showInstance(context, i);
980 				return;
981 			}
982 		printf("No instance named '%s'.", parameter);
983 	}
984 }
985 
986 /*----------------------------------------------------------------------*/
exactSameVersion()987 static bool exactSameVersion() {
988 	return header->version[3] == alan.version.version
989 	       && header->version[2] == alan.version.revision
990 	       && header->version[1] == alan.version.correction
991 	       && header->version[0] == alan.version.state[0];
992 }
993 
994 
995 /*======================================================================*/
debug(CONTEXT,bool calledFromBreakpoint,int line,int fileNumber)996 void debug(CONTEXT, bool calledFromBreakpoint, int line, int fileNumber) {
997 	static bool warned = FALSE;
998 
999 	saveInfo();
1000 	g_vm->glk_set_style(style_Preformatted);
1001 
1002 	if (calledFromBreakpoint)
1003 		displaySourceLocation(line, fileNumber);
1004 	else {
1005 		if (!exactSameVersion() && !warned && !regressionTestOption) {
1006 			printf("<WARNING: You are debugging a game which has version %s.>\n",
1007 			       decodedGameVersion(header->version));
1008 			printf("<That is not exactly the same as this interpreter (%s).>\n", alan.version.string);
1009 			printf("<This might cause a lot of trouble. Cross your fingers...>\n");
1010 			warned = TRUE;
1011 		}
1012 	}
1013 
1014 	while (TRUE) {
1015 		char commandLine[200];
1016 		CALL2(readCommand, commandLine, 200)
1017 
1018 		char *command = strtok(commandLine, " ");
1019 		char commandCode = parseDebugCommand(command);
1020 
1021 		switch (commandCode) {
1022 		case AMBIGUOUS_COMMAND:
1023 			output("Ambiguous ADBG command abbreviation. ? for help.");
1024 			break;
1025 		case ACTORS_COMMAND:
1026 			handleActorsCommand(context);
1027 			break;
1028 		case BREAK_COMMAND:
1029 			handleBreakCommand(fileNumber);
1030 			break;
1031 		case CLASSES_COMMAND:
1032 			handleClassesCommand(context);
1033 			break;
1034 		case DELETE_COMMAND:
1035 			handleDeleteCommand(calledFromBreakpoint, line, fileNumber);
1036 			break;
1037 		case EVENTS_COMMAND:
1038 			showEvents(context);
1039 			break;
1040 		case EXIT_COMMAND:
1041 			debugOption = FALSE;
1042 			restoreInfo();
1043 			goto exit_debug;
1044 		case FILES_COMMAND:
1045 			listFiles();
1046 			break;
1047 		case GO_COMMAND:
1048 			restoreInfo();
1049 			goto exit_debug;
1050 		case HELP_COMMAND:
1051 			handleHelpCommand();
1052 			break;
1053 		case INSTANCES_COMMAND:
1054 			handleInstancesCommand(context);
1055 			break;
1056 		case TRACE_COMMAND:
1057 			handleTraceCommand();
1058 			break;
1059 		case INSTRUCTION_TRACE_COMMAND:
1060 			toggleInstructionTrace();
1061 			break;
1062 		case LOCATIONS_COMMAND:
1063 			handleLocationsCommand(context);
1064 			break;
1065 		case NEXT_COMMAND:
1066 			handleNextCommand(calledFromBreakpoint);
1067 			goto exit_debug;
1068 		case OBJECTS_COMMAND:
1069 			handleObjectsCommand(context);
1070 			break;
1071 		case QUIT_COMMAND:
1072 			CALL1(terminate, 0)
1073 			break;
1074 		case SECTION_TRACE_COMMAND:
1075 			toggleSectionTrace();
1076 			break;
1077 		default:
1078 			output("Unknown ADBG command. ? for help.");
1079 			break;
1080 		}
1081 	}
1082 
1083 exit_debug:
1084 	g_vm->glk_set_style(style_Normal);
1085 }
1086 
1087 
1088 /*======================================================================*/
traceSay(CONTEXT,int item)1089 void traceSay(CONTEXT, int item) {
1090 	/*
1091 	  Say something, but make sure we don't disturb anything and that it is
1092 	  shown to the player. Needed for tracing. During debugging things are
1093 	  set up to avoid this problem.
1094 	*/
1095 
1096 	saveInfo();
1097 	needSpace = FALSE;
1098 	col = 1;
1099 	if (item == 0) {
1100 		printf("$null$");
1101 	} else {
1102 		CALL1(say, item)
1103 	}
1104 
1105 	needSpace = FALSE;
1106 	col = 1;
1107 	restoreInfo();
1108 }
1109 
1110 } // End of namespace Alan3
1111 } // End of namespace Glk
1112