added "output pass", to ease adding of new features.

I had to modify two regression tests because they barfed in the second pass,
but real-world code would not do this, so I'm okay with this incompatibility.


git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@400 4df02467-bbd4-4a76-a152-e7ce94205b78
This commit is contained in:
marcobaye 2024-08-18 22:49:35 +00:00
parent 0af42c7a8a
commit df7f1bf06b
8 changed files with 82 additions and 87 deletions

View File

@ -162,17 +162,16 @@ static void report_init(struct report *report)
report->bin_used = 0;
}
// open report file
static int report_open(struct report *report, const char *filename)
static void report_open(struct report *report, const char *filename)
{
// show filename _now_ because I do not want to massage it into perror()
printf("Opening report file \"%s\".\n", filename);
report->fd = fopen(filename, FILE_WRITETEXT);
if (report->fd == NULL) {
perror("Error: Cannot open report file");
return 1;
exit(ACME_finalize(EXIT_FAILURE));
}
input_set_report_enabled(TRUE);
return 0; // success
}
// close report file
static void report_close(struct report *report)
@ -330,24 +329,38 @@ static void save_output_file(void)
}
// definitions for pass flags:
#define PF_COMPLAIN_ABOUT_UNDEFINEDS (1u << 0) // throw "Symbol not defined" errors
#define PF_THROW_SEGMENT_MESSAGES (1u << 1) // throw segment warnings/errors
#define PF_GENERATE_OUTPUT (1u << 2) // generate output and/or report file
// perform a single pass
static void perform_pass(void)
static void perform_pass(bits passflags)
{
FILE *fd;
int ii;
// init variables
pass.counters.undefineds = 0;
//pass.counters.needvalue = 0; FIXME - use
pass.counters.symbolchanges = 0;
pass.counters.errors = 0;
pass.counters.warnings = 0;
pass.flags.complain_about_undefined = !!(passflags & PF_COMPLAIN_ABOUT_UNDEFINEDS);
pass.flags.throw_segment_messages = !!(passflags & PF_THROW_SEGMENT_MESSAGES);
pass.flags.generate_output = !!(passflags & PF_GENERATE_OUTPUT);
if (config.process_verbosity >= 2)
printf("Pass %d:\n", pass.number);
if (pass.flags.generate_output) {
// if wanted, open report listing
if (config.report_filename)
report_open(report, config.report_filename);
}
cputype_passinit(); // set default cpu type
output_passinit(); // set initial pc or start with undefined pc
encoding_passinit(); // set default encoding
section_passinit(); // set initial zone (untitled)
// init variables
pass.counters.undefineds = 0;
//pass.counters.needvalue = 0; FIXME - use
pass.counters.symbolchanges = 0;
pass.counters.errors = 0;
pass.counters.warnings = 0;
// process toplevel files
for (ii = 0; ii < toplevel_src_count; ++ii) {
fd = fopen(toplevel_sources_plat[ii], FILE_READBINARY);
@ -367,8 +380,17 @@ static void perform_pass(void)
// they would need to be evaluated.
if (config.process_verbosity >= 8)
printf("Undefined expressions: %d. Symbol updates: %d.\n", pass.counters.undefineds, pass.counters.symbolchanges);
// FIXME - make this into next if's "else" block:
if (pass.counters.errors)
exit(ACME_finalize(EXIT_FAILURE));
if (pass.flags.generate_output) {
// FIXME - add some code here to do "if there were errors, call BUG()"
// if report listing is open, close it
if (config.report_filename)
report_close(report);
save_output_file();
}
// now increment pass number
// this must be done _after_ the pass because assignments done via
// "-DSYMBOL=VALUE" cli args must be handled as if they were done at the
@ -387,21 +409,21 @@ static void do_actual_work(void)
report = &global_report; // let global pointer point to something
report_init(report); // we must init struct before doing passes
sanity.macro_recursions_left = config.sanity_limit;
sanity.source_recursions_left = config.sanity_limit;
sanity.passes_left = config.sanity_limit;
sanity.macro_recursions_left = config.sanity_limit;
sanity.source_recursions_left = config.sanity_limit;
sanity.passes_left = config.sanity_limit;
pass.flags.complain_about_undefined = FALSE; // disable until error pass needed
pass.flags.do_segment_checks = TRUE; // FIXME - do in _last_ pass instead!
perform_pass(); // first pass
pass.flags.do_segment_checks = FALSE; // FIXME - do in _last_ pass instead!
// first pass:
perform_pass(PF_THROW_SEGMENT_MESSAGES); // FIXME - check segments in all passes, but only throw errors/warnings in final pass!
// pretend there has been a previous pass, with one more undefined result
undefs_before = pass.counters.undefineds + 1;
// further passes:
// keep doing passes as long as the number of undefined results keeps decreasing.
// stop on zero (FIXME - zero-check pass.counters.needvalue instead!)
while ((pass.counters.undefineds && (pass.counters.undefineds < undefs_before)) || pass.counters.symbolchanges) {
undefs_before = pass.counters.undefineds;
perform_pass();
perform_pass(0);
if (--sanity.passes_left < 0) {
// FIXME - exit with error
// ...or maybe do one additional pass where all errors are reported, including "not defined" and "value has changed".
@ -409,27 +431,20 @@ static void do_actual_work(void)
//break;
}
}
// last pass:
// any errors left?
if (pass.counters.undefineds == 0) { // FIXME - use pass.counters.needvalue instead!
// if listing report is wanted and there were no errors,
// do another pass to generate listing report
if (config.report_filename) {
if (config.process_verbosity >= 2)
puts("Extra pass to generate listing report.");
if (report_open(report, config.report_filename) == 0) {
perform_pass();
report_close(report);
}
// FIXME - add some code here to do "if there were errors, call BUG()"
}
save_output_file();
// victory lap
if (config.process_verbosity >= 2)
puts("Extra pass to generate output.");
perform_pass(PF_GENERATE_OUTPUT);
} else {
// There are still errors (unsolvable by doing further passes),
// so perform additional pass to find and show them.
if (config.process_verbosity >= 2)
puts("Extra pass to display errors.");
pass.flags.complain_about_undefined = TRUE; // activate error output
perform_pass(); // perform pass, but now show "value undefined"
perform_pass(PF_COMPLAIN_ABOUT_UNDEFINEDS); // perform pass, but now show "value undefined"
// FIXME - perform_pass() calls exit() when there were errors,
// so if controls returns here, call BUG()!
// (this can be triggered using ifdef/ifndef)

View File

@ -118,8 +118,9 @@ struct pass {
// error output pass.
} counters;
struct {
char complain_about_undefined; // will be FALSE until error pass is needed
char do_segment_checks; // atm only used in pass 1, should be used in _last_ pass!
boolean complain_about_undefined; // will be FALSE until error pass is needed
boolean throw_segment_messages; // atm only used in pass 1, should be used in _last_ pass!
boolean generate_output; // create output and/or report file
} flags;
};
extern struct pass pass;

View File

@ -99,7 +99,7 @@ static void border_crossed(int current_offset)
// further:
if (current_offset >= config.outbuf_size)
Throw_serious_error("Reached memory limit.");
if (pass.flags.do_segment_checks) {
if (pass.flags.throw_segment_messages) {
throw_message(config.debuglevel_segmentprobs, "Segment reached another one, overwriting it.", NULL);
find_segment_max(current_offset + 1); // find new (next) limit
}
@ -177,29 +177,6 @@ void output_skip(int size)
}
// fill output buffer with given byte value
static void fill_completely(char value)
{
memset(out->buffer, value, config.outbuf_size);
}
// default value for empty memory has changed (called by "!initmem" pseudo opcode)
void output_newdefault(void)
{
// init memory
fill_completely(config.mem_init_value);
// enforce another pass (FIXME - no, just do a separate output pass anyway!)
if (pass.counters.undefineds == 0)
pass.counters.undefineds = 1;
//if (pass.counters.needvalue == 0) FIXME - use? instead or additionally?
// pass.counters.needvalue = 1;
// enforcing another pass is not needed if there hasn't been any
// output yet. But that's tricky to detect without too much overhead.
// The old solution was to add &&(out->lowest_written < out->highest_written+1) to "if" above
// in future, just allocate and init outbuf at the start of the "last" pass!
}
// remember current outbuf index as start/limit of output file
static boolean force_file_start = FALSE;
static intval_t forced_start_idx;
@ -228,16 +205,10 @@ void outbuf_set_outfile_limit(void)
// init output struct
// FIXME - remove this, create the buffer at start of final pass instead!
void output_createbuffer(void)
{
out->buffer = safe_malloc(config.outbuf_size);
// FIXME - in future, do both of these only at start of "last" pass:
// fill memory with initial value
if (config.mem_init_value == NO_VALUE_GIVEN) {
fill_completely(0); // default value
} else {
fill_completely(config.mem_init_value & 0xff);
}
// init ring list of segments
out->segm.list_head.next = &out->segm.list_head;
out->segm.list_head.prev = &out->segm.list_head;
@ -272,6 +243,7 @@ static void link_segment(intval_t start, intval_t length)
// check whether given PC is inside segment.
// only call in first pass, otherwise too many warnings might be thrown
// FIXME - do it the other way round and only complain if there were no other errors!
// FIXME - move this fn to its single caller!
static void check_segment(intval_t new_pc)
{
struct segment *test_segment = out->segm.list_head.next;
@ -297,9 +269,23 @@ void output_passinit(void)
{
// struct segment *temp;
// are we supposed to actually generate correct output?
if (pass.flags.generate_output) {
// FIXME - allocate output buffer using size info gathered in previous pass!
// fill output buffer with initial byte value
if (config.mem_init_value == NO_VALUE_GIVEN) {
memset(out->buffer, 0, config.outbuf_size); // default value
} else {
memset(out->buffer, config.mem_init_value & 0xff, config.outbuf_size);
}
}
//FIXME - why clear ring list in every pass?
// Because later pass shouldn't complain about overwriting the same segment from earlier pass!
// Currently this does not happen because segment warnings are only generated in first pass. FIXME!
// Currently this does not happen because segment warnings are only generated in first pass.
// FIXME - in future we want to _output_ the errors/warnings in the final pass only,
// but we need to _check_ for them in every pass because only the results of the second-to-last
// pass are important. so for the time being, keep this code:
// delete segment list (and free blocks)
// while ((temp = segment_list)) {
// segment_list = segment_list->next;
@ -347,7 +333,8 @@ static void end_segment(void)
intval_t amount;
// only do in first or last pass
if (!pass.flags.do_segment_checks)
// FIXME - do the _checking_ in all passes, but only throw errors/warnings in final pass!
if (!pass.flags.throw_segment_messages)
return;
// if there is no segment, there is nothing to do
@ -396,7 +383,7 @@ static void start_segment(intval_t address_change, bits segment_flags)
// allow writing to output buffer
output_byte = real_output;
// in first/last pass, check for other segments and maybe issue warning
if (pass.flags.do_segment_checks) {
if (pass.flags.throw_segment_messages) {
if (!(segment_flags & SEGMENT_FLAG_OVERLAY))
check_segment(out->segm.start);
find_segment_max(out->segm.start);

View File

@ -34,9 +34,6 @@ extern void output_skip(int size);
// FIXME - replace by output_sequence(char *src, size_t size)
extern void (*output_byte)(intval_t);
// default value for empty memory has changed (called by "!initmem" pseudo opcode)
extern void output_newdefault(void);
// remember current outbuf index as start/limit of output file
extern void outbuf_set_outfile_start(void);
extern void outbuf_set_outfile_limit(void);

View File

@ -72,9 +72,6 @@ static enum eos po_initmem(void)
// remember value
config.mem_init_value = intresult.val.intval & 0xff;
// fill outbuffer and enforce another pass
output_newdefault(); // FIXME - remove when outbuffer gets initialized only right before "last" pass!
return ENSURE_EOS;
}
@ -537,6 +534,7 @@ static enum eos po_binary(void)
int byte;
struct number size,
skip;
int amount;
size.val.intval = -1; // means "not given" => "until EOF"
skip.val.intval = 0;
@ -571,8 +569,7 @@ static enum eos po_binary(void)
// check whether including is a waste of time
// FIXME - future changes ("several-projects-at-once")
// may be incompatible with this!
if ((size.val.intval >= 0) && (pass.counters.undefineds || pass.counters.errors)) {
//if ((size.val.intval >= 0) && (pass.counters.needvalue || pass.counters.errors)) { FIXME - use!
if ((size.val.intval >= 0) && (!pass.flags.generate_output)) {
output_skip(size.val.intval); // really including is useless anyway
} else {
// really insert file
@ -593,16 +590,14 @@ static enum eos po_binary(void)
output_byte(0);
} while (--size.val.intval);
}
// if verbose, produce some output
if (config.process_verbosity >= 2) {
amount = output_get_statement_size();
printf("Loaded %d (0x%04x) bytes from file offset %d (0x%04x).\n",
amount, amount, skip.val.intval, skip.val.intval);
}
}
fclose(stream);
// if verbose, produce some output
// FIXME - do in _last_ pass instead of first!
if ((pass.number == 1) && (config.process_verbosity >= 2)) {
int amount = output_get_statement_size();
printf("Loaded %d (0x%04x) bytes from file offset %d (0x%04x).\n",
amount, amount, skip.val.intval, skip.val.intval);
}
return ENSURE_EOS;
}

View File

@ -9,7 +9,7 @@
#define RELEASE "0.97" // update before release FIXME
#define CODENAME "Zem" // update before release
#define CHANGE_DATE "2 Aug" // update before release FIXME
#define CHANGE_DATE "3 Aug" // update before release FIXME
#define CHANGE_YEAR "2024" // update before release
//#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/"
#define HOME_PAGE "http://sourceforge.net/p/acme-crossass/" // FIXME

View File

@ -15,7 +15,7 @@
otherstring = "Ford"
+a otherstring + emptystring + somestring == "FordArthur"
collection = []
!set collection = []
!for c in "Trillian" {
!set collection = collection + [c]
}

View File

@ -23,7 +23,7 @@ label4
lda d
+a * - label4 == 3
label5
e+2=5
!set e+2=5
!for e+2, 17, 17 {
lda e
}