From 9ad03311c54041612c840902aed5bf80321e33db Mon Sep 17 00:00:00 2001 From: marcobaye Date: Mon, 25 Nov 2024 00:44:37 +0000 Subject: [PATCH] the "-D" switch can now assign strings in double quotes (but check your shell's quoting rules!) git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@433 4df02467-bbd4-4a76-a152-e7ce94205b78 --- src/acme.c | 110 +++++++++++++++++++++++++++++--------------- src/acme.h | 3 ++ src/alu.c | 4 +- src/alu.h | 7 +++ src/global.c | 23 ++++++++- src/global.h | 3 ++ src/input.c | 65 ++++++++++++++------------ src/input.h | 4 ++ src/symbol.c | 31 +++++++++---- src/symbol.h | 11 +++-- src/version.h | 2 +- testing/auto/math.a | 4 ++ 12 files changed, 186 insertions(+), 81 deletions(-) diff --git a/src/acme.c b/src/acme.c index 1c9fcc8..035bc99 100644 --- a/src/acme.c +++ b/src/acme.c @@ -511,11 +511,19 @@ static void set_starting_cpu(const char cpu_name[]) } -static void could_not_parse(const char strange[]) +// helper function to complain about error in cli args, does not return +static void exit__cli_arg_error(const char format[], const char arg[]) { - fprintf(stderr, "%sCould not parse '%s'.\n", cliargs_error, strange); + fprintf(stderr, "%s", cliargs_error); + fprintf(stderr, format, arg); + fprintf(stderr, "\n"); exit(EXIT_FAILURE); } +// exit with "Error in CLI arguments: ..." message +void ACME_cli_args_error(const char msg[]) +{ + exit__cli_arg_error("%s", msg); +} // return signed long representation of string. @@ -544,7 +552,7 @@ static signed long string_to_number(const char *string) } result = strtol(string, &end, base); if (*end) - could_not_parse(end); + exit__cli_arg_error("Could not parse \"%s\" as number.", end); return result; } // wrapper for fn above: complain about negative numbers @@ -552,10 +560,8 @@ static signed long string_to_nonneg_number(const char *string) { signed long result = string_to_number(string); - if (result < 0) { - fprintf(stderr, "%sInvalid value, number is negative: '%s'.\n", cliargs_error, string); - exit(EXIT_FAILURE); - } + if (result < 0) + exit__cli_arg_error("Invalid value, number is negative: \"%s\".", string); return result; } @@ -564,29 +570,64 @@ static signed long string_to_nonneg_number(const char *string) static void set_mem_contents(const char expression[]) { config.mem_init_value = string_to_number(expression); - if ((config.mem_init_value < -128) || (config.mem_init_value > 255)) { - fprintf(stderr, "%sInitmem value out of range (0-0xff).\n", cliargs_error); - exit(EXIT_FAILURE); - } + if ((config.mem_init_value < -128) || (config.mem_init_value > 255)) + exit__cli_arg_error("%s", "Initmem value must be in 0..0xff range."); } // define symbol +// FIXME - replace all this with a call to some kind of "hey parser, parse this string" function? +// but then we would have to pass a "do backslash escapes even if older dialect selected" flag... static void define_symbol(const char definition[]) { const char *walk = definition; - signed long value; + struct symbol *symbol; + signed long intvalue; + boolean escaped; - // copy definition to GlobalDynaBuf until '=' reached + // copy symbol name (everything up to the '=' char) to GlobalDynaBuf and add terminator: dynabuf_clear(GlobalDynaBuf); while ((*walk != '=') && (*walk != '\0')) dynabuf_append(GlobalDynaBuf, *walk++); - if ((*walk == '\0') || (walk[1] == '\0')) - could_not_parse(definition); - // TODO - if first char is double quote, maybe interpret as string instead of number? - value = string_to_number(walk + 1); dynabuf_append(GlobalDynaBuf, '\0'); - symbol_define(value); + // use name to make symbol + symbol = symbol_for_cli_def(); + // now we can clobber GlobalDynaBuf + + // no '=' or nothing after? then complain and die: + if ((*walk == '\0') || (walk[1] == '\0')) + exit__cli_arg_error("\"%s\" does not have SYMBOL=VALUE format.", definition); + ++walk; // eat '=' + // now check value type (at the moment only integers and strings are possible) + if (*walk == '"') { + // it starts with a doublequote, so it must be a string + ++walk; // eat '"' + dynabuf_clear(GlobalDynaBuf); + escaped = FALSE; + for (;;) { + if (*walk == '\0') + exit__cli_arg_error("Closing quote not found after -D %s.", definition); + if (escaped) { + // previous byte was backslash, so do not check for closing quote nor backslash + escaped = FALSE; + } else { + // non-escaped: only closing quote and backslash are of interest + if (*walk == '"') + break; // leave loop + if (*walk == '\\') + escaped = TRUE; + } + DYNABUF_APPEND(GlobalDynaBuf, *walk++); + } + if (walk[1] != '\0') + exit__cli_arg_error("Garbage after closing quote at -D %s.", definition); + unescape_dynabuf(); + symbol_define_string(symbol); + } else { + // integer + intvalue = string_to_number(walk); + symbol_define_int(symbol, intvalue); + } } @@ -605,7 +646,7 @@ struct dialect_info dialects[] = { {V0_94_12__NEW_FOR_SYNTAX, "0.94.12", "new \"!for\" syntax"}, {V0_95_2__NEW_ANC_OPCODE, "0.95.2", "changed ANC#8 from 0x2b to 0x0b"}, {V0_97__BACKSLASH_ESCAPING, "0.97", "backslash escaping and strings"}, -// {V0_98__PATHS_AND_SYMBOLCHANGE, "0.98", "paths are relative to current file"}, + {V0_98__PATHS_AND_SYMBOLCHANGE, "0.98", "paths are relative to current file"}, // {V__CURRENT_VERSION, "default", "default"}, {V__FUTURE_VERSION, "future", "enable all experimental features"}, {0, NULL, NULL} // NULLs terminate @@ -758,8 +799,7 @@ static char short_option(const char *argument) config.warn_on_type_mismatch = TRUE; goto done; } else { - fprintf(stderr, "%sUnknown warning level.\n", cliargs_error); - exit(EXIT_FAILURE); + exit__cli_arg_error("Unknown warning level \"%s\".", argument); } break; default: // unknown ones: program termination @@ -794,35 +834,31 @@ int main(int argc, const char *argv[]) // this may read the library path from an environment variable. PLATFORM_INIT; // handle command line arguments + // (this will handle all "-D SYMBOL=VALUE" assignments) cliargs_handle_options(short_option, long_option); // generate list of files to process cliargs_get_rest(&toplevel_src_count, &toplevel_sources_plat, "No top level sources given"); // now that we have processed all cli switches, check a few values for // valid range: - if ((config.initial_pc != NO_VALUE_GIVEN) && (config.initial_pc >= OUTBUF_MAXSIZE)) { - fprintf(stderr, "%sProgram counter exceeds maximum outbuffer size.\n", cliargs_error); - exit(EXIT_FAILURE); - } - if ((config.outfile_start != NO_VALUE_GIVEN) && (config.outfile_start >= OUTBUF_MAXSIZE)) { - fprintf(stderr, "%sStart address of output file exceeds maximum outbuffer size.\n", cliargs_error); - exit(EXIT_FAILURE); - } + if ((config.initial_pc != NO_VALUE_GIVEN) && (config.initial_pc >= OUTBUF_MAXSIZE)) + throw_error("Program counter exceeds maximum outbuffer size."); + if ((config.outfile_start != NO_VALUE_GIVEN) && (config.outfile_start >= OUTBUF_MAXSIZE)) + throw_error("Start address of output file exceeds maximum outbuffer size."); // "limit" is end+1 and therefore we need ">" instead of ">=": - if ((config.outfile_limit != NO_VALUE_GIVEN) && (config.outfile_limit > OUTBUF_MAXSIZE)) { - fprintf(stderr, "%sEnd+1 of output file exceeds maximum outbuffer size.\n", cliargs_error); - exit(EXIT_FAILURE); - } + if ((config.outfile_limit != NO_VALUE_GIVEN) && (config.outfile_limit > OUTBUF_MAXSIZE)) + throw_error("End+1 of output file exceeds maximum outbuffer size."); if ((config.outfile_start != NO_VALUE_GIVEN) && (config.outfile_limit != NO_VALUE_GIVEN) - && (config.outfile_start >= config.outfile_limit)) { - fprintf(stderr, "%sStart address of output file exceeds end+1.\n", cliargs_error); - exit(EXIT_FAILURE); - } + && (config.outfile_start >= config.outfile_limit)) + throw_error("Start address of output file exceeds end+1."); // since version 0.98, "--strict-segments" are default: if (config.dialect >= V0_98__PATHS_AND_SYMBOLCHANGE) config.strict_segments = TRUE; + // make throw_error() behave normally instead of exiting with "Error in CLI arguments: ..." + throw__done_with_cli_args(); + // do the actual work do_actual_work(); return ACME_finalize(EXIT_SUCCESS); // dump labels, if wanted diff --git a/src/acme.h b/src/acme.h index 0566013..7314493 100644 --- a/src/acme.h +++ b/src/acme.h @@ -15,5 +15,8 @@ // tidy up before exiting by saving symbol dump extern int ACME_finalize(int exit_code); +// exit with "Error in CLI arguments: ..." message +extern void ACME_cli_args_error(const char msg[]); + #endif diff --git a/src/alu.c b/src/alu.c index 27f167a..c6ec260 100644 --- a/src/alu.c +++ b/src/alu.c @@ -404,7 +404,8 @@ static void parse_program_counter(void) // now GotByte = "*" // make new string object -static void string_prepare_string(struct object *self, int len) +// (exported because symbol.c calls this for strings given on command line) +void string_prepare_string(struct object *self, int len) { self->type = &type_string; self->u.string = safe_malloc(sizeof(*(self->u.string)) + len); @@ -1285,6 +1286,7 @@ static void unsupported_operation(const struct object *optional, const struct op // int: // create byte-sized int object (for comparison results, converted characters, ...) +// FIXME - as this does not set the FITS_BYTE flag, why "byte-sized"?! why not int_create_int? static void int_create_byte(struct object *self, intval_t byte) { self->type = &type_number; diff --git a/src/alu.h b/src/alu.h index 9d95fa9..07ef9ab 100644 --- a/src/alu.h +++ b/src/alu.h @@ -52,6 +52,13 @@ struct expression { // labels that are undefined, we can't simply get the addressing mode // from looking at the parameter's value. FIXME - rename to TAINTED :) + +// prototypes + +// make new string object +// (exported because symbol.c calls this for strings given on command line) +extern void string_prepare_string(struct object *self, int len); + /* // FIXME - replace all the functions below with a single one using a "flags" arg! // its return value would then be "error"/"ok". diff --git a/src/global.c b/src/global.c index 38957e3..4fb00b6 100644 --- a/src/global.c +++ b/src/global.c @@ -527,10 +527,22 @@ static void print_msg(const char *message, const char *ansicolor, const char *ty } } +// flag, tells throw_error how to behave. set to FALSE by throw__done_with_cli_args(). +// this is needed because errors like "\xZZ is not a valid backslash sequence" are +// handled by calling throw_error(), but they can happen +// - in cli args, using the "-D SYMBOL=VALUE" syntax, where we want to exit, and +// - in source codes, where we want a "file=F, line=N" output and go on! +static boolean error_is_in_cli_args = TRUE; + // generate debug/info/warning/error message // if the "optional alternative location" given is NULL, the current location is used void throw_message(enum debuglevel level, const char msg[], struct location *opt_alt_loc) { + // FIXME: we only expect normal errors in cli args, no warnings or + // other stuff, so enable this: + //if (error_is_in_cli_args) + // BUG("damn"); + // if level is taken from source, ensure valid value: if (level < DEBUGLEVEL_SERIOUS) level = DEBUGLEVEL_SERIOUS; @@ -590,7 +602,10 @@ void throw_warning(const char msg[]) // about more than one of his typos at a time. void throw_error(const char msg[]) { - throw_message(DEBUGLEVEL_ERROR, msg, NULL); + if (error_is_in_cli_args) + ACME_cli_args_error(msg); // does not return... + else + throw_message(DEBUGLEVEL_ERROR, msg, NULL); } // output a serious error (assembly stops, for example if outbuffer overruns). @@ -649,6 +664,12 @@ void throw_redef_error(const char error_msg[], struct location *old_def, const c section_now->title = buffered_section_title; } +// ugly kluge, switches throw_error() from cli args mode to normal mode +void throw__done_with_cli_args(void) +{ + error_is_in_cli_args = FALSE; +} + // handle bugs // FIXME - use a local buffer and sprintf/snprintf to put error code into message! void BUG(const char *message, int code) diff --git a/src/global.h b/src/global.h index 513fa06..5eef9fd 100644 --- a/src/global.h +++ b/src/global.h @@ -242,6 +242,9 @@ extern void throw_finalpass_warning(const char msg[]); // first output error as "error", then location of initial definition as "info" extern void throw_redef_error(const char error_msg[], struct location *old_def, const char info_msg[]); +// ugly kluge, switches throw_error() from cli args mode to normal mode +extern void throw__done_with_cli_args(void); + // handle bugs extern void BUG(const char *msg, int code); diff --git a/src/input.c b/src/input.c index e7faa85..ac63735 100644 --- a/src/input.c +++ b/src/input.c @@ -80,7 +80,7 @@ static void report_printline(int line_number) { int ii; char hex_address[HEXBUFSIZE]; - char hexdump[2 * REPORT_BINBUFSIZE + 2]; // +2 for '.' and terminator + char hexdump[2 * REPORT_BINBUFSIZE + 2 + 1]; // +2 for '.' and terminator, +1 to suppress compiler warning // suppress empty lines if (report->bin_used == 0) { @@ -649,34 +649,17 @@ enum escape { ESCAPE_HEX1, // after reading "\x", expecting first hex digit ESCAPE_HEX2, // after reading "\x", expecting second hex digit }; -// clear dynabuf, read string to it until closing quote is found, then -// process backslash escapes (so size might shrink) -// returns 1 on error (unterminated or escaping error) -int input_read_string_literal(char closing_quote) +// process backslash escapes in GlobalDynaBuf (so size might shrink) +// returns 1 on excaping errors +int unescape_dynabuf(void) { - int read_index, - write_index; + int read_index = 0, + write_index = 0; char byte; - enum escape escape_state; - char hexbyte = 0; + enum escape escape_state = ESCAPE_NONE; + char hexbyte = 0; + int err = 0; - dynabuf_clear(GlobalDynaBuf); - if (quoted_to_dynabuf(closing_quote)) - return 1; // unterminated - - // eat closing quote - GetByte(); - - if (config.dialect >= V0_97__BACKSLASH_ESCAPING) { - // since v0.97 we need to process backslashes (see below) - } else { - return 0; // older versions did not support backslash escapes, so return "ok" - } - - // now un-escape dynabuf contents: - read_index = 0; - write_index = 0; - escape_state = ESCAPE_NONE; // CAUTION - contents of dynabuf are not terminated: while (read_index < GlobalDynaBuf->size) { byte = GLOBALDYNABUF_CURRENT[read_index++]; @@ -714,6 +697,7 @@ int input_read_string_literal(char closing_quote) // TODO - 'a' to BEL? others? default: throw_error("Unsupported backslash sequence."); // TODO - add unexpected character to error message? + err = 1; } GLOBALDYNABUF_CURRENT[write_index++] = byte; break; @@ -721,13 +705,14 @@ int input_read_string_literal(char closing_quote) hexbyte = 0; if (shift_hex_nibble(&hexbyte, byte)) { escape_state = ESCAPE_NONE; // error -> back to default state + err = 1; } else { escape_state = ESCAPE_HEX2; // ok -> expect second hex digit } break; case ESCAPE_HEX2: if (shift_hex_nibble(&hexbyte, byte)) { - // error will have been thrown already + err = 1; } else { // parsing "\x" has written "x", so overwrite it: GLOBALDYNABUF_CURRENT[write_index - 1] = hexbyte; @@ -738,6 +723,7 @@ int input_read_string_literal(char closing_quote) BUG("StrangeEscapeState1", escape_state); } } + GlobalDynaBuf->size = write_index; switch (escape_state) { case ESCAPE_NONE: break; @@ -746,12 +732,33 @@ int input_read_string_literal(char closing_quote) case ESCAPE_HEX1: case ESCAPE_HEX2: throw_error("Expected two hex digits after \\x sequence."); // TODO - add unexpected character to error message? + err = 1; break; default: BUG("StrangeEscapeState2", escape_state); } - GlobalDynaBuf->size = write_index; - return 0; // ok + return err; +} + + +// clear dynabuf, read string to it until closing quote is found, then +// process backslash escapes (so size might shrink) +// returns 1 on error (unterminated or escaping error) +int input_read_string_literal(char closing_quote) +{ + dynabuf_clear(GlobalDynaBuf); + if (quoted_to_dynabuf(closing_quote)) + return 1; // unterminated + + // eat closing quote + GetByte(); + + // since v0.97 we need to process backslashes: + if (config.dialect >= V0_97__BACKSLASH_ESCAPING) { + return unescape_dynabuf(); + } else { + return 0; // older versions did not support backslash escapes, so return "ok" + } } diff --git a/src/input.h b/src/input.h index 845f840..cf129ca 100644 --- a/src/input.h +++ b/src/input.h @@ -71,6 +71,10 @@ extern void parser_skip_remainder(void); // after mnemonics using implied addressing. extern void parser_ensure_EOS(void); +// process backslash escapes in GlobalDynaBuf (so size might shrink) +// returns 1 on escaping errors +extern int unescape_dynabuf(void); + // clear dynabuf, read string to it until closing quote is found, then process // backslash escapes (so size might shrink) // returns 1 on error (unterminated or escaping errors) diff --git a/src/symbol.c b/src/symbol.c index b5d0e7a..7a8864e 100644 --- a/src/symbol.c +++ b/src/symbol.c @@ -9,6 +9,7 @@ // 23 Nov 2014 Added label output in VICE format #include "symbol.h" #include +#include // for memcpy() #include "alu.h" #include "dynabuf.h" #include "global.h" @@ -222,21 +223,33 @@ void symbol_set_force_bit(struct symbol *symbol, bits force_bit) } -// set global symbol to integer value, no questions asked (for "-D" switch) -// Name must be held in GlobalDynaBuf. -void symbol_define(intval_t value) +// create and return symbol for "-D" command line switch (with NULL type object, CAUTION!). +// name must be held in GlobalDynaBuf +extern struct symbol *symbol_for_cli_def(void) { - struct object result; struct symbol *symbol; - result.type = &type_number; - result.u.number.ntype = NUMTYPE_INT; - result.u.number.flags = 0; - result.u.number.val.intval = value; symbol = symbol_find(SCOPE_GLOBAL); - symbol->object = result; symbol->definition.plat_filename = "\"-D SYMBOL=VALUE\""; symbol->definition.line_number = 1; + return symbol; +} +// set symbol to integer value, no questions asked (for "-D" switch) +// FIXME - remove and call int_create_byte instead? +void symbol_define_int(struct symbol *symbol, intval_t value) +{ + symbol->object.type = &type_number; + symbol->object.u.number.ntype = NUMTYPE_INT; + symbol->object.u.number.flags = 0; + symbol->object.u.number.val.intval = value; + symbol->object.u.number.addr_refs = 0; +} +// set symbol to string value, no questions asked (for "-D" switch) +// string value must be held in GlobalDynaBuf +void symbol_define_string(struct symbol *symbol) +{ + string_prepare_string(&symbol->object, GlobalDynaBuf->size); + memcpy(symbol->object.u.string->payload, GLOBALDYNABUF_CURRENT, GlobalDynaBuf->size); } diff --git a/src/symbol.h b/src/symbol.h index f471c5c..c4e781b 100644 --- a/src/symbol.h +++ b/src/symbol.h @@ -49,9 +49,14 @@ extern void symbol_set_object(struct symbol *symbol, struct object *new_obj, bit // set force bit of symbol. trying to change to a different one will raise error. extern void symbol_set_force_bit(struct symbol *symbol, bits force_bit); -// set global symbol to value, no questions asked (for "-D" switch) -// name must be held in GlobalDynaBuf. -extern void symbol_define(intval_t value); +// create and return symbol for "-D" command line switch (with NULL type object, CAUTION!). +// name must be held in GlobalDynaBuf +extern struct symbol *symbol_for_cli_def(void); +// set symbol to integer value, no questions asked (for "-D" switch) +extern void symbol_define_int(struct symbol *symbol, intval_t value); +// set symbol to string value, no questions asked (for "-D" switch) +// string value must be held in GlobalDynaBuf +extern void symbol_define_string(struct symbol *symbol); // dump global symbols to file extern void symbols_list(FILE *fd); diff --git a/src/version.h b/src/version.h index 8fa6c5a..9b7da31 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 "22 Nov" // update before release FIXME +#define CHANGE_DATE "23 Nov" // 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/auto/math.a b/testing/auto/math.a index 6fa65f8..65a8c66 100644 --- a/testing/auto/math.a +++ b/testing/auto/math.a @@ -221,4 +221,8 @@ +a '\n' == 10 +a '\r' == 13 +a '\x41' == 'A' + +a '\xa0' == 160 + +a '\xA0' == 160 + +a '\xff' == 255 + +a '\xFF' == 255 +a "uv\x41yz" == "uvAyz"