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
This commit is contained in:
marcobaye 2024-02-18 01:11:33 +00:00
parent 496fde6f1a
commit 7a4237eb3c
17 changed files with 334 additions and 233 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 <stdio.h>
#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;

View File

@ -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;
}

View File

@ -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

View File

@ -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

14
testing/cliargs/Makefile Normal file
View File

@ -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

View File

@ -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

27
testing/cliargs/outformats.sh Executable file
View File

@ -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

View File

@ -0,0 +1,4 @@
*=$1000, outfilestart
!tx "Hinz"
!outfilestart ; -> "Start of output file already chosen."
!tx "Kunz"