1 /* object.c - Object manipulation opcodes
2  *	Copyright (c) 1995-1997 Stefan Jokisch
3  *
4  * This file is part of Frotz.
5  *
6  * Frotz is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Frotz is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include "frotz.h"
22 
23 f_setup_t f_setup;
24 z_header_t z_header;
25 
26 #define MAX_OBJECT 2000
27 
28 #define O1_PARENT 4
29 #define O1_SIBLING 5
30 #define O1_CHILD 6
31 #define O1_PROPERTY_OFFSET 7
32 #define O1_SIZE 9
33 
34 #define O4_PARENT 6
35 #define O4_SIBLING 8
36 #define O4_CHILD 10
37 #define O4_PROPERTY_OFFSET 12
38 #define O4_SIZE 14
39 
40 
41 /*
42  * object_address
43  *
44  * Calculate the address of an object.
45  *
46  */
object_address(zword obj)47 static zword object_address(zword obj)
48 {
49 	/* Check object number */
50 	if (obj > ((z_header.version <= V3) ? 255 : MAX_OBJECT)) {
51 		print_string("@Attempt to address illegal object ");
52 		print_num(obj);
53 		print_string(".  This is normally fatal.");
54 		new_line();
55 		runtime_error (ERR_ILL_OBJ);
56 	}
57 
58 	/* Return object address */
59 	if (z_header.version <= V3)
60 		return z_header.objects + ((obj - 1) * O1_SIZE + 62);
61 	else
62 		return z_header.objects + ((obj - 1) * O4_SIZE + 126);
63 } /* object_address */
64 
65 
66 /*
67  * object_name
68  *
69  * Return the address of the given object's name.
70  *
71  */
object_name(zword object)72 zword object_name(zword object)
73 {
74 	zword obj_addr;
75 	zword name_addr;
76 
77 	obj_addr = object_address(object);
78 
79 	/* The object name address is found at the start of the properties */
80 	if (z_header.version <= V3)
81 		obj_addr += O1_PROPERTY_OFFSET;
82 	else
83 		obj_addr += O4_PROPERTY_OFFSET;
84 	LOW_WORD(obj_addr, name_addr)
85 
86 	return name_addr;
87 } /* object_name */
88 
89 
90 /*
91  * first_property
92  *
93  * Calculate the start address of the property list associated with
94  * an object.
95  *
96  */
first_property(zword obj)97 static zword first_property(zword obj)
98 {
99 	zword prop_addr;
100 	zbyte size;
101 
102 	/* Fetch address of object name */
103 	prop_addr = object_name (obj);
104 
105 	/* Get length of object name */
106 	LOW_BYTE(prop_addr, size)
107 
108 	/* Add name length to pointer */
109 	return prop_addr + 1 + 2 * size;
110 } /* first_property */
111 
112 
113 /*
114  * next_property
115  *
116  * Calculate the address of the next property in a property list.
117  *
118  */
next_property(zword prop_addr)119 static zword next_property(zword prop_addr)
120 {
121 	zbyte value;
122 
123 	/* Load the current property id */
124 	LOW_BYTE(prop_addr, value)
125 	prop_addr++;
126 
127 	/* Calculate the length of this property */
128 
129 	if (z_header.version <= V3)
130 		value >>= 5;
131 	else if (!(value & 0x80))
132 		value >>= 6;
133 	else {
134 		LOW_BYTE(prop_addr, value)
135 		value &= 0x3f;
136 		if (value == 0) value = 64;	/* demanded by Spec 1.0 */
137 	}
138 
139 	/* Add property length to current property pointer */
140 	return prop_addr + value + 1;
141 } /* next_property */
142 
143 
144 /*
145  * unlink_object
146  *
147  * Unlink an object from its parent and siblings.
148  *
149  */
unlink_object(zword object)150 static void unlink_object(zword object)
151 {
152 	zword obj_addr;
153 	zword parent_addr;
154 	zword sibling_addr;
155 
156 	if (object == 0) {
157 		runtime_error(ERR_REMOVE_OBJECT_0);
158 		return;
159 	}
160 
161 	obj_addr = object_address (object);
162 
163 	if (z_header.version <= V3) {
164 		zbyte parent;
165 		zbyte younger_sibling;
166 		zbyte older_sibling;
167 		zbyte zero = 0;
168 
169 		/* Get parent of object, and return if no parent */
170 		obj_addr += O1_PARENT;
171 		LOW_BYTE(obj_addr, parent)
172 		if (!parent)
173 			return;
174 
175 		/* Get (older) sibling of object and set both
176 		 * parent and sibling pointers to 0 */
177 		SET_BYTE(obj_addr, zero)
178 		obj_addr += O1_SIBLING - O1_PARENT;
179 		LOW_BYTE(obj_addr, older_sibling)
180 		SET_BYTE(obj_addr, zero)
181 
182 		/* Get first child of parent (the youngest sibling
183 		 * of the object) */
184 		parent_addr = object_address(parent) + O1_CHILD;
185 		LOW_BYTE(parent_addr, younger_sibling)
186 
187 		/* Remove object from the list of siblings */
188 		if (younger_sibling == object)
189 			SET_BYTE(parent_addr, older_sibling)
190 		else {
191 			do {
192 				sibling_addr = object_address(younger_sibling)
193 					+ O1_SIBLING;
194 				LOW_BYTE(sibling_addr, younger_sibling)
195 			} while (younger_sibling != object);
196 			SET_BYTE(sibling_addr, older_sibling)
197 		}
198 	} else {
199 		zword parent;
200 		zword younger_sibling;
201 		zword older_sibling;
202 		zword zero = 0;
203 
204 		/* Get parent of object, and return if no parent */
205 		obj_addr += O4_PARENT;
206 		LOW_WORD(obj_addr, parent)
207 		if (!parent)
208 			return;
209 
210 		/* Get (older) sibling of object and set both parent
211 		 * and sibling pointers to 0 */
212 		SET_WORD(obj_addr, zero)
213 		obj_addr += O4_SIBLING - O4_PARENT;
214 		LOW_WORD(obj_addr, older_sibling)
215 		SET_WORD(obj_addr, zero)
216 
217 		/* Get first child of parent (the youngest sibling
218 		 * of the object) */
219 		parent_addr = object_address(parent) + O4_CHILD;
220 		LOW_WORD(parent_addr, younger_sibling)
221 
222 		/* Remove object from the list of siblings */
223 		if (younger_sibling == object)
224 			SET_WORD(parent_addr, older_sibling)
225 		else {
226 			do {
227 				sibling_addr = object_address(younger_sibling)
228 					+ O4_SIBLING;
229 				LOW_WORD(sibling_addr, younger_sibling)
230 			} while (younger_sibling != object);
231 			SET_WORD(sibling_addr, older_sibling)
232 		}
233 	}
234 } /* unlink_object */
235 
236 
237 /*
238  * z_clear_attr, clear an object attribute.
239  *
240  *	zargs[0] = object
241  *	zargs[1] = number of attribute to be cleared
242  *
243  */
z_clear_attr(void)244 void z_clear_attr(void)
245 {
246 	zword obj_addr;
247 	zbyte value;
248 
249 	if (story_id == SHERLOCK)
250 		if (zargs[1] == 48)
251 			return;
252 
253 	if (zargs[1] > ((z_header.version <= V3) ? 31 : 47))
254 		runtime_error(ERR_ILL_ATTR);
255 
256 	/* If we are monitoring attribute assignment display a short note */
257 
258 	if (f_setup.attribute_assignment) {
259 		stream_mssg_on();
260 		print_string("@clear_attr ");
261 		print_object(zargs[0]);
262 		print_string(" ");
263 		print_num(zargs[1]);
264 		stream_mssg_off();
265 	}
266 
267 	if (zargs[0] == 0) {
268 		runtime_error(ERR_CLEAR_ATTR_0);
269 		return;
270 	}
271 
272 	/* Get attribute address */
273 	obj_addr = object_address(zargs[0]) + zargs[1] / 8;
274 
275 	/* Clear attribute bit */
276 	LOW_BYTE(obj_addr, value)
277 	value &= ~(0x80 >> (zargs[1] & 7));
278 	SET_BYTE(obj_addr, value)
279 } /* z_clear_attr */
280 
281 
282 /*
283  * z_jin, branch if the first object is inside the second.
284  *
285  *	zargs[0] = first object
286  *	zargs[1] = second object
287  *
288  */
z_jin(void)289 void z_jin(void)
290 {
291 	zword obj_addr;
292 
293 	/* If we are monitoring object locating display a short note */
294 	if (f_setup.object_locating) {
295 		stream_mssg_on();
296 		print_string("@jin ");
297 		print_object(zargs[0]);
298 		print_string(" ");
299 		print_object(zargs[1]);
300 		stream_mssg_off();
301 	}
302 
303 	if (zargs[0] == 0) {
304 		runtime_error(ERR_JIN_0);
305 		branch(0 == zargs[1]);
306 		return;
307 	}
308 
309 	obj_addr = object_address(zargs[0]);
310 
311 	if (z_header.version <= V3) {
312 		zbyte parent;
313 
314 		/* Get parent id from object */
315 		obj_addr += O1_PARENT;
316 		LOW_BYTE(obj_addr, parent)
317 
318 		/* Branch if the parent is obj2 */
319 		branch (parent == zargs[1]);
320 	} else {
321 		zword parent;
322 		/* Get parent id from object */
323 		obj_addr += O4_PARENT;
324 		LOW_WORD(obj_addr, parent)
325 
326 		/* Branch if the parent is obj2 */
327 		branch (parent == zargs[1]);
328 	}
329 } /* z_jin */
330 
331 
332 /*
333  * z_get_child, store the child of an object.
334  *
335  *	zargs[0] = object
336  *
337  */
z_get_child(void)338 void z_get_child(void)
339 {
340 	zword obj_addr;
341 
342 	/* If we are monitoring object locating display a short note */
343 
344 	if (f_setup.object_locating) {
345 		stream_mssg_on();
346 		print_string("@get_child ");
347 		print_object(zargs[0]);
348 		stream_mssg_off();
349 	}
350 
351 	if (zargs[0] == 0) {
352 		runtime_error(ERR_GET_CHILD_0);
353 		store(0);
354 		branch(FALSE);
355 		return;
356 	}
357 
358 	obj_addr = object_address (zargs[0]);
359 
360 	if (z_header.version <= V3) {
361 		zbyte child;
362 
363 		/* Get child id from object */
364 		obj_addr += O1_CHILD;
365 		LOW_BYTE(obj_addr, child)
366 
367 		/* Store child id and branch */
368 		store(child);
369 		branch(child);
370 	} else {
371 		zword child;
372 
373 		/* Get child id from object */
374 		obj_addr += O4_CHILD;
375 		LOW_WORD(obj_addr, child)
376 
377 		/* Store child id and branch */
378 		store(child);
379 		branch(child);
380 	}
381 } /* z_get_child */
382 
383 
384 /*
385  * z_get_next_prop, store the number of the first or next property.
386  *
387  *	zargs[0] = object
388  *	zargs[1] = address of current property (0 gets the first property)
389  *
390  */
z_get_next_prop(void)391 void z_get_next_prop(void)
392 {
393 	zword prop_addr;
394 	zbyte value;
395 	zbyte mask;
396 
397 	if (zargs[0] == 0) {
398 		runtime_error(ERR_GET_NEXT_PROP_0);
399 		store(0);
400 		return;
401 	}
402 
403 	/* Property id is in bottom five (six) bits */
404 	mask = (z_header.version <= V3) ? 0x1f : 0x3f;
405 
406 	/* Load address of first property */
407 	prop_addr = first_property(zargs[0]);
408 
409 	if (zargs[1] != 0) {
410 		/* Scan down the property list */
411 		do {
412 			LOW_BYTE(prop_addr, value)
413 			prop_addr = next_property(prop_addr);
414 		} while ((value & mask) > zargs[1]);
415 
416 		/* Exit if the property does not exist */
417 		if ((value & mask) != zargs[1])
418 			runtime_error(ERR_NO_PROP);
419 	}
420 
421 	/* Return the property id */
422 	LOW_BYTE(prop_addr, value)
423 	store((zword) (value & mask));
424 } /* z_get_next_prop */
425 
426 
427 /*
428  * z_get_parent, store the parent of an object.
429  *
430  *	zargs[0] = object
431  *
432  */
z_get_parent(void)433 void z_get_parent(void)
434 {
435 	zword obj_addr;
436 
437 	/* If we are monitoring object locating display a short note */
438 
439 	if (f_setup.object_locating) {
440 		stream_mssg_on();
441 		print_string("@get_parent ");
442 		print_object(zargs[0]);
443 		stream_mssg_off();
444 	}
445 
446 	if (zargs[0] == 0) {
447 		runtime_error(ERR_GET_PARENT_0);
448 		store(0);
449 		return;
450 	}
451 
452 	obj_addr = object_address (zargs[0]);
453 
454 	if (z_header.version <= V3) {
455 		zbyte parent;
456 
457 		/* Get parent id from object */
458 		obj_addr += O1_PARENT;
459 		LOW_BYTE(obj_addr, parent)
460 
461 		/* Store parent */
462 		store(parent);
463 	} else {
464 		zword parent;
465 
466 		/* Get parent id from object */
467 		obj_addr += O4_PARENT;
468 		LOW_WORD (obj_addr, parent)
469 
470 		/* Store parent */
471 		store (parent);
472 	}
473 } /* z_get_parent */
474 
475 
476 /*
477  * z_get_prop, store the value of an object property.
478  *
479  *	zargs[0] = object
480  *	zargs[1] = number of property to be examined
481  *
482  */
z_get_prop(void)483 void z_get_prop(void)
484 {
485 	zword prop_addr;
486 	zword wprop_val;
487 	zbyte bprop_val;
488 	zbyte value;
489 	zbyte mask;
490 
491 	if (zargs[0] == 0) {
492 		runtime_error(ERR_GET_PROP_0);
493 		store(0);
494 		return;
495 	}
496 
497 	/* Property id is in bottom five (six) bits */
498 	mask = (z_header.version <= V3) ? 0x1f : 0x3f;
499 
500 	/* Load address of first property */
501 	prop_addr = first_property(zargs[0]);
502 
503 	/* Scan down the property list */
504 	for (;;) {
505 		LOW_BYTE(prop_addr, value)
506 		if ((value & mask) <= zargs[1])
507 			break;
508 		prop_addr = next_property(prop_addr);
509 	}
510 
511 	if ((value & mask) == zargs[1]) { 	/* property found */
512 		/* Load property (byte or word sized) */
513 		prop_addr++;
514 		if ((z_header.version <= V3 && !(value & 0xe0)) ||
515 		    (z_header.version >= V4 && !(value & 0xc0))) {
516 			LOW_BYTE(prop_addr, bprop_val)
517 			wprop_val = bprop_val;
518 		} else
519 			LOW_WORD(prop_addr, wprop_val)
520 	} else {	/* property not found */
521 		/* Load default value */
522 		prop_addr = z_header.objects + 2 * (zargs[1] - 1);
523 		LOW_WORD(prop_addr, wprop_val)
524 	}
525 	/* Store the property value */
526 	store (wprop_val);
527 } /* z_get_prop */
528 
529 
530 /*
531  * z_get_prop_addr, store the address of an object property.
532  *
533  *	zargs[0] = object
534  *	zargs[1] = number of property to be examined
535  *
536  */
z_get_prop_addr(void)537 void z_get_prop_addr(void)
538 {
539 	zword prop_addr;
540 	zbyte value;
541 	zbyte mask;
542 
543 	if (zargs[0] == 0) {
544 		runtime_error(ERR_GET_PROP_ADDR_0);
545 		store(0);
546 		return;
547 	}
548 
549 	if (story_id == BEYOND_ZORK) {
550 		if (zargs[0] > MAX_OBJECT) {
551 			store (0);
552 			return;
553 		}
554 	}
555 
556 	/* Property id is in bottom five (six) bits */
557 	mask = (z_header.version <= V3) ? 0x1f : 0x3f;
558 
559 	/* Load address of first property */
560 	prop_addr = first_property(zargs[0]);
561 
562 	/* Scan down the property list */
563 	for (;;) {
564 		LOW_BYTE(prop_addr, value)
565 		if ((value & mask) <= zargs[1])
566 			break;
567 		prop_addr = next_property(prop_addr);
568 	}
569 
570 	/* Calculate the property address or return zero */
571 	if ((value & mask) == zargs[1]) {
572 		if (z_header.version >= V4 && (value & 0x80))
573 			prop_addr++;
574 		store ((zword) (prop_addr + 1));
575 	} else
576 		store (0);
577 } /* z_get_prop_addr */
578 
579 
580 /*
581  * z_get_prop_len, store the length of an object property.
582  *
583  * 	zargs[0] = address of property to be examined
584  *
585  */
z_get_prop_len(void)586 void z_get_prop_len(void)
587 {
588 	zword addr;
589 	zbyte value;
590 
591 	if (zargs[0] == 0) {
592 		store(0); /* demanded by Spec 1.1 */
593 		return;
594 	}
595 
596 	/* Back up the property pointer to the property id */
597 	addr = zargs[0] - 1;
598 	LOW_BYTE(addr, value)
599 
600 	/* Calculate length of property */
601 	if (z_header.version <= V3)
602 		value = (value >> 5) + 1;
603 	else if (!(value & 0x80))
604 		value = (value >> 6) + 1;
605 	else {
606 		value &= 0x3f;
607 		if (value == 0) value = 64;	/* demanded by Spec 1.0 */
608 
609 	}
610 	/* Store length of property */
611 	store(value);
612 } /* z_get_prop_len */
613 
614 
615 /*
616  * z_get_sibling, store the sibling of an object.
617  *
618  *	zargs[0] = object
619  *
620  */
z_get_sibling(void)621 void z_get_sibling(void)
622 {
623 	zword obj_addr;
624 
625 	if (zargs[0] == 0) {
626 		runtime_error(ERR_GET_SIBLING_0);
627 		store(0);
628 		branch(FALSE);
629 		return;
630 	}
631 
632 	obj_addr = object_address(zargs[0]);
633 
634 	if (z_header.version <= V3) {
635 		zbyte sibling;
636 
637 		/* Get sibling id from object */
638 		obj_addr += O1_SIBLING;
639 		LOW_BYTE(obj_addr, sibling)
640 
641 		/* Store sibling and branch */
642 		store(sibling);
643 		branch(sibling);
644 	} else {
645 		zword sibling;
646 
647 		/* Get sibling id from object */
648 		obj_addr += O4_SIBLING;
649 		LOW_WORD(obj_addr, sibling)
650 
651 		/* Store sibling and branch */
652 		store(sibling);
653 		branch(sibling);
654 	}
655 
656 } /* z_get_sibling */
657 
658 
659 /*
660  * z_insert_obj, make an object the first child of another object.
661  *
662  *	zargs[0] = object to be moved
663  *	zargs[1] = destination object
664  *
665  */
z_insert_obj(void)666 void z_insert_obj(void)
667 {
668 	zword obj1 = zargs[0];
669 	zword obj2 = zargs[1];
670 	zword obj1_addr;
671 	zword obj2_addr;
672 
673 	/* If we are monitoring object movements display a short note */
674 	if (f_setup.object_movement) {
675 		stream_mssg_on();
676 		print_string("@move_obj ");
677 		print_object(obj1);
678 		print_string(" ");
679 		print_object(obj2);
680 		stream_mssg_off();
681 	}
682 
683 	if (obj1 == 0) {
684 		runtime_error(ERR_MOVE_OBJECT_0);
685 		return;
686 	}
687 
688 	if (obj2 == 0) {
689 		runtime_error(ERR_MOVE_OBJECT_TO_0);
690 		return;
691 	}
692 
693 	/* Get addresses of both objects */
694 	obj1_addr = object_address(obj1);
695 	obj2_addr = object_address(obj2);
696 
697 	/* Remove object 1 from current parent */
698 	unlink_object(obj1);
699 
700 	/* Make object 1 first child of object 2 */
701 	if (z_header.version <= V3) {
702 		zbyte child;
703 
704 		obj1_addr += O1_PARENT;
705 		SET_BYTE(obj1_addr, obj2)
706 		obj2_addr += O1_CHILD;
707 		LOW_BYTE(obj2_addr, child)
708 		SET_BYTE(obj2_addr, obj1)
709 		obj1_addr += O1_SIBLING - O1_PARENT;
710 		SET_BYTE(obj1_addr, child)
711 	} else {
712 		zword child;
713 
714 		obj1_addr += O4_PARENT;
715 		SET_WORD(obj1_addr, obj2)
716 		obj2_addr += O4_CHILD;
717 		LOW_WORD(obj2_addr, child)
718 		SET_WORD(obj2_addr, obj1)
719 		obj1_addr += O4_SIBLING - O4_PARENT;
720 		SET_WORD(obj1_addr, child)
721 	}
722 } /* z_insert_obj */
723 
724 
725 /*
726  * z_put_prop, set the value of an object property.
727  *
728  *	zargs[0] = object
729  *	zargs[1] = number of property to set
730  *	zargs[2] = value to set property to
731  *
732  */
z_put_prop(void)733 void z_put_prop(void)
734 {
735 	zword prop_addr;
736 	zword value;
737 	zbyte mask;
738 
739 	if (zargs[0] == 0) {
740 		runtime_error(ERR_PUT_PROP_0);
741 		return;
742 	}
743 
744 	/* Property id is in bottom five or six bits */
745 	mask = (z_header.version <= V3) ? 0x1f : 0x3f;
746 
747 	/* Load address of first property */
748 	prop_addr = first_property(zargs[0]);
749 
750 	/* Scan down the property list */
751 	for (;;) {
752 		LOW_BYTE(prop_addr, value)
753 		if ((value & mask) <= zargs[1])
754 			break;
755 		prop_addr = next_property (prop_addr);
756 	}
757 
758 	/* Exit if the property does not exist */
759 	if ((value & mask) != zargs[1])
760 		runtime_error(ERR_NO_PROP);
761 
762 	/* Store the new property value (byte or word sized) */
763 	prop_addr++;
764 
765 	if ((z_header.version <= V3 && !(value & 0xe0)) ||
766 	    (z_header.version >= V4 && !(value & 0xc0))) {
767 		zbyte v = zargs[2];
768 		SET_BYTE(prop_addr, v)
769 	} else {
770 		zword v = zargs[2];
771 		SET_WORD(prop_addr, v)
772 	}
773 } /* z_put_prop */
774 
775 
776 /*
777  * z_remove_obj, unlink an object from its parent and siblings.
778  *
779  *	zargs[0] = object
780  *
781  */
z_remove_obj(void)782 void z_remove_obj (void)
783 {
784 	/* If we are monitoring object movements display a short note */
785 	if (f_setup.object_movement) {
786 		stream_mssg_on();
787 		print_string("@remove_obj ");
788 		print_object(zargs[0]);
789 		stream_mssg_off();
790 	}
791 
792 	/* Call unlink_object to do the job */
793 	unlink_object(zargs[0]);
794 } /* z_remove_obj */
795 
796 
797 /*
798  * z_set_attr, set an object attribute.
799  *
800  *	zargs[0] = object
801  *	zargs[1] = number of attribute to set
802  *
803  */
z_set_attr(void)804 void z_set_attr(void)
805 {
806     zword obj_addr;
807     zbyte value;
808 
809 	if (story_id == SHERLOCK)
810 		if (zargs[1] == 48)
811 			return;
812 
813 	if (zargs[1] > ((z_header.version <= V3) ? 31 : 47))
814 		runtime_error(ERR_ILL_ATTR);
815 
816 	/* If we are monitoring attribute assignment display a short note */
817 	if (f_setup.attribute_assignment) {
818 		stream_mssg_on();
819 		print_string("@set_attr ");
820 		print_object(zargs[0]);
821 		print_string(" ");
822 		print_num(zargs[1]);
823 		stream_mssg_off();
824 	}
825 
826 	if (zargs[0] == 0) {
827 		runtime_error(ERR_SET_ATTR_0);
828 		return;
829 	}
830 
831 	/* Get attribute address */
832 	obj_addr = object_address(zargs[0]) + zargs[1] / 8;
833 
834 	/* Load attribute byte */
835 	LOW_BYTE(obj_addr, value)
836 
837 	/* Set attribute bit */
838 	value |= 0x80 >> (zargs[1] & 7);
839 
840 	/* Store attribute byte */
841 	SET_BYTE(obj_addr, value)
842 } /* z_set_attr */
843 
844 
845 /*
846  * z_test_attr, branch if an object attribute is set.
847  *
848  *	zargs[0] = object
849  *	zargs[1] = number of attribute to test
850  *
851  */
z_test_attr(void)852 void z_test_attr(void)
853 {
854 	zword obj_addr;
855 	zbyte value;
856 
857 	if (zargs[1] > ((z_header.version <= V3) ? 31 : 47))
858 		runtime_error(ERR_ILL_ATTR);
859 
860 	/* If we are monitoring attribute testing display a short note */
861 	if (f_setup.attribute_testing) {
862 		stream_mssg_on();
863 		print_string("@test_attr ");
864 		print_object(zargs[0]);
865 		print_string(" ");
866 		print_num(zargs[1]);
867 		stream_mssg_off();
868 	}
869 	if (zargs[0] == 0) {
870 		runtime_error(ERR_TEST_ATTR_0);
871 		branch(FALSE);
872 		return;
873 	}
874 
875 	/* Get attribute address */
876 	obj_addr = object_address(zargs[0]) + zargs[1] / 8;
877 
878 	/* Load attribute byte */
879 	LOW_BYTE(obj_addr, value)
880 
881 	/* Test attribute */
882 	branch (value & (0x80 >> (zargs[1] & 7)));
883 
884 } /* z_test_attr */
885