1 // ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code.
2 // Copyright (C) 1998-2020 Marco Baye
3 // Have a look at "acme.c" for further info
4 //
5 // Output stuff
6 // 24 Nov 2007	Added possibility to suppress segment overlap warnings
7 // 25 Sep 2011	Fixed bug in !to (colons in filename could be interpreted as EOS)
8 //  5 Mar 2014	Fixed bug where setting *>0xffff resulted in hangups.
9 // 19 Nov 2014	Merged Johann Klasek's report listing generator patch
10 // 22 Sep 2015	Added big-endian output functions
11 // 20 Apr 2019	Prepared for "make segment overlap warnings into errors" later on
12 #include "output.h"
13 #include <stdlib.h>
14 #include <string.h>	// for memset()
15 #include "acme.h"
16 #include "alu.h"
17 #include "config.h"
18 #include "cpu.h"
19 #include "dynabuf.h"
20 #include "global.h"
21 #include "input.h"
22 #include "platform.h"
23 #include "tree.h"
24 
25 
26 // constants
27 #define NO_SEGMENT_START	(-1)	// invalid value to signal "not in a segment"
28 
29 
30 // structure for linked list of segment data
31 struct segment {
32 	struct segment	*next,
33 			*prev;
34 	intval_t	start,
35 			length;
36 };
37 
38 // structure for all output stuff:
39 struct output {
40 	// output buffer stuff
41 	intval_t	bufsize;	// either 64 KiB or 16 MiB
42 	char		*buffer;	// holds assembled code
43 	intval_t	write_idx;	// index of next write
44 	intval_t	lowest_written;		// smallest address used
45 	intval_t	highest_written;	// largest address used
46 	boolean		initvalue_set;
47 	struct {
48 		intval_t	start;	// start of current segment (or NO_SEGMENT_START)
49 		intval_t	max;	// highest address segment may use
50 		bits		flags;	// segment flags ("overlay" and "invisible", see header file)
51 		struct segment	list_head;	// head element of doubly-linked ring list
52 	} segment;
53 	char		xor;		// output modifier
54 };
55 
56 // for offset assembly:
57 static struct pseudopc	*pseudopc_current_context;	// current struct (NULL when not in pseudopc block)
58 
59 
60 // variables
61 static struct output	default_output;
62 static struct output	*out	= &default_output;
63 // FIXME - make static
64 struct vcpu		CPU_state;	// current CPU state
65 
66 // FIXME - move output _file_ stuff to some other .c file!
67 // possible file formats
68 enum output_format {
69 	OUTPUT_FORMAT_UNSPECIFIED,	// default (uses "plain" actually)
70 	OUTPUT_FORMAT_APPLE,		// load address, length, code
71 	OUTPUT_FORMAT_CBM,		// load address, code (default for "!to" pseudo opcode)
72 	OUTPUT_FORMAT_PLAIN		// code only
73 };
74 // predefined stuff
75 // tree to hold output formats (FIXME - a tree for three items, really?)
76 static struct ronode	file_format_tree[]	= {
77 	PREDEF_START,
78 #define KNOWN_FORMATS	"'plain', 'cbm', 'apple'"	// shown in CLI error message for unknown formats
79 	PREDEFNODE("apple",	OUTPUT_FORMAT_APPLE),
80 	PREDEFNODE("cbm",	OUTPUT_FORMAT_CBM),
81 //	PREDEFNODE("o65",	OUTPUT_FORMAT_O65),
82 	PREDEF_END("plain",	OUTPUT_FORMAT_PLAIN),
83 	//    ^^^^ this marks the last element
84 };
85 // chosen file format
86 static enum output_format	output_format	= OUTPUT_FORMAT_UNSPECIFIED;
87 const char			outputfile_formats[]	= KNOWN_FORMATS;	// string to show if outputfile_set_format() returns nonzero
88 
89 
90 // report binary output
report_binary(char value)91 static void report_binary(char value)
92 {
93 	if (report->bin_used == 0)
94 		report->bin_address = out->write_idx;	// remember address at start of line
95 	if (report->bin_used < REPORT_BINBUFSIZE)
96 		report->bin_buf[report->bin_used++] = value;
97 }
98 
99 
100 // set up new out->segment.max value according to the given address.
101 // just find the next segment start and subtract 1.
find_segment_max(intval_t new_pc)102 static void find_segment_max(intval_t new_pc)
103 {
104 	struct segment	*test_segment	= out->segment.list_head.next;
105 
106 	// search for smallest segment start address that
107 	// is larger than given address
108 	// use list head as sentinel
109 // FIXME - if +1 overflows intval_t, we have an infinite loop!
110 	out->segment.list_head.start = new_pc + 1;
111 	while (test_segment->start <= new_pc)
112 		test_segment = test_segment->next;
113 	if (test_segment == &out->segment.list_head)
114 		out->segment.max = out->bufsize - 1;
115 	else
116 		out->segment.max = test_segment->start - 1;	// last free address available
117 }
118 
119 
120 //
border_crossed(int current_offset)121 static void border_crossed(int current_offset)
122 {
123 	if (current_offset >= out->bufsize)
124 		Throw_serious_error("Produced too much code.");
125 	// TODO - get rid of FIRST_PASS condition, because user can suppress these warnings if they want
126 	if (FIRST_PASS) {
127 		// TODO: make warn/err an arg for a general "Throw" function
128 		if (config.segment_warning_is_error)
129 			Throw_error("Segment reached another one, overwriting it.");
130 		else
131 			Throw_warning("Segment reached another one, overwriting it.");
132 		find_segment_max(current_offset + 1);	// find new (next) limit
133 	}
134 }
135 
136 
137 // function ptr to write byte into output buffer (might point to real fn or error trigger)
138 void (*Output_byte)(intval_t byte);
139 
140 
141 // send low byte to output buffer, automatically increasing program counter
real_output(intval_t byte)142 static void real_output(intval_t byte)
143 {
144 	// CAUTION - there are two copies of these checks!
145 	// TODO - add additional check for current segment's "limit" value
146 	// did we reach next segment?
147 	if (out->write_idx > out->segment.max)
148 		border_crossed(out->write_idx);
149 	// new minimum address?
150 	if (out->write_idx < out->lowest_written)
151 		out->lowest_written = out->write_idx;
152 	// new maximum address?
153 	if (out->write_idx > out->highest_written)
154 		out->highest_written = out->write_idx;
155 	// write byte and advance ptrs
156 	if (report->fd)
157 		report_binary(byte & 0xff);	// file for reporting, taking also CPU_2add
158 	out->buffer[out->write_idx++] = (byte & 0xff) ^ out->xor;
159 	++CPU_state.add_to_pc;
160 }
161 
162 
163 // throw error (pc undefined) and use fake pc from now on
no_output(intval_t byte)164 static void no_output(intval_t byte)
165 {
166 	Throw_error(exception_pc_undefined);
167 	// now change fn ptr to not complain again.
168 	Output_byte = real_output;
169 	Output_byte(byte);	// try again
170 }
171 
172 
173 // skip over some bytes in output buffer without starting a new segment
174 // (used by "!skip", and also called by "!binary" if really calling
175 // Output_byte would be a waste of time)
output_skip(int size)176 void output_skip(int size)
177 {
178 	if (size < 1) {
179 		// FIXME - ok for zero, but why is there no error message
180 		// output for negative values?
181 		return;
182 	}
183 
184 	// check whether ptr undefined
185 	if (Output_byte == no_output) {
186 		Output_byte(0);	// trigger error with a dummy byte
187 		--size;	// fix amount to cater for dummy byte
188 	}
189 	// CAUTION - there are two copies of these checks!
190 	// TODO - add additional check for current segment's "limit" value
191 	// did we reach next segment?
192 	if (out->write_idx + size - 1 > out->segment.max)
193 		border_crossed(out->write_idx + size - 1);
194 	// new minimum address?
195 	if (out->write_idx < out->lowest_written)
196 		out->lowest_written = out->write_idx;
197 	// new maximum address?
198 	if (out->write_idx + size - 1 > out->highest_written)
199 		out->highest_written = out->write_idx + size - 1;
200 	// advance ptrs
201 	out->write_idx += size;
202 	CPU_state.add_to_pc += size;
203 }
204 
205 
206 // fill output buffer with given byte value
fill_completely(char value)207 static void fill_completely(char value)
208 {
209 	memset(out->buffer, value, out->bufsize);
210 }
211 
212 
213 // define default value for empty memory ("!initmem" pseudo opcode)
214 // returns zero if ok, nonzero if already set
output_initmem(char content)215 int output_initmem(char content)
216 {
217 	// if MemInit flag is already set, complain
218 	if (out->initvalue_set) {
219 		Throw_warning("Memory already initialised.");
220 		return 1;	// failed
221 	}
222 	// set MemInit flag
223 	out->initvalue_set = TRUE;
224 	// init memory
225 	fill_completely(content);
226 	// enforce another pass
227 	if (pass.undefined_count == 0)
228 		pass.undefined_count = 1;
229 	//if (pass.needvalue_count == 0)	FIXME - use? instead or additionally?
230 	//	pass.needvalue_count = 1;
231 // FIXME - enforcing another pass is not needed if there hasn't been any
232 // output yet. But that's tricky to detect without too much overhead.
233 // The old solution was to add &&(out->lowest_written < out->highest_written+1) to "if" above
234 	return 0;	// ok
235 }
236 
237 
238 // try to set output format held in DynaBuf. Returns zero on success.
outputfile_set_format(void)239 int outputfile_set_format(void)
240 {
241 	void	*node_body;
242 
243 	// perform lookup
244 	if (!Tree_easy_scan(file_format_tree, &node_body, GlobalDynaBuf))
245 		return 1;
246 
247 	output_format = (enum output_format) node_body;
248 	return 0;
249 }
250 
251 // if file format was already chosen, returns zero.
252 // if file format isn't set, chooses CBM and returns 1.
outputfile_prefer_cbm_format(void)253 int outputfile_prefer_cbm_format(void)
254 {
255 	if (output_format != OUTPUT_FORMAT_UNSPECIFIED)
256 		return 0;
257 
258 	output_format = OUTPUT_FORMAT_CBM;
259 	return 1;
260 }
261 
262 // select output file ("!to" pseudo opcode)
263 // returns zero on success, nonzero if already set
outputfile_set_filename(void)264 int outputfile_set_filename(void)
265 {
266 	// if output file already chosen, complain and exit
267 	if (output_filename) {
268 		Throw_warning("Output file already chosen.");
269 		return 1;	// failed
270 	}
271 
272 	// get malloc'd copy of filename
273 	output_filename = DynaBuf_get_copy(GlobalDynaBuf);
274 	return 0;	// ok
275 }
276 
277 
278 // init output struct (done later)
Output_init(signed long fill_value,boolean use_large_buf)279 void Output_init(signed long fill_value, boolean use_large_buf)
280 {
281 	out->bufsize = use_large_buf ? 0x1000000 : 0x10000;
282 	out->buffer = safe_malloc(out->bufsize);
283 	if (fill_value == MEMINIT_USE_DEFAULT) {
284 		fill_value = FILLVALUE_INITIAL;
285 		out->initvalue_set = FALSE;
286 	} else {
287 		out->initvalue_set = TRUE;
288 	}
289 	// init output buffer (fill memory with initial value)
290 	fill_completely(fill_value & 0xff);
291 	// init ring list of segments
292 	out->segment.list_head.next = &out->segment.list_head;
293 	out->segment.list_head.prev = &out->segment.list_head;
294 }
295 
296 
297 // dump used portion of output buffer into output file
Output_save_file(FILE * fd)298 void Output_save_file(FILE *fd)
299 {
300 	intval_t	start,
301 			amount;
302 
303 	if (out->highest_written < out->lowest_written) {
304 		// nothing written
305 		start = 0;	// I could try to use some segment start, but what for?
306 		amount = 0;
307 	} else {
308 		start = out->lowest_written;
309 		amount = out->highest_written - start + 1;
310 	}
311 	if (config.process_verbosity)
312 		printf("Saving %ld (0x%lx) bytes (0x%lx - 0x%lx exclusive).\n",
313 			amount, amount, start, start + amount);
314 	// output file header according to file format
315 	switch (output_format) {
316 	case OUTPUT_FORMAT_APPLE:
317 		PLATFORM_SETFILETYPE_APPLE(output_filename);
318 		// output 16-bit load address in little-endian byte order
319 		putc(start & 255, fd);
320 		putc(start >> 8, fd);
321 		// output 16-bit length in little-endian byte order
322 		putc(amount & 255, fd);
323 		putc(amount >> 8, fd);
324 		break;
325 	case OUTPUT_FORMAT_UNSPECIFIED:
326 	case OUTPUT_FORMAT_PLAIN:
327 		PLATFORM_SETFILETYPE_PLAIN(output_filename);
328 		break;
329 	case OUTPUT_FORMAT_CBM:
330 		PLATFORM_SETFILETYPE_CBM(output_filename);
331 		// output 16-bit load address in little-endian byte order
332 		putc(start & 255, fd);
333 		putc(start >> 8, fd);
334 	}
335 	// dump output buffer to file
336 	fwrite(out->buffer + start, amount, 1, fd);
337 }
338 
339 
340 // link segment data into segment ring
link_segment(intval_t start,intval_t length)341 static void link_segment(intval_t start, intval_t length)
342 {
343 	struct segment	*new_segment,
344 			*test_segment	= out->segment.list_head.next;
345 
346 	// init new segment
347 	new_segment = safe_malloc(sizeof(*new_segment));
348 	new_segment->start = start;
349 	new_segment->length = length;
350 	// use ring head as sentinel
351 	out->segment.list_head.start = start;
352 	out->segment.list_head.length = length + 1;	// +1 to make sure sentinel exits loop
353 	// walk ring to find correct spot
354 	while ((test_segment->start < new_segment->start)
355 	|| ((test_segment->start == new_segment->start) && (test_segment->length < new_segment->length)))
356 		test_segment = test_segment->next;
357 	// link into ring
358 	new_segment->next = test_segment;
359 	new_segment->prev = test_segment->prev;
360 	new_segment->next->prev = new_segment;
361 	new_segment->prev->next = new_segment;
362 }
363 
364 
365 // check whether given PC is inside segment.
366 // only call in first pass, otherwise too many warnings might be thrown	(TODO - still?)
check_segment(intval_t new_pc)367 static void check_segment(intval_t new_pc)
368 {
369 	struct segment	*test_segment	= out->segment.list_head.next;
370 
371 	// use list head as sentinel
372 	out->segment.list_head.start = new_pc + 1;	// +1 to make sure sentinel exits loop
373 	out->segment.list_head.length = 1;
374 	// search ring for matching entry
375 	while (test_segment->start <= new_pc) {
376 		if ((test_segment->start + test_segment->length) > new_pc) {
377 			// TODO - include overlap size in error message!
378 			if (config.segment_warning_is_error)
379 				Throw_error("Segment starts inside another one, overwriting it.");
380 			else
381 				Throw_warning("Segment starts inside another one, overwriting it.");
382 			return;
383 		}
384 
385 		test_segment = test_segment->next;
386 	}
387 }
388 
389 
390 // clear segment list and disable output
Output_passinit(void)391 void Output_passinit(void)
392 {
393 //	struct segment	*temp;
394 
395 //FIXME - why clear ring list in every pass?
396 // Because later pass shouldn't complain about overwriting the same segment from earlier pass!
397 // Currently this does not happen because segment warnings are only generated in first pass. FIXME!
398 	// delete segment list (and free blocks)
399 //	while ((temp = segment_list)) {
400 //		segment_list = segment_list->next;
401 //		free(temp);
402 //	}
403 
404 	// invalidate start and end (first byte actually written will fix them)
405 	out->lowest_written = out->bufsize - 1;
406 	out->highest_written = 0;
407 	// deactivate output - any byte written will trigger error:
408 	Output_byte = no_output;
409 	out->write_idx = 0;	// same as pc on pass init!
410 	out->segment.start = NO_SEGMENT_START;	// TODO - "no active segment" could be made a segment flag!
411 	out->segment.max = out->bufsize - 1;	// TODO - use end of bank?
412 	out->segment.flags = 0;
413 	out->xor = 0;
414 
415 	//vcpu stuff:
416 	CPU_state.pc.ntype = NUMTYPE_UNDEFINED;	// not defined yet
417 	CPU_state.pc.flags = 0;
418 	// FIXME - number type is "undefined", but still the intval 0 below will
419 	// be used to calculate diff when pc is first set.
420 	CPU_state.pc.val.intval = 0;	// same as output's write_idx on pass init
421 	CPU_state.add_to_pc = 0;	// increase PC by this at end of statement
422 
423 	// pseudopc stuff:
424 	pseudopc_current_context = NULL;
425 }
426 
427 
428 // show start and end of current segment
429 // called whenever a new segment begins, and at end of pass.
Output_end_segment(void)430 void Output_end_segment(void)
431 {
432 	intval_t	amount;
433 
434 	// in later passes, ignore completely
435 	if (!FIRST_PASS)
436 		return;
437 
438 	// if there is no segment, there is nothing to do
439 	if (out->segment.start == NO_SEGMENT_START)
440 		return;
441 
442 	// ignore "invisible" segments
443 	if (out->segment.flags & SEGMENT_FLAG_INVISIBLE)
444 		return;
445 
446 	// ignore empty segments
447 	amount = out->write_idx - out->segment.start;
448 	if (amount == 0)
449 		return;
450 
451 	// link to segment list
452 	link_segment(out->segment.start, amount);
453 	// announce
454 	if (config.process_verbosity > 1)
455 		// TODO - change output to start, limit, size, name:
456 		// TODO - output hex numbers as %04x? What about limit 0x10000?
457 		printf("Segment size is %ld (0x%lx) bytes (0x%lx - 0x%lx exclusive).\n",
458 			amount, amount, out->segment.start, out->write_idx);
459 }
460 
461 
462 // change output pointer and enable output
463 // TODO - this only gets called from vcpu_set_pc so could be made static!
Output_start_segment(intval_t address_change,bits segment_flags)464 void Output_start_segment(intval_t address_change, bits segment_flags)
465 {
466 	// properly finalize previous segment (link to list, announce)
467 	Output_end_segment();
468 
469 	// calculate start of new segment
470 	out->write_idx = (out->write_idx + address_change) & (out->bufsize - 1);
471 	out->segment.start = out->write_idx;
472 	out->segment.flags = segment_flags;
473 	// allow writing to output buffer
474 	Output_byte = real_output;
475 	// in first pass, check for other segments and maybe issue warning
476 	// TODO - remove FIRST_PASS condition
477 	if (FIRST_PASS) {
478 		if (!(segment_flags & SEGMENT_FLAG_OVERLAY))
479 			check_segment(out->segment.start);
480 		find_segment_max(out->segment.start);
481 	}
482 }
483 
484 
output_get_xor(void)485 char output_get_xor(void)
486 {
487 	return out->xor;
488 }
output_set_xor(char xor)489 void output_set_xor(char xor)
490 {
491 	out->xor = xor;
492 }
493 
494 
495 // set program counter to defined value (FIXME - allow for undefined!)
496 // if start address was given on command line, main loop will call this before each pass.
497 // in addition to that, it will be called on each "*= VALUE".
vcpu_set_pc(intval_t new_pc,bits segment_flags)498 void vcpu_set_pc(intval_t new_pc, bits segment_flags)
499 {
500 	intval_t	pc_change;
501 
502 	// support stupidly bad, old, ancient, deprecated, obsolete behaviour:
503 	if (pseudopc_current_context != NULL) {
504 		if (config.wanted_version < VER_SHORTER_SETPC_WARNING) {
505 			Throw_warning("Offset assembly still active at end of segment. Switched it off.");
506 			pseudopc_end_all();
507 		} else if (config.wanted_version < VER_DISABLED_OBSOLETE_STUFF) {
508 			Throw_warning("Offset assembly still active at end of segment.");
509 			pseudopc_end_all();	// warning no longer said it
510 			// would switch off, but still did. nevertheless, there
511 			// is something different to older versions: when the
512 			// closing '}' or !realpc is encountered, _really_ weird
513 			// stuff happens! i see no reason to try to mimic that.
514 		}
515 	}
516 	pc_change = new_pc - CPU_state.pc.val.intval;
517 	CPU_state.pc.val.intval = new_pc;	// FIXME - oversized values are accepted without error and will be wrapped at end of statement!
518 	CPU_state.pc.ntype = NUMTYPE_INT;	// FIXME - remove when allowing undefined!
519 	CPU_state.pc.addr_refs = 1;	// yes, PC counts as address
520 	// now tell output buffer to start a new segment
521 	Output_start_segment(pc_change, segment_flags);
522 }
523 /*
524 TODO - overhaul program counter and memory pointer stuff:
525 general stuff: PC and mem ptr might be marked as "undefined" via flags field.
526 However, their "value" fields are still updated, so we can calculate differences.
527 
528 on pass init:
529 	if value given on command line, set PC and out ptr to that value
530 	otherwise, set both to zero and mark as "undefined"
531 when ALU asks for "*":
532 	return current PC (value and flags)
533 when encountering "!pseudopc VALUE { BLOCK }":
534 	parse new value (NEW: might be undefined!)
535 	remember difference between current and new value
536 	set PC to new value
537 	after BLOCK, use remembered difference to change PC back
538 when encountering "*= VALUE":
539 	parse new value (NEW: might be undefined!)
540 	calculate difference between current PC and new value
541 	set PC to new value
542 	tell outbuf to add difference to mem ptr (starting a new segment) - if new value is undefined, tell outbuf to disable output
543 
544 Problem: always check for "undefined"; there are some problematic combinations.
545 I need a way to return the size of a generated code block even if PC undefined.
546 Maybe like this:
547 	*= new_address [, invisible] [, overlay] [, &size_symbol_ref {]
548 		...code...
549 	[} ; at end of block, size is written to size symbol given above!]
550 */
551 
552 
553 // get program counter
vcpu_read_pc(struct number * target)554 void vcpu_read_pc(struct number *target)
555 {
556 	*target = CPU_state.pc;
557 }
558 
559 
560 // get size of current statement (until now) - needed for "!bin" verbose output
vcpu_get_statement_size(void)561 int vcpu_get_statement_size(void)
562 {
563 	return CPU_state.add_to_pc;
564 }
565 
566 
567 // adjust program counter (called at end of each statement)
vcpu_end_statement(void)568 void vcpu_end_statement(void)
569 {
570 	CPU_state.pc.val.intval = (CPU_state.pc.val.intval + CPU_state.add_to_pc) & (out->bufsize - 1);
571 	CPU_state.add_to_pc = 0;
572 }
573 
574 
575 // struct to describe a pseudopc context
576 struct pseudopc {
577 	struct pseudopc	*outer;	// next layer (to be able to "unpseudopc" labels by more than one level)
578 	intval_t	offset;	// inner minus outer pc
579 	enum numtype	ntype;	// type of outer pc (INT/UNDEFINED)
580 };
581 // start offset assembly
pseudopc_start(struct number * new_pc)582 void pseudopc_start(struct number *new_pc)
583 {
584 	struct pseudopc	*new_context;
585 
586 	new_context = safe_malloc(sizeof(*new_context));	// create new struct (this must never be freed, as it gets linked to labels!)
587 	new_context->outer = pseudopc_current_context;	// let it point to previous one
588 	pseudopc_current_context = new_context;	// make it the current one
589 
590 	new_context->ntype = CPU_state.pc.ntype;
591 	new_context->offset = new_pc->val.intval - CPU_state.pc.val.intval;
592 	CPU_state.pc.val.intval = new_pc->val.intval;
593 	CPU_state.pc.ntype = NUMTYPE_INT;	// FIXME - remove when allowing undefined!
594 	//new: CPU_state.pc.flags = new_pc->flags & (NUMBER_IS_DEFINED | NUMBER_EVER_UNDEFINED);
595 }
596 // end offset assembly
pseudopc_end(void)597 void pseudopc_end(void)
598 {
599 	if (pseudopc_current_context == NULL) {
600 		// trying to end offset assembly though it isn't active:
601 		// in current versions this cannot happen and so must be a bug.
602 		// but in versions older than 0.94.8 this was possible using
603 		// !realpc, and offset assembly got automatically disabled when
604 		// encountering "*=".
605 		// so if wanted version is new enough, choke on bug!
606 		if (config.wanted_version >= VER_DISABLED_OBSOLETE_STUFF)
607 			Bug_found("ClosingUnopenedPseudopcBlock", 0);
608 	} else {
609 		CPU_state.pc.val.intval = (CPU_state.pc.val.intval - pseudopc_current_context->offset) & (out->bufsize - 1);	// pc might have wrapped around
610 		CPU_state.pc.ntype = pseudopc_current_context->ntype;
611 		pseudopc_current_context = pseudopc_current_context->outer;	// go back to outer block
612 	}
613 }
614 // this is only for old, deprecated, obsolete, stupid "realpc":
pseudopc_end_all(void)615 void pseudopc_end_all(void)
616 {
617 	while (pseudopc_current_context)
618 		pseudopc_end();
619 }
620 // un-pseudopc a label value by given number of levels
621 // returns nonzero on error (if level too high)
pseudopc_unpseudo(struct number * target,struct pseudopc * context,unsigned int levels)622 int pseudopc_unpseudo(struct number *target, struct pseudopc *context, unsigned int levels)
623 {
624 	while (levels--) {
625 		//if (target->ntype == NUMTYPE_UNDEFINED)
626 		//	return 0;	// ok (no sense in trying to unpseudo this, and it might be an unresolved forward ref anyway)
627 
628 		if (context == NULL) {
629 			Throw_error("Un-pseudopc operator '&' has no !pseudopc context.");
630 			return 1;	// error
631 		}
632 		// FIXME - in future, check both target and context for NUMTYPE_UNDEFINED!
633 		target->val.intval = (target->val.intval - context->offset) & (out->bufsize - 1);	// FIXME - is masking really needed?	TODO
634 		context = context->outer;
635 	}
636 	return 0;	// ok
637 }
638 // return pointer to current "pseudopc" struct (may be NULL!)
639 // this gets called when parsing label definitions
pseudopc_get_context(void)640 struct pseudopc *pseudopc_get_context(void)
641 {
642 	return pseudopc_current_context;
643 }
644