From 7a4237eb3cbb7e0d18de244e0816bb6911c5de10 Mon Sep 17 00:00:00 2001 From: marcobaye Date: Sun, 18 Feb 2024 01:11:33 +0000 Subject: [PATCH] fixed two bugs, added test files, did cleanup bug 1: in some cases "--format" could not override "!to" bug 2: "cannot open output file" resulted in "success" exit code git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@340 4df02467-bbd4-4a76-a152-e7ce94205b78 --- src/acme.c | 84 ++++++++++- src/cpu.c | 24 +-- src/cpu.h | 23 ++- src/global.c | 25 +++- src/global.h | 19 ++- src/input.h | 2 + src/mnemo.c | 10 +- src/output.c | 199 ++++++++----------------- src/output.h | 51 ++----- src/pseudoopcodes.c | 60 ++++---- src/version.h | 2 +- testing/Makefile | 9 +- testing/cliargs/Makefile | 14 ++ testing/cliargs/outformats.a | 14 ++ testing/cliargs/outformats.sh | 27 ++++ testing/warnings/{start.a => start1.a} | 0 testing/warnings/start2.a | 4 + 17 files changed, 334 insertions(+), 233 deletions(-) create mode 100644 testing/cliargs/Makefile create mode 100644 testing/cliargs/outformats.a create mode 100755 testing/cliargs/outformats.sh rename testing/warnings/{start.a => start1.a} (100%) create mode 100644 testing/warnings/start2.a diff --git a/src/acme.c b/src/acme.c index 3854f83..1dcf22c 100644 --- a/src/acme.c +++ b/src/acme.c @@ -221,20 +221,88 @@ int ACME_finalize(int exit_code) // save output file static void save_output_file(void) { - FILE *fd; + const char *body; + intval_t amount, + loadaddr; + unsigned char header[4]; + int headersize = 0; + FILE *fd; // if no output file chosen, tell user and do nothing if (config.output_filename == NULL) { fputs("No output file specified (use the \"-o\" option or the \"!to\" pseudo opcode).\n", stderr); return; } + + // get memory pointer, block size and load address + output_get_result(&body, &amount, &loadaddr); + + // generate header according to file format + switch (config.outfile_format) { + case OUTFILE_FORMAT_UNSPECIFIED: + case OUTFILE_FORMAT_PLAIN: + headersize = 0; // no header + break; + case OUTFILE_FORMAT_CBM: + if (loadaddr > 0xffff) { + fprintf(stderr, "Error: Load address 0x%04lx too large for cbm file format.\n", loadaddr); + exit(EXIT_FAILURE); + } + header[0] = loadaddr & 255; + header[1] = loadaddr >> 8; + headersize = 2; // 16-bit load address, little-endian + break; + case OUTFILE_FORMAT_APPLE: + if (loadaddr > 0xffff) { + fprintf(stderr, "Error: Load address 0x%04lx too large for apple file format.\n", loadaddr); + exit(EXIT_FAILURE); + } + if (amount > 0xffff) { + fprintf(stderr, "Error: File size 0x%04lx too large for apple file format.\n", loadaddr); + exit(EXIT_FAILURE); + } + header[0] = loadaddr & 255; + header[1] = loadaddr >> 8; + header[2] = amount & 255; + header[3] = amount >> 8; + headersize = 4; // 16-bit load address and 16-bit length, little-endian + break; + default: + BUG("IllegalOutformat1", config.outfile_format); + } + + // open file fd = fopen(config.output_filename, FILE_WRITEBINARY); // FIXME - what if filename is given via !to in sub-dir? fix path! if (fd == NULL) { fprintf(stderr, "Error: Cannot open output file \"%s\".\n", config.output_filename); - return; + exit(EXIT_FAILURE); } - output_save_file(fd); + + if (config.process_verbosity) { + printf("Saving %ld (0x%04lx) bytes (0x%04lx - 0x%04lx exclusive).\n", + amount, amount, loadaddr, loadaddr + amount); + } + + // write header and body + fwrite(header, headersize, 1, fd); + fwrite(body, amount, 1, fd); fclose(fd); + + // set file type + switch (config.outfile_format) { + case OUTFILE_FORMAT_UNSPECIFIED: + case OUTFILE_FORMAT_PLAIN: + PLATFORM_SETFILETYPE_PLAIN(config.output_filename); + break; + case OUTFILE_FORMAT_APPLE: + PLATFORM_SETFILETYPE_APPLE(config.output_filename); + break; + case OUTFILE_FORMAT_CBM: + PLATFORM_SETFILETYPE_CBM(config.output_filename); + break; + default: + BUG("IllegalOutformat2", config.outfile_format); + } } @@ -247,7 +315,7 @@ static void perform_pass(void) ++pass.number; // call modules' "pass init" functions output_passinit(); // disable output, PC undefined - cputype_passinit(config.default_cpu); // set default cpu type + cputype_passinit(config.initial_cpu_type); // if start address was given on command line, use it: if (config.initial_pc != NO_VALUE_GIVEN) vcpu_set_pc(config.initial_pc, 0); // 0 -> no segment flags @@ -342,14 +410,15 @@ static void set_output_format(const char format_name[]) // caution, name may be NULL! if (format_name) { keyword_to_dynabuf(format_name); - if (!outputfile_set_format()) + config.outfile_format = outputformat_find(); + if (config.outfile_format != OUTFILE_FORMAT_UNSPECIFIED) return; // ok fputs("Error: Unknown output format.\n", stderr); } else { fputs("Error: No output format specified.\n", stderr); } - fprintf(stderr, "Supported formats are:\n\n\t%s\n\n", outputfile_formats); + fprintf(stderr, "Supported formats are:\n\n\t%s\n\n", outputformat_names); exit(EXIT_FAILURE); } @@ -364,7 +433,7 @@ static void set_starting_cpu(const char cpu_name[]) keyword_to_dynabuf(cpu_name); new_cpu_type = cputype_find(); if (new_cpu_type) { - config.default_cpu = new_cpu_type; + config.initial_cpu_type = new_cpu_type; return; // ok } fputs("Error: Unknown CPU type.\n", stderr); @@ -676,6 +745,7 @@ int main(int argc, const char *argv[]) // init output buffer output_createbuffer(); + // do the actual work if (do_actual_work()) save_output_file(); return ACME_finalize(EXIT_SUCCESS); // dump labels, if wanted diff --git a/src/cpu.c b/src/cpu.c index fa024ef..2d0ce31 100644 --- a/src/cpu.c +++ b/src/cpu.c @@ -14,7 +14,8 @@ #include "tree.h" -// constants +// predefined stuff + static struct cpu_type cpu_type_6502 = { keyword_is_6502_mnemo, CPUFLAG_WARN_ABOUT_FF_PTR | CPUFLAG_INDIRECTJMPBUGGY, // warn about "XYZ ($ff),y" and "jmp ($XYff)" @@ -67,15 +68,8 @@ static struct cpu_type cpu_type_m65 = { 234 // !align fills with "NOP" }; - -// variables - -boolean cpu_a_is_long = FALSE; -boolean cpu_xy_are_long = FALSE; -// predefined stuff static struct ronode cputype_tree[] = { PREDEF_START, -#define KNOWN_TYPES "'6502', 'nmos6502', '6510', '65c02', 'r65c02', 'w65c02', '65816', '65ce02', '4502', 'm65', 'c64dtv2'" // shown in CLI error message for unknown types // PREDEFNODE("z80", &cpu_type_Z80), PREDEFNODE("6502", &cpu_type_6502), PREDEFNODE("nmos6502", &cpu_type_nmos6502), @@ -90,7 +84,15 @@ static struct ronode cputype_tree[] = { PREDEF_END("c64dtv2", &cpu_type_c64dtv2), // ^^^^ this marks the last element }; -const char cputype_names[] = KNOWN_TYPES; // string to show if cputype_find() returns NULL +// string shown in CLI error message if cputype_find() returns NULL: +const char cputype_names[] = "'6502', 'nmos6502', '6510', '65c02', 'r65c02', 'w65c02', '65816', '65ce02', '4502', 'm65', 'c64dtv2'"; + + +// variables +const struct cpu_type *cpu_current_type = NULL; +boolean cpu_a_is_long = FALSE; +boolean cpu_xy_are_long = FALSE; + // lookup cpu type held in DynaBuf and return its struct pointer (or NULL on failure) const struct cpu_type *cputype_find(void) @@ -111,7 +113,7 @@ const struct cpu_type *cputype_find(void) // initial change, but because of reverting back to old cpu type after "{}" block! void vcpu_check_and_set_reg_length(boolean *var, boolean make_long) { - if (((CPU_state.type->flags & CPUFLAG_SUPPORTSLONGREGS) == 0) && make_long) + if (((cpu_current_type->flags & CPUFLAG_SUPPORTSLONGREGS) == 0) && make_long) Throw_error("Chosen CPU does not support long registers."); else *var = make_long; @@ -122,7 +124,7 @@ void vcpu_check_and_set_reg_length(boolean *var, boolean make_long) void cputype_passinit(const struct cpu_type *cpu_type) { // handle cpu type (default is 6502) - CPU_state.type = cpu_type ? cpu_type : &cpu_type_6502; + cpu_current_type = cpu_type ? cpu_type : &cpu_type_6502; cpu_a_is_long = FALSE; // short accu cpu_xy_are_long = FALSE; // short index regs } diff --git a/src/cpu.h b/src/cpu.h index 50764b3..db18440 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -1,5 +1,5 @@ // ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code. -// Copyright (C) 1998-2020 Marco Baye +// Copyright (C) 1998-2024 Marco Baye // Have a look at "acme.c" for further info // // CPU type stuff @@ -10,7 +10,8 @@ #include "config.h" -// CPU type structure definition +// types + struct cpu_type { // This function is not allowed to change GlobalDynaBuf // because that's where the mnemonic is stored! @@ -25,18 +26,30 @@ struct cpu_type { #define CPUFLAG_DECIMALSUBTRACTBUGGY (1u << 4) // warn if "sed" is assembled #define CPUFLAG_WARN_ABOUT_FF_PTR (1u << 5) // warn if MNEMO($ff) is assembled + +// constants + +extern const char cputype_names[]; // string to show if cputype_find() returns NULL + + // variables -extern boolean cpu_a_is_long; -extern boolean cpu_xy_are_long; + +extern const struct cpu_type *cpu_current_type; +extern boolean cpu_a_is_long; +extern boolean cpu_xy_are_long; + + +// prototypes // if cpu type and value match, set register length variable to value. // if cpu type and value don't match, complain instead. extern void vcpu_check_and_set_reg_length(boolean *var, boolean make_long); + // set default value for pass extern void cputype_passinit(const struct cpu_type *cpu_type); + // lookup cpu type held in DynaBuf and return its struct pointer (or NULL on failure) extern const struct cpu_type *cputype_find(void); -extern const char cputype_names[]; // string to show if cputype_find() returns NULL #endif diff --git a/src/global.c b/src/global.c index 258e52b..9108140 100644 --- a/src/global.c +++ b/src/global.c @@ -123,10 +123,11 @@ void config_default(struct config *conf) conf->wanted_version = VER_CURRENT; // changed by --dialect conf->debuglevel = DEBUGLEVEL_DEBUG; // changed by --debuglevel, used by "!debug" conf->outbuf_size = 0x10000; // 64K, "--test" changes to 16M - conf->default_cpu = NULL; + conf->initial_cpu_type = NULL; conf->symbollist_filename = NULL; conf->vicelabels_filename = NULL; conf->output_filename = NULL; + conf->outfile_format = OUTFILE_FORMAT_UNSPECIFIED; conf->report_filename = NULL; conf->mem_init_value = MEMINIT_USE_DEFAULT; // set by --initmem conf->initial_pc = NO_VALUE_GIVEN; // set by --setpc @@ -281,7 +282,7 @@ static void parse_symbol_definition(scope_t scope) static void parse_mnemo_or_global_symbol_def(void) { // read keyword and ask current cpu type if it's a mnemonic - if (CPU_state.type->keyword_is_mnemonic(input_read_keyword())) + if (cpu_current_type->keyword_is_mnemonic(input_read_keyword())) return; // statement has been handled // if we're here, it wasn't a mnemonic, so it can only be a symbol name @@ -644,3 +645,23 @@ void output_le32(intval_t value) output_byte(value >> 16); output_byte(value >> 24); } + + +// string shown in CLI error message if outputformat_set() returns nonzero: +const char outputformat_names[] = "'plain', 'cbm', 'apple'"; + +// convert output format name held in DynaBuf to enum. +// returns OUTFILE_FORMAT_UNSPECIFIED on error. +enum outfile_format outputformat_find(void) +{ + if (strcmp(GlobalDynaBuf->buffer, "plain") == 0) + return OUTFILE_FORMAT_PLAIN; + else if (strcmp(GlobalDynaBuf->buffer, "cbm") == 0) + return OUTFILE_FORMAT_CBM; + else if (strcmp(GlobalDynaBuf->buffer, "apple") == 0) + return OUTFILE_FORMAT_APPLE; +// else if (strcmp(GlobalDynaBuf->buffer, "o65") == 0) +// return OUTFILE_FORMAT_O65; + + return OUTFILE_FORMAT_UNSPECIFIED; +} diff --git a/src/global.h b/src/global.h index dc680ee..0d7dc71 100644 --- a/src/global.h +++ b/src/global.h @@ -69,6 +69,12 @@ enum debuglevel { // debug messages with higher levels are suppressed, // can be changed using "--debuglevel" cli switch. }; +enum outfile_format { + OUTFILE_FORMAT_UNSPECIFIED, // default (uses "plain" actually) + OUTFILE_FORMAT_PLAIN, // no header, just code + OUTFILE_FORMAT_CBM, // 16-bit load address, code (default for "!to" pseudo opcode) + OUTFILE_FORMAT_APPLE // 16-bit load address, 16-bit length, code +}; // configuration struct config { char pseudoop_prefix; // '!' or '.' @@ -87,10 +93,11 @@ struct config { enum version wanted_version; // set by --dialect (and --test --test) signed long debuglevel; // set by --debuglevel, used by "!debug" signed long outbuf_size; // 64K, "--test" changes to 16M - const struct cpu_type *default_cpu; + const struct cpu_type *initial_cpu_type; const char *symbollist_filename; const char *vicelabels_filename; const char *output_filename; // TODO - put in "part" struct + enum outfile_format outfile_format; const char *report_filename; // TODO - put in "part" struct #define MEMINIT_USE_DEFAULT 256 // default value for next field if cli switch not used: signed long mem_init_value; // set by --initmem @@ -139,7 +146,7 @@ do { \ } while (0) -// Prototypes +// prototypes // set configuration to default values extern void config_default(struct config *conf); @@ -227,4 +234,12 @@ extern void output_be32(intval_t value); extern void output_le32(intval_t value); +// string to show if outputformat_set() returns nonzero +extern const char outputformat_names[]; + +// convert output format name held in DynaBuf to enum. +// returns OUTFILE_FORMAT_UNSPECIFIED on error. +extern enum outfile_format outputformat_find(void); + + #endif diff --git a/src/input.h b/src/input.h index cd9cbc7..b638eec 100644 --- a/src/input.h +++ b/src/input.h @@ -133,6 +133,8 @@ extern int input_read_input_filename(boolean *uses_lib); // Returns nonzero on error. Filename in GlobalDynaBuf. // Errors are handled and reported, but caller should call // input_skip_remainder() then. +// FIXME - the name suggests this fn reads "the" output filename, but it only +// reads "an" output filename: either symbollist or the real output file. extern int input_read_output_filename(void); // Try to read a comma, skipping spaces before and after. Return TRUE if comma diff --git a/src/mnemo.c b/src/mnemo.c index 7355bc5..a88416c 100644 --- a/src/mnemo.c +++ b/src/mnemo.c @@ -775,7 +775,7 @@ static void group_only_implied_addressing(int opcode) //bits force_bit = input_get_force_bit(); // skips spaces after // TODO - accept postfix and complain about it? // TODO - accept argument and complain about it? error message should tell more than "garbage data at end of line"! // for 65ce02 and 4502, warn about buggy decimal mode - if ((opcode == 0xf8) && (CPU_state.type->flags & CPUFLAG_DECIMALSUBTRACTBUGGY)) + if ((opcode == 0xf8) && (cpu_current_type->flags & CPUFLAG_DECIMALSUBTRACTBUGGY)) Throw_first_pass_warning("Found SED instruction for CPU with known decimal SBC bug."); output_byte(opcode); input_ensure_EOS(); @@ -898,7 +898,7 @@ static unsigned int imm_ops(bits *force_bit, unsigned char opcode, bits immediat BUG("IllegalImmediateMode", immediate_mode); } // if the CPU does not support long registers... - if ((CPU_state.type->flags & CPUFLAG_SUPPORTSLONGREGS) == 0) + if ((cpu_current_type->flags & CPUFLAG_SUPPORTSLONGREGS) == 0) return opcode; // result in bits 0..7 forces single-byte argument // check force bits - if no force bits given, use cpu state and convert to force bit @@ -913,7 +913,7 @@ static void check_zp_wraparound(struct number *result) { if ((result->ntype == NUMTYPE_INT) && (result->val.intval == 0xff) - && (CPU_state.type->flags & CPUFLAG_WARN_ABOUT_FF_PTR)) + && (cpu_current_type->flags & CPUFLAG_WARN_ABOUT_FF_PTR)) Throw_warning("Zeropage pointer wraps around from $ff to $00"); } @@ -1002,7 +1002,7 @@ static void group_misc(int index, bits immediate_mode) // below - "force_bit" might be undefined (depends on compiler). make_instruction(force_bit, &result, immediate_opcodes); // warn about unstable ANE/LXA (undocumented opcode of NMOS 6502)? - if ((CPU_state.type->flags & CPUFLAG_8B_AND_AB_NEED_0_ARG) + if ((cpu_current_type->flags & CPUFLAG_8B_AND_AB_NEED_0_ARG) && (result.ntype == NUMTYPE_INT) && (result.val.intval != 0x00)) { if (immediate_opcodes == 0x8b) @@ -1137,7 +1137,7 @@ static void group_jump(int index) // check whether to warn about 6502's JMP() bug if ((result.ntype == NUMTYPE_INT) && ((result.val.intval & 0xff) == 0xff) - && (CPU_state.type->flags & CPUFLAG_INDIRECTJMPBUGGY)) + && (cpu_current_type->flags & CPUFLAG_INDIRECTJMPBUGGY)) Throw_warning("Assembling buggy JMP($xxff) instruction"); break; case X_INDEXED_INDIRECT_ADDRESSING: // ($ffff,x) diff --git a/src/output.c b/src/output.c index ef216c0..9f36fe1 100644 --- a/src/output.c +++ b/src/output.c @@ -20,7 +20,6 @@ #include "global.h" #include "input.h" #include "platform.h" -#include "tree.h" // constants @@ -58,34 +57,10 @@ static struct pseudopc *pseudopc_current_context; // current struct (NULL when n // variables static struct output default_output; -static struct output *out = &default_output; +static struct output *out = &default_output; // FIXME - never changes! is the ptr a preparation for "assembling several different parts in one go"? static int statement_size; // add to PC after statement -// FIXME - make static -struct vcpu CPU_state; // current CPU state - -// FIXME - move output _file_ stuff to some other .c file! -// possible file formats -enum output_format { - OUTPUT_FORMAT_UNSPECIFIED, // default (uses "plain" actually) - OUTPUT_FORMAT_APPLE, // load address, length, code - OUTPUT_FORMAT_CBM, // load address, code (default for "!to" pseudo opcode) - OUTPUT_FORMAT_PLAIN // code only -}; -// predefined stuff -// tree to hold output formats (FIXME - a tree for three items, really?) -static struct ronode file_format_tree[] = { - PREDEF_START, -#define KNOWN_FORMATS "'plain', 'cbm', 'apple'" // shown in CLI error message for unknown formats - PREDEFNODE("apple", OUTPUT_FORMAT_APPLE), - PREDEFNODE("cbm", OUTPUT_FORMAT_CBM), -// PREDEFNODE("o65", OUTPUT_FORMAT_O65), - PREDEF_END("plain", OUTPUT_FORMAT_PLAIN), - // ^^^^ this marks the last element -}; -// chosen file format -static enum output_format output_format = OUTPUT_FORMAT_UNSPECIFIED; -const char outputfile_formats[] = KNOWN_FORMATS; // string to show if outputfile_set_format() returns nonzero - +static intval_t program_counter; // current program counter (pseudopc value) +static enum numtype pc_ntype; // report binary output static void report_binary(char value) @@ -134,7 +109,7 @@ static void border_crossed(int current_offset) void (*output_byte)(intval_t byte); -// send low byte to output buffer, automatically increasing program counter +// send low byte to output buffer and remember to later increase program counter static void real_output(intval_t byte) { // CAUTION - there are two copies of these checks! @@ -257,32 +232,7 @@ void outbuf_set_outfile_limit(void) } -// try to set output format held in DynaBuf. Returns zero on success. -int outputfile_set_format(void) -{ - void *node_body; - - // perform lookup - if (!tree_easy_scan(file_format_tree, &node_body, GlobalDynaBuf)) - return 1; - - output_format = (enum output_format) node_body; - return 0; -} - -// if file format was already chosen, returns zero. -// if file format isn't set, chooses CBM and returns 1. -int outputfile_prefer_cbm_format(void) -{ - if (output_format != OUTPUT_FORMAT_UNSPECIFIED) - return 0; - - output_format = OUTPUT_FORMAT_CBM; - return 1; -} - - -// init output struct (done later) +// init output struct void output_createbuffer(void) { char fill_value = 0; // default value for output buffer @@ -302,66 +252,6 @@ void output_createbuffer(void) } -// write used portion of output buffer to output file -void output_save_file(FILE *fd) -{ - intval_t start, - limit, // end+1 - amount; - - start = out->lowest_written; - limit = out->highest_written + 1; - // if pseudo opcodes were used, they override the actual values: - if (force_file_start) - start = forced_start_idx; - if (force_file_limit) - limit = forced_limit_idx; - // if cli args were given, they override even harder: - if (config.outfile_start != NO_VALUE_GIVEN) - start = config.outfile_start; - if (config.outfile_limit != NO_VALUE_GIVEN) - limit = config.outfile_limit; - - if (limit <= start) { - // nothing written - start = 0; // I could try to use some segment start, but what for? - amount = 0; - // FIXME - how about not writing anything in this case? - // a CBM file would consist of a bogus load address and nothing else! - } else { - amount = limit - start; - } - if (config.process_verbosity) { - printf("Saving %ld (0x%04lx) bytes (0x%04lx - 0x%04lx exclusive).\n", - amount, amount, start, start + amount); - } - // output file header according to file format - // FIXME - add checks and error messages for "start is above $ffff"!) - switch (output_format) { - case OUTPUT_FORMAT_APPLE: - PLATFORM_SETFILETYPE_APPLE(config.output_filename); - // output 16-bit load address in little-endian byte order - putc(start & 255, fd); - putc(start >> 8, fd); - // output 16-bit length in little-endian byte order - putc(amount & 255, fd); - putc(amount >> 8, fd); - break; - case OUTPUT_FORMAT_UNSPECIFIED: - case OUTPUT_FORMAT_PLAIN: - PLATFORM_SETFILETYPE_PLAIN(config.output_filename); - break; - case OUTPUT_FORMAT_CBM: - PLATFORM_SETFILETYPE_CBM(config.output_filename); - // output 16-bit load address in little-endian byte order - putc(start & 255, fd); - putc(start >> 8, fd); - } - // dump output buffer to file - fwrite(out->buffer + start, amount, 1, fd); -} - - // link segment data into segment ring static void link_segment(intval_t start, intval_t length) { @@ -435,11 +325,10 @@ void output_passinit(void) out->xor = 0; //vcpu stuff: - CPU_state.pc.ntype = NUMTYPE_UNDEFINED; // not defined yet - CPU_state.pc.flags = 0; + pc_ntype = NUMTYPE_UNDEFINED; // not defined yet // FIXME - number type is "undefined", but still the intval 0 below will // be used to calculate diff when pc is first set. - CPU_state.pc.val.intval = 0; // same as output's write_idx on pass init + program_counter = 0; // same as output's write_idx on pass init statement_size = 0; // increase PC by this at end of statement // pseudopc stuff: @@ -536,10 +425,9 @@ void vcpu_set_pc(intval_t new_pc, bits segment_flags) // stuff happens! i see no reason to try to mimic that. } } - pc_change = new_pc - CPU_state.pc.val.intval; - CPU_state.pc.val.intval = new_pc; // FIXME - oversized values are accepted without error and will be wrapped at end of statement! - CPU_state.pc.ntype = NUMTYPE_INT; // FIXME - remove when allowing undefined! - CPU_state.pc.addr_refs = 1; // yes, PC counts as address + pc_change = new_pc - program_counter; + program_counter = new_pc; // FIXME - oversized values are accepted without error and will be wrapped at end of statement! + pc_ntype = NUMTYPE_INT; // FIXME - remove when allowing undefined! // now tell output buffer to start a new segment output_start_segment(pc_change, segment_flags); } @@ -567,16 +455,19 @@ when encountering "*= VALUE": Problem: always check for "undefined"; there are some problematic combinations. I need a way to return the size of a generated code block even if PC undefined. Maybe like this: - *= new_address [, invisible] [, overlay] [, &size_symbol_ref {] + *= new_address [, invisible] [, overlay] [, size_counter = symbol {] ...code... - [} ; at end of block, size is written to size symbol given above!] + [} ; at end of block, size is written to symbol given above!] */ // get program counter void vcpu_read_pc(struct number *target) { - *target = CPU_state.pc; + target->ntype = pc_ntype; + target->flags = 0; // FIXME - if defined, check for FITS_BYTE etc.? use pc_flags? + target->val.intval = program_counter; + target->addr_refs = 1; // yes, PC counts as address } @@ -590,11 +481,50 @@ int vcpu_get_statement_size(void) // adjust program counter (called at end of each statement) void vcpu_end_statement(void) { - CPU_state.pc.val.intval = (CPU_state.pc.val.intval + statement_size) & (config.outbuf_size - 1); + program_counter = (program_counter + statement_size) & (config.outbuf_size - 1); // FIXME - this cannot be right! statement_size = 0; // reset } +// return start and size of memory block to write to output file, +// along with load address for cbm/apple headers. +void output_get_result(const char **ptr, intval_t *size, intval_t *loadaddr) +{ + intval_t start, + limit, // end+1 + amount; + + start = out->lowest_written; + limit = out->highest_written + 1; + // if pseudo opcodes were used, they override the actual values: + if (force_file_start) + start = forced_start_idx; + if (force_file_limit) + limit = forced_limit_idx; + // if cli args were given, they override even harder: + if (config.outfile_start != NO_VALUE_GIVEN) + start = config.outfile_start; + if (config.outfile_limit != NO_VALUE_GIVEN) + limit = config.outfile_limit; + + if (limit <= start) { + // nothing written + start = 0; // I could try to use some segment start, but what for? + amount = 0; + // FIXME - how about not writing anything in this case? + // a CBM file would consist of a bogus load address and nothing else! + } else { + amount = limit - start; + } + + *ptr = out->buffer + start; + *size = amount; + *loadaddr = start; +} + + +// pseudopc stuff: + // struct to describe a pseudopc context struct pseudopc { struct pseudopc *outer; // next layer (to be able to "unpseudopc" labels by more than one level) @@ -610,11 +540,11 @@ void pseudopc_start(struct number *new_pc) new_context->outer = pseudopc_current_context; // let it point to previous one pseudopc_current_context = new_context; // make it the current one - new_context->ntype = CPU_state.pc.ntype; - new_context->offset = new_pc->val.intval - CPU_state.pc.val.intval; - CPU_state.pc.val.intval = new_pc->val.intval; - CPU_state.pc.ntype = NUMTYPE_INT; // FIXME - remove when allowing undefined! - //new: CPU_state.pc.flags = new_pc->flags & (NUMBER_IS_DEFINED | NUMBER_EVER_UNDEFINED); + new_context->ntype = pc_ntype; + new_context->offset = new_pc->val.intval - program_counter; + program_counter = new_pc->val.intval; + pc_ntype = NUMTYPE_INT; // FIXME - remove when allowing undefined! + //new: pc_flags = new_pc->flags & (NUMBER_IS_DEFINED | NUMBER_EVER_UNDEFINED); } // end offset assembly void pseudopc_end(void) @@ -629,15 +559,16 @@ void pseudopc_end(void) if (config.wanted_version >= VER_DISABLED_OBSOLETE_STUFF) BUG("ClosingUnopenedPseudopcBlock", 0); } else { - CPU_state.pc.val.intval = (CPU_state.pc.val.intval - pseudopc_current_context->offset) & (config.outbuf_size - 1); // pc might have wrapped around - CPU_state.pc.ntype = pseudopc_current_context->ntype; + program_counter = (program_counter - pseudopc_current_context->offset) & (config.outbuf_size - 1); // pc might have wrapped around + pc_ntype = pseudopc_current_context->ntype; pseudopc_current_context = pseudopc_current_context->outer; // go back to outer block } } // this is only for old, deprecated, obsolete, stupid "realpc": void pseudopc_end_all(void) { - while (pseudopc_current_context) + // FIXME - this needs changing if we start with a "offset is 0" struct in the future! + while (pseudopc_current_context != NULL) pseudopc_end(); } // un-pseudopc a label value by given number of levels diff --git a/src/output.h b/src/output.h index ee113fa..4fe9386 100644 --- a/src/output.h +++ b/src/output.h @@ -2,12 +2,11 @@ // Copyright (C) 1998-2024 Marco Baye // Have a look at "acme.c" for further info // -// Output stuff (FIXME - split into outbuf, outfile/format and vcpu parts) +// output buffer stuff #ifndef output_H #define output_H -#include #include "config.h" @@ -18,35 +17,20 @@ #define SEGMENT_FLAG_INVISIBLE (1u << 1) // do not warn about other segments overwriting this one -// current CPU state -// FIXME - move vcpu struct definition to .c file and change other .c files' accesses to fn calls. then replace "struct number" with minimized version. -struct vcpu { - const struct cpu_type *type; // current CPU type (default 6502) (FIXME - move out of struct again?) - struct number pc; // current program counter (pseudo value) -}; +// prototypes - -// variables -extern struct vcpu CPU_state; // current CPU state FIXME - restrict visibility to .c file - - -// Prototypes - -// clear segment list and disable output -extern void output_passinit(void); - - -// outbuf stuff: - -// alloc and init mem buffer (done later) +// alloc and init mem buffer (called once on startup) extern void output_createbuffer(void); +// clear segment list and disable output (called on each pass) +extern void output_passinit(void); + // skip over some bytes in output buffer without starting a new segment // (used by "!skip", and also called by "!binary" if really calling // output_byte would be a waste of time) extern void output_skip(int size); -// Send low byte of arg to output buffer and advance pointer +// send low byte of arg to output buffer and advance pointer // FIXME - replace by output_sequence(char *src, size_t size) extern void (*output_byte)(intval_t); @@ -58,27 +42,14 @@ extern int output_setdefault(char content); extern void outbuf_set_outfile_start(void); extern void outbuf_set_outfile_limit(void); -// outfile stuff: - -// try to set output format held in DynaBuf. Returns zero on success. -extern int outputfile_set_format(void); -extern const char outputfile_formats[]; // string to show if outputfile_set_format() returns nonzero - -// if file format was already chosen, returns zero. -// if file format isn't set, chooses CBM and returns 1. -extern int outputfile_prefer_cbm_format(void); - -// write used portion of output buffer to output file -extern void output_save_file(FILE *fd); - // change output pointer and enable output extern void output_start_segment(intval_t address_change, bits segment_flags); -// Show start and end of current segment +// show start and end of current segment extern void output_end_segment(void); +// get/set "encryption" byte extern char output_get_xor(void); - extern void output_set_xor(char xor); // set program counter to defined value (TODO - allow undefined!) @@ -93,6 +64,10 @@ extern int vcpu_get_statement_size(void); // adjust program counter (called at end of each statement) extern void vcpu_end_statement(void); +// return start and size of memory block to write to output file, +// along with load address for cbm/apple headers. +extern void output_get_result(const char **ptr, intval_t *size, intval_t *loadaddr); + // pseudopc stuff: struct pseudopc; diff --git a/src/pseudoopcodes.c b/src/pseudoopcodes.c index 52652bb..622f00a 100644 --- a/src/pseudoopcodes.c +++ b/src/pseudoopcodes.c @@ -140,13 +140,15 @@ static enum eos po_xor(void) // select output file name and format ("!to" pseudo opcode) static enum eos po_to(void) { + enum outfile_format format; + // only process this pseudo opcode in first pass if (!FIRST_PASS) return SKIP_REMAINDER; - // cli arg and earlier calls supersede this call + // the "--outfile" cli arg and earlier calls have priority if (config.output_filename) { - Throw_warning("Output file already chosen."); + Throw_warning("Output file name already chosen."); return SKIP_REMAINDER; } @@ -159,27 +161,33 @@ static enum eos po_to(void) config.output_filename = dynabuf_get_copy(GlobalDynaBuf); // select output format - // if no comma found, use default file format - if (input_accept_comma() == FALSE) { - if (outputfile_prefer_cbm_format()) { - // output deprecation warning (unless user requests really old behaviour) - if (config.wanted_version >= VER_DEPRECATE_REALPC) - Throw_warning("Used \"!to\" without file format indicator. Defaulting to \"cbm\"."); + if (input_accept_comma()) { + // parse output format name + // if no keyword given, give up + if (input_read_and_lower_keyword() == 0) + return SKIP_REMAINDER; + + format = outputformat_find(); + if (format == OUTFILE_FORMAT_UNSPECIFIED) { + Throw_error("Unknown output format."); + return SKIP_REMAINDER; } - return ENSURE_EOS; + } else { + // no comma: complain unless user requested really old behaviour + if (config.wanted_version >= VER_DEPRECATE_REALPC) + Throw_warning("Used \"!to\" without file format indicator."); + // default to cbm + format = OUTFILE_FORMAT_CBM; } - // parse output format name - // if no keyword given, give up - if (input_read_and_lower_keyword() == 0) - return SKIP_REMAINDER; - - if (outputfile_set_format()) { - // error occurred - Throw_error("Unknown output format."); - return SKIP_REMAINDER; + // the "--format" cli arg has priority + if (config.outfile_format != OUTFILE_FORMAT_UNSPECIFIED) { + Throw_warning("Output file format already chosen."); + } else { + config.outfile_format = format; } - return ENSURE_EOS; // success + + return ENSURE_EOS; } @@ -210,7 +218,7 @@ static enum eos po_byte(void) // Insert 16-bit values ("!16" / "!wo" / "!word" pseudo opcode) static enum eos po_16(void) { - return iterate((CPU_state.type->flags & CPUFLAG_ISBIGENDIAN) ? output_be16 : output_le16); + return iterate((cpu_current_type->flags & CPUFLAG_ISBIGENDIAN) ? output_be16 : output_le16); } // Insert 16-bit values big-endian ("!be16" pseudo opcode) static enum eos po_be16(void) @@ -227,7 +235,7 @@ static enum eos po_le16(void) // Insert 24-bit values ("!24" pseudo opcode) static enum eos po_24(void) { - return iterate((CPU_state.type->flags & CPUFLAG_ISBIGENDIAN) ? output_be24 : output_le24); + return iterate((cpu_current_type->flags & CPUFLAG_ISBIGENDIAN) ? output_be24 : output_le24); } // Insert 24-bit values big-endian ("!be24" pseudo opcode) static enum eos po_be24(void) @@ -244,7 +252,7 @@ static enum eos po_le24(void) // Insert 32-bit values ("!32" pseudo opcode) static enum eos po_32(void) { - return iterate((CPU_state.type->flags & CPUFLAG_ISBIGENDIAN) ? output_be32 : output_le32); + return iterate((cpu_current_type->flags & CPUFLAG_ISBIGENDIAN) ? output_be32 : output_le32); } // Insert 32-bit values big-endian ("!be32" pseudo opcode) static enum eos po_be32(void) @@ -625,7 +633,7 @@ static enum eos po_align(void) if (input_accept_comma()) ALU_any_int(&fill); else - fill = CPU_state.type->default_align_value; + fill = cpu_current_type->default_align_value; // make sure PC is defined vcpu_read_pc(&pc); @@ -709,19 +717,19 @@ static enum eos po_realpc(void) // (allows for block, so must be reentrant) static enum eos po_cpu(void) { - const struct cpu_type *cpu_buffer = CPU_state.type; // remember current cpu + const struct cpu_type *cpu_buffer = cpu_current_type; // remember const struct cpu_type *new_cpu_type; if (input_read_and_lower_keyword()) { new_cpu_type = cputype_find(); if (new_cpu_type) - CPU_state.type = new_cpu_type; // activate new cpu type + cpu_current_type = new_cpu_type; // activate new cpu type else Throw_error("Unknown processor."); } // if there's a block, parse that and then restore old value if (parse_optional_block()) - CPU_state.type = cpu_buffer; + cpu_current_type = cpu_buffer; return ENSURE_EOS; } diff --git a/src/version.h b/src/version.h index df76c37..ae224df 100644 --- a/src/version.h +++ b/src/version.h @@ -9,7 +9,7 @@ #define RELEASE "0.97" // update before release FIXME #define CODENAME "Zem" // update before release -#define CHANGE_DATE "14 Feb" // update before release FIXME +#define CHANGE_DATE "15 Feb" // 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 diff --git a/testing/Makefile b/testing/Makefile index 6eff973..a65d752 100644 --- a/testing/Makefile +++ b/testing/Makefile @@ -1,9 +1,9 @@ .SILENT: -.PHONY: auto bugfixes cpus errors warnings +.PHONY: auto bugfixes cliargs cpus errors warnings -all: auto bugfixes cpus errors warnings +all: auto bugfixes cliargs cpus errors warnings auto: echo "Performing self-tests..." @@ -15,6 +15,11 @@ bugfixes: echo make -C bugfixes +cliargs: + echo "Testing cliargs..." + echo + make -C cliargs + cpus: echo "Testing CPUs..." echo diff --git a/testing/cliargs/Makefile b/testing/cliargs/Makefile new file mode 100644 index 0000000..553d5fa --- /dev/null +++ b/testing/cliargs/Makefile @@ -0,0 +1,14 @@ +#ACMEFLAGS = -v0 + +.PHONY: outformats + +.SILENT: + +all: outformats + echo + echo "Testing cliargs: PASSED" + echo + +outformats: outformats.a outformats.sh outformat-*.o + echo "Testing output formats:" + bash outformats.sh diff --git a/testing/cliargs/outformats.a b/testing/cliargs/outformats.a new file mode 100644 index 0000000..69c2f8b --- /dev/null +++ b/testing/cliargs/outformats.a @@ -0,0 +1,14 @@ + !if FORMAT == 0 { + !to "test.o" + } else if FORMAT == 1 { + !to "test.o", plain + } else if FORMAT == 2 { + !to "test.o", cbm + } else if FORMAT == 3 { + !to "test.o", apple + } else { + !error "unexpected FORMAT" + } + *=$1000 + lda #1 + rts diff --git a/testing/cliargs/outformats.sh b/testing/cliargs/outformats.sh new file mode 100755 index 0000000..91026e9 --- /dev/null +++ b/testing/cliargs/outformats.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +check() { + #echo "$@" + FILE="$1" + shift + acme "$@" outformats.a || exit 1 + cmp test.o "$FILE" || exit 1 + rm test.o +} + +# if neither -o nor -f are given, use format from "!to", which defaults to cbm: +check outformat-cbm.o -DFORMAT=0 +check outformat-plain.o -DFORMAT=1 +check outformat-cbm.o -DFORMAT=2 +check outformat-apple.o -DFORMAT=3 + +# if -o or -f are given, format from "!to" should be ignored: +for f in 0 1 2 3 ; do + check outformat-plain.o -DFORMAT=$f -f plain + check outformat-cbm.o -DFORMAT=$f -f cbm + check outformat-apple.o -DFORMAT=$f -f apple + check outformat-plain.o -DFORMAT=$f -o test.o # defaults to plain + check outformat-plain.o -DFORMAT=$f -o test.o -f plain + check outformat-cbm.o -DFORMAT=$f -o test.o -f cbm + check outformat-apple.o -DFORMAT=$f -o test.o -f apple +done diff --git a/testing/warnings/start.a b/testing/warnings/start1.a similarity index 100% rename from testing/warnings/start.a rename to testing/warnings/start1.a diff --git a/testing/warnings/start2.a b/testing/warnings/start2.a new file mode 100644 index 0000000..50e95b8 --- /dev/null +++ b/testing/warnings/start2.a @@ -0,0 +1,4 @@ + *=$1000, outfilestart + !tx "Hinz" + !outfilestart ; -> "Start of output file already chosen." + !tx "Kunz"