2016-12-28 20:32:00 +00:00
|
|
|
// ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code.
|
2024-01-28 19:38:20 +00:00
|
|
|
// Copyright (C) 1998-2024 Marco Baye
|
2012-02-27 21:14:46 +00:00
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
2014-12-22 00:47:52 +00:00
|
|
|
#include "acme.h"
|
2012-02-27 21:14:46 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "alu.h"
|
|
|
|
#include "cliargs.h"
|
|
|
|
#include "config.h"
|
|
|
|
#include "cpu.h"
|
|
|
|
#include "dynabuf.h"
|
|
|
|
#include "encoding.h"
|
|
|
|
#include "flow.h"
|
|
|
|
#include "global.h"
|
|
|
|
#include "input.h"
|
|
|
|
#include "macro.h"
|
|
|
|
#include "mnemo.h"
|
|
|
|
#include "output.h"
|
|
|
|
#include "platform.h"
|
2014-12-03 22:18:06 +00:00
|
|
|
#include "pseudoopcodes.h"
|
2012-02-27 21:14:46 +00:00
|
|
|
#include "section.h"
|
2014-06-07 00:12:10 +00:00
|
|
|
#include "symbol.h"
|
2016-07-19 08:11:58 +00:00
|
|
|
#include "version.h"
|
2012-02-27 21:14:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
// constants
|
|
|
|
static const char FILE_WRITETEXT[] = "w";
|
|
|
|
static const char FILE_WRITEBINARY[] = "wb";
|
|
|
|
// names for error messages
|
|
|
|
static const char name_outfile[] = "output filename";
|
2014-11-22 01:36:02 +00:00
|
|
|
static const char arg_symbollist[] = "symbol list filename";
|
|
|
|
static const char arg_reportfile[] = "report filename";
|
2014-11-23 23:40:01 +00:00
|
|
|
static const char arg_vicelabels[] = "VICE labels filename";
|
2012-02-27 21:14:46 +00:00
|
|
|
// long options
|
|
|
|
#define OPTION_HELP "help"
|
|
|
|
#define OPTION_FORMAT "format"
|
|
|
|
#define OPTION_OUTFILE "outfile"
|
2014-11-22 01:36:02 +00:00
|
|
|
#define OPTION_LABELDUMP "labeldump" // old
|
|
|
|
#define OPTION_SYMBOLLIST "symbollist" // new
|
2014-11-23 23:40:01 +00:00
|
|
|
#define OPTION_VICELABELS "vicelabels"
|
2014-11-22 01:36:02 +00:00
|
|
|
#define OPTION_REPORT "report"
|
2012-02-27 21:14:46 +00:00
|
|
|
#define OPTION_SETPC "setpc"
|
|
|
|
#define OPTION_CPU "cpu"
|
|
|
|
#define OPTION_INITMEM "initmem"
|
|
|
|
#define OPTION_MAXERRORS "maxerrors"
|
|
|
|
#define OPTION_MAXDEPTH "maxdepth"
|
|
|
|
#define OPTION_USE_STDOUT "use-stdout"
|
|
|
|
#define OPTION_VERSION "version"
|
2014-11-23 23:40:01 +00:00
|
|
|
#define OPTION_MSVC "msvc"
|
2017-03-10 12:19:15 +00:00
|
|
|
#define OPTION_COLOR "color"
|
2017-10-29 23:29:07 +00:00
|
|
|
#define OPTION_FULLSTOP "fullstop"
|
2020-04-14 00:28:31 +00:00
|
|
|
#define OPTION_IGNORE_ZEROES "ignore-zeroes"
|
|
|
|
#define OPTION_STRICT_SEGMENTS "strict-segments"
|
2024-02-10 17:21:54 +00:00
|
|
|
#define OPTION_STRICT "strict"
|
2020-05-31 20:55:38 +00:00
|
|
|
#define OPTION_DIALECT "dialect"
|
2024-02-10 12:43:56 +00:00
|
|
|
#define OPTION_DEBUGLEVEL "debuglevel"
|
2020-05-01 21:01:23 +00:00
|
|
|
#define OPTION_TEST "test"
|
2012-02-27 21:14:46 +00:00
|
|
|
// options for "-W"
|
|
|
|
#define OPTIONWNO_LABEL_INDENT "no-label-indent"
|
2014-06-02 00:47:46 +00:00
|
|
|
#define OPTIONWNO_OLD_FOR "no-old-for"
|
2020-06-22 20:32:38 +00:00
|
|
|
#define OPTIONWNO_BIN_LEN "no-bin-len"
|
2014-06-02 00:47:46 +00:00
|
|
|
#define OPTIONWTYPE_MISMATCH "type-mismatch"
|
2012-02-27 21:14:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
// variables
|
|
|
|
static const char **toplevel_sources;
|
|
|
|
static int toplevel_src_count = 0;
|
2014-03-10 00:17:10 +00:00
|
|
|
#define ILLEGAL_START_ADDRESS (-1)
|
|
|
|
static signed long start_address = ILLEGAL_START_ADDRESS;
|
2012-02-27 21:14:46 +00:00
|
|
|
static signed long fill_value = MEMINIT_USE_DEFAULT;
|
2014-03-10 00:17:10 +00:00
|
|
|
static const struct cpu_type *default_cpu = NULL;
|
2014-11-22 01:36:02 +00:00
|
|
|
const char *symbollist_filename = NULL;
|
2014-11-23 23:40:01 +00:00
|
|
|
const char *vicelabels_filename = NULL;
|
2012-02-27 21:14:46 +00:00
|
|
|
const char *output_filename = NULL;
|
2014-11-22 01:36:02 +00:00
|
|
|
const char *report_filename = NULL;
|
2012-02-27 21:14:46 +00:00
|
|
|
// maximum recursion depth for macro calls and "!source"
|
|
|
|
signed long macro_recursions_left = MAX_NESTING;
|
|
|
|
signed long source_recursions_left = MAX_NESTING;
|
|
|
|
|
|
|
|
|
|
|
|
// show release and platform info (and exit, if wanted)
|
|
|
|
static void show_version(int exit_after)
|
|
|
|
{
|
|
|
|
puts(
|
|
|
|
"This is ACME, release " RELEASE " (\"" CODENAME "\"), " CHANGE_DATE " " CHANGE_YEAR "\n"
|
|
|
|
" " PLATFORM_VERSION);
|
|
|
|
if (exit_after)
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// show full help (headline, release/platform/version, copyright, dedication,
|
|
|
|
// warranty disclaimer, usage) and exit program (SUCCESS)
|
|
|
|
static void show_help_and_exit(void)
|
|
|
|
{
|
|
|
|
puts(
|
|
|
|
"ACME - the ACME Crossassembler for Multiple Environments\n"
|
|
|
|
" Copyright (C) 1998-" CHANGE_YEAR " Marco Baye");
|
|
|
|
show_version(FALSE);
|
|
|
|
puts(
|
|
|
|
"ACME comes with ABSOLUTELY NO WARRANTY; for details read the help file.\n"
|
|
|
|
" This is free software, and you are welcome to redistribute it under\n"
|
|
|
|
" certain conditions; as outlined in the GNU General Public License.\n"
|
|
|
|
"Dedicated to the wisest being I ever had the pleasure of reading\n"
|
|
|
|
" books of (currently spending some time dead for tax reasons).\n"
|
|
|
|
"The newest version can be found at the ACME homepage:\n"
|
|
|
|
" " HOME_PAGE "\n"
|
|
|
|
"\n"
|
|
|
|
"Usage:\n"
|
|
|
|
"acme [OPTION...] [FILE]...\n"
|
|
|
|
"\n"
|
|
|
|
"Options:\n"
|
|
|
|
" -h, --" OPTION_HELP " show this help and exit\n"
|
2014-11-22 01:36:02 +00:00
|
|
|
" -f, --" OPTION_FORMAT " FORMAT set output file format\n"
|
|
|
|
" -o, --" OPTION_OUTFILE " FILE set output file name\n"
|
|
|
|
" -r, --" OPTION_REPORT " FILE set report file name\n"
|
|
|
|
" -l, --" OPTION_SYMBOLLIST " FILE set symbol list file name\n"
|
|
|
|
" --" OPTION_LABELDUMP " (old name for --" OPTION_SYMBOLLIST ")\n"
|
2014-11-23 23:40:01 +00:00
|
|
|
" --" OPTION_VICELABELS " FILE set file name for label dump in VICE format\n"
|
2020-08-12 12:31:06 +00:00
|
|
|
" --" OPTION_SETPC " VALUE set program counter\n"
|
2014-11-22 01:36:02 +00:00
|
|
|
" --" OPTION_CPU " CPU set target processor\n"
|
2020-08-12 12:31:06 +00:00
|
|
|
" --" OPTION_INITMEM " VALUE define 'empty' memory\n"
|
2012-02-27 21:14:46 +00:00
|
|
|
" --" OPTION_MAXERRORS " NUMBER set number of errors before exiting\n"
|
|
|
|
" --" OPTION_MAXDEPTH " NUMBER set recursion depth for macro calls and !src\n"
|
2020-04-14 00:28:31 +00:00
|
|
|
" --" OPTION_IGNORE_ZEROES " do not determine number size by leading zeroes\n"
|
|
|
|
" --" OPTION_STRICT_SEGMENTS " turn segment overlap warnings into errors\n"
|
2024-02-10 17:21:54 +00:00
|
|
|
" --" OPTION_STRICT " treat all warnings like errors\n"
|
2012-02-27 21:14:46 +00:00
|
|
|
" -vDIGIT set verbosity level\n"
|
2014-06-07 00:12:10 +00:00
|
|
|
" -DSYMBOL=VALUE define global symbol\n"
|
2017-12-22 22:55:36 +00:00
|
|
|
" -I PATH/TO/DIR add search path for input files\n"
|
2020-04-14 00:28:31 +00:00
|
|
|
// TODO: replace these:
|
2012-02-27 21:14:46 +00:00
|
|
|
" -W" OPTIONWNO_LABEL_INDENT " suppress warnings about indented labels\n"
|
2020-06-28 18:56:55 +00:00
|
|
|
" -W" OPTIONWNO_OLD_FOR " (old, use \"--dialect 0.94.8\" instead)\n"
|
2020-06-22 20:32:38 +00:00
|
|
|
" -W" OPTIONWNO_BIN_LEN " suppress warnings about lengths of binary literals\n"
|
2014-06-02 00:47:46 +00:00
|
|
|
" -W" OPTIONWTYPE_MISMATCH " enable type checking (warn about type mismatch)\n"
|
2020-04-14 00:28:31 +00:00
|
|
|
// with this line and add a separate function:
|
2012-02-27 21:14:46 +00:00
|
|
|
//" -W show warning level options\n"
|
|
|
|
" --" OPTION_USE_STDOUT " fix for 'Relaunch64' IDE (see docs)\n"
|
2017-10-29 23:29:07 +00:00
|
|
|
" --" OPTION_MSVC " output errors in MS VS format\n"
|
2024-02-10 12:43:56 +00:00
|
|
|
" --" OPTION_COLOR " use ANSI color codes for error output\n"
|
2017-10-29 23:29:07 +00:00
|
|
|
" --" OPTION_FULLSTOP " use '.' as pseudo opcode prefix\n"
|
2020-05-31 20:55:38 +00:00
|
|
|
" --" OPTION_DIALECT " VERSION behave like different version\n"
|
2024-02-10 12:43:56 +00:00
|
|
|
" --" OPTION_DEBUGLEVEL " VALUE drop all higher-level debug messages\n"
|
2020-05-01 21:01:23 +00:00
|
|
|
" --" OPTION_TEST " enable experimental features\n"
|
2012-02-27 21:14:46 +00:00
|
|
|
PLATFORM_OPTION_HELP
|
|
|
|
" -V, --" OPTION_VERSION " show version and exit\n");
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-11-22 01:36:02 +00:00
|
|
|
// initialise report struct
|
|
|
|
static void report_init(struct report *report)
|
|
|
|
{
|
|
|
|
report->fd = NULL;
|
|
|
|
report->asc_used = 0;
|
|
|
|
report->bin_used = 0;
|
|
|
|
report->last_input = NULL;
|
|
|
|
}
|
|
|
|
// open report file
|
|
|
|
static int report_open(struct report *report, const char *filename)
|
|
|
|
{
|
|
|
|
report->fd = fopen(filename, FILE_WRITETEXT);
|
|
|
|
if (report->fd == NULL) {
|
|
|
|
fprintf(stderr, "Error: Cannot open report file \"%s\".\n", filename);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0; // success
|
|
|
|
}
|
|
|
|
// close report file
|
|
|
|
static void report_close(struct report *report)
|
|
|
|
{
|
2015-02-05 18:29:01 +00:00
|
|
|
if (report && report->fd) {
|
2014-11-22 01:36:02 +00:00
|
|
|
fclose(report->fd);
|
|
|
|
report->fd = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-27 21:14:46 +00:00
|
|
|
// error handling
|
|
|
|
|
2014-11-22 01:36:02 +00:00
|
|
|
// tidy up before exiting by saving symbol list and close other output files
|
2012-02-27 21:14:46 +00:00
|
|
|
int ACME_finalize(int exit_code)
|
|
|
|
{
|
|
|
|
FILE *fd;
|
|
|
|
|
2014-11-22 01:36:02 +00:00
|
|
|
report_close(report);
|
|
|
|
if (symbollist_filename) {
|
2017-12-22 22:55:36 +00:00
|
|
|
fd = fopen(symbollist_filename, FILE_WRITETEXT); // FIXME - what if filename is given via !sl in sub-dir? fix path!
|
2012-02-27 21:14:46 +00:00
|
|
|
if (fd) {
|
2014-11-22 01:36:02 +00:00
|
|
|
symbols_list(fd);
|
2012-02-27 21:14:46 +00:00
|
|
|
fclose(fd);
|
2014-11-23 23:40:01 +00:00
|
|
|
PLATFORM_SETFILETYPE_TEXT(symbollist_filename);
|
2012-02-27 21:14:46 +00:00
|
|
|
} else {
|
2014-11-22 01:36:02 +00:00
|
|
|
fprintf(stderr, "Error: Cannot open symbol list file \"%s\".\n", symbollist_filename);
|
2012-02-27 21:14:46 +00:00
|
|
|
exit_code = EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
2014-11-23 23:40:01 +00:00
|
|
|
if (vicelabels_filename) {
|
|
|
|
fd = fopen(vicelabels_filename, FILE_WRITETEXT);
|
|
|
|
if (fd) {
|
|
|
|
symbols_vicelabels(fd);
|
|
|
|
fclose(fd);
|
|
|
|
PLATFORM_SETFILETYPE_TEXT(vicelabels_filename);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Error: Cannot open VICE label dump file \"%s\".\n", vicelabels_filename);
|
|
|
|
exit_code = EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
2012-02-27 21:14:46 +00:00
|
|
|
return exit_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// save output file
|
|
|
|
static void save_output_file(void)
|
|
|
|
{
|
|
|
|
FILE *fd;
|
|
|
|
|
|
|
|
// if no output file chosen, tell user and do nothing
|
|
|
|
if (output_filename == NULL) {
|
|
|
|
fputs("No output file specified (use the \"-o\" option or the \"!to\" pseudo opcode).\n", stderr);
|
|
|
|
return;
|
|
|
|
}
|
2017-12-22 22:55:36 +00:00
|
|
|
fd = fopen(output_filename, FILE_WRITEBINARY); // FIXME - what if filename is given via !to in sub-dir? fix path!
|
2012-02-27 21:14:46 +00:00
|
|
|
if (fd == NULL) {
|
|
|
|
fprintf(stderr, "Error: Cannot open output file \"%s\".\n",
|
|
|
|
output_filename);
|
|
|
|
return;
|
|
|
|
}
|
2024-02-08 19:17:07 +00:00
|
|
|
output_save_file(fd);
|
2012-02-27 21:14:46 +00:00
|
|
|
fclose(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-02 10:40:10 +00:00
|
|
|
// increment pass number and perform a single pass
|
|
|
|
static void perform_pass(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
|
|
|
FILE *fd;
|
2014-03-05 23:33:19 +00:00
|
|
|
int ii;
|
2012-02-27 21:14:46 +00:00
|
|
|
|
2020-05-02 10:40:10 +00:00
|
|
|
++pass.number;
|
2012-02-27 21:14:46 +00:00
|
|
|
// call modules' "pass init" functions
|
2024-02-08 19:17:07 +00:00
|
|
|
output_passinit(); // disable output, PC undefined
|
2014-12-16 08:21:44 +00:00
|
|
|
cputype_passinit(default_cpu); // set default cpu type
|
2014-03-10 00:17:10 +00:00
|
|
|
// if start address was given on command line, use it:
|
|
|
|
if (start_address != ILLEGAL_START_ADDRESS)
|
2014-03-11 14:22:32 +00:00
|
|
|
vcpu_set_pc(start_address, 0);
|
2014-12-04 23:58:00 +00:00
|
|
|
encoding_passinit(); // set default encoding
|
2016-08-05 09:59:07 +00:00
|
|
|
section_passinit(); // set initial zone (untitled)
|
2012-02-27 21:14:46 +00:00
|
|
|
// init variables
|
2020-05-02 10:40:10 +00:00
|
|
|
pass.undefined_count = 0;
|
|
|
|
//pass.needvalue_count = 0; FIXME - use
|
|
|
|
pass.error_count = 0;
|
2024-02-10 12:43:56 +00:00
|
|
|
pass.warning_count = 0;
|
2012-02-27 21:14:46 +00:00
|
|
|
// Process toplevel files
|
2014-11-30 17:00:13 +00:00
|
|
|
for (ii = 0; ii < toplevel_src_count; ++ii) {
|
2014-03-05 23:33:19 +00:00
|
|
|
if ((fd = fopen(toplevel_sources[ii], FILE_READBINARY))) {
|
2014-12-22 00:47:52 +00:00
|
|
|
flow_parse_and_close_file(fd, toplevel_sources[ii]);
|
2012-02-27 21:14:46 +00:00
|
|
|
} else {
|
2014-03-05 23:33:19 +00:00
|
|
|
fprintf(stderr, "Error: Cannot open toplevel file \"%s\".\n", toplevel_sources[ii]);
|
2016-02-21 12:58:22 +00:00
|
|
|
if (toplevel_sources[ii][0] == '-')
|
|
|
|
fprintf(stderr, "Options (starting with '-') must be given _before_ source files!\n");
|
2020-05-02 10:40:10 +00:00
|
|
|
++pass.error_count;
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-08 19:17:07 +00:00
|
|
|
output_end_segment();
|
2020-04-25 10:20:52 +00:00
|
|
|
/* TODO:
|
|
|
|
if --save-start is given, parse arg string
|
|
|
|
if --save-limit is given, parse arg string
|
|
|
|
*/
|
2020-05-02 10:40:10 +00:00
|
|
|
if (pass.error_count)
|
2012-02-27 21:14:46 +00:00
|
|
|
exit(ACME_finalize(EXIT_FAILURE));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-02-05 18:29:01 +00:00
|
|
|
static struct report global_report;
|
|
|
|
// do passes until done (or errors occurred). Return whether output is ready.
|
2020-05-02 10:40:10 +00:00
|
|
|
static boolean do_actual_work(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2020-05-02 10:40:10 +00:00
|
|
|
int undefs_before; // number of undefined results in previous pass
|
2012-02-27 21:14:46 +00:00
|
|
|
|
2015-02-05 18:29:01 +00:00
|
|
|
report = &global_report; // let global pointer point to something
|
2014-11-22 01:36:02 +00:00
|
|
|
report_init(report); // we must init struct before doing passes
|
2017-10-29 23:29:07 +00:00
|
|
|
if (config.process_verbosity > 1)
|
2012-02-27 21:14:46 +00:00
|
|
|
puts("First pass.");
|
2020-05-02 14:58:17 +00:00
|
|
|
pass.complain_about_undefined = FALSE; // disable until error pass needed
|
2020-05-02 10:40:10 +00:00
|
|
|
pass.number = -1; // pre-init, will be incremented by perform_pass()
|
|
|
|
perform_pass(); // first pass
|
|
|
|
// pretend there has been a previous pass, with one more undefined result
|
|
|
|
undefs_before = pass.undefined_count + 1;
|
|
|
|
// keep doing passes as long as the number of undefined results keeps decreasing.
|
|
|
|
// stop on zero (FIXME - zero-check pass.needvalue_count instead!)
|
|
|
|
while (pass.undefined_count && (pass.undefined_count < undefs_before)) {
|
|
|
|
undefs_before = pass.undefined_count;
|
2017-10-29 23:29:07 +00:00
|
|
|
if (config.process_verbosity > 1)
|
2012-02-27 21:14:46 +00:00
|
|
|
puts("Further pass.");
|
2020-05-02 10:40:10 +00:00
|
|
|
perform_pass();
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
2014-11-22 01:36:02 +00:00
|
|
|
// any errors left?
|
2020-05-02 10:40:10 +00:00
|
|
|
if (pass.undefined_count == 0) { // FIXME - use pass.needvalue_count instead!
|
2014-11-22 01:36:02 +00:00
|
|
|
// if listing report is wanted and there were no errors,
|
|
|
|
// do another pass to generate listing report
|
|
|
|
if (report_filename) {
|
2017-10-29 23:29:07 +00:00
|
|
|
if (config.process_verbosity > 1)
|
2014-11-22 01:36:02 +00:00
|
|
|
puts("Extra pass to generate listing report.");
|
|
|
|
if (report_open(report, report_filename) == 0) {
|
|
|
|
perform_pass();
|
|
|
|
report_close(report);
|
|
|
|
}
|
|
|
|
}
|
2020-05-02 10:40:10 +00:00
|
|
|
return TRUE;
|
2014-11-22 01:36:02 +00:00
|
|
|
}
|
|
|
|
// There are still errors (unsolvable by doing further passes),
|
|
|
|
// so perform additional pass to find and show them.
|
2017-10-29 23:29:07 +00:00
|
|
|
if (config.process_verbosity > 1)
|
2014-11-22 01:36:02 +00:00
|
|
|
puts("Extra pass needed to find error.");
|
2020-05-02 14:58:17 +00:00
|
|
|
pass.complain_about_undefined = TRUE; // activate error output
|
2012-02-27 21:14:46 +00:00
|
|
|
perform_pass(); // perform pass, but now show "value undefined"
|
2020-05-02 10:40:10 +00:00
|
|
|
return FALSE;
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// copy string to DynaBuf
|
|
|
|
static void keyword_to_dynabuf(const char keyword[])
|
|
|
|
{
|
2024-01-29 22:02:28 +00:00
|
|
|
dynabuf_clear(GlobalDynaBuf);
|
|
|
|
dynabuf_add_string(GlobalDynaBuf, keyword);
|
|
|
|
dynabuf_append(GlobalDynaBuf, '\0');
|
|
|
|
dynabuf_to_lower(GlobalDynaBuf, GlobalDynaBuf); // convert to lower case
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-20 09:33:17 +00:00
|
|
|
// set output format
|
2020-06-09 15:58:48 +00:00
|
|
|
static void set_output_format(const char format_name[])
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2020-06-09 15:58:48 +00:00
|
|
|
// caution, name may be NULL!
|
|
|
|
if (format_name) {
|
|
|
|
keyword_to_dynabuf(format_name);
|
|
|
|
if (!outputfile_set_format())
|
|
|
|
return; // ok
|
|
|
|
|
|
|
|
fputs("Error: Unknown output format.\n", stderr);
|
|
|
|
} else {
|
|
|
|
fputs("Error: No output format specified.\n", stderr);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
2020-06-09 15:58:48 +00:00
|
|
|
fprintf(stderr, "Supported formats are:\n\n\t%s\n\n", outputfile_formats);
|
|
|
|
exit(EXIT_FAILURE);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-20 09:33:17 +00:00
|
|
|
// set CPU type
|
2020-06-09 15:58:48 +00:00
|
|
|
static void set_starting_cpu(const char cpu_name[])
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2014-12-04 15:31:54 +00:00
|
|
|
const struct cpu_type *new_cpu_type;
|
|
|
|
|
2020-06-09 15:58:48 +00:00
|
|
|
// caution, name may be NULL!
|
|
|
|
if (cpu_name) {
|
|
|
|
keyword_to_dynabuf(cpu_name);
|
|
|
|
new_cpu_type = cputype_find();
|
|
|
|
if (new_cpu_type) {
|
|
|
|
default_cpu = new_cpu_type;
|
|
|
|
return; // ok
|
|
|
|
}
|
|
|
|
fputs("Error: Unknown CPU type.\n", stderr);
|
2014-12-04 15:31:54 +00:00
|
|
|
} else {
|
2020-06-09 15:58:48 +00:00
|
|
|
fputs("Error: No CPU type specified.\n", stderr);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
2020-06-09 15:58:48 +00:00
|
|
|
fprintf(stderr, "Supported types are:\n\n\t%s\n\n", cputype_names);
|
|
|
|
exit(EXIT_FAILURE);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void could_not_parse(const char strange[])
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%sCould not parse '%s'.\n", cliargs_error, strange);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// return signed long representation of string.
|
|
|
|
// copes with hexadecimal if prefixed with "$", "0x" or "0X".
|
2020-04-14 00:28:31 +00:00
|
|
|
// copes with octal if prefixed with "&". FIXME - add "0o" prefix?
|
|
|
|
// copes with binary if prefixed with "%". FIXME - add "0b" prefix!
|
2012-02-27 21:14:46 +00:00
|
|
|
// assumes decimal otherwise.
|
|
|
|
static signed long string_to_number(const char *string)
|
|
|
|
{
|
|
|
|
signed long result;
|
|
|
|
char *end;
|
|
|
|
int base = 10;
|
|
|
|
|
|
|
|
if (*string == '%') {
|
|
|
|
base = 2;
|
2014-11-30 17:00:13 +00:00
|
|
|
++string;
|
2012-02-27 21:14:46 +00:00
|
|
|
} else if (*string == '&') {
|
|
|
|
base = 8;
|
2014-11-30 17:00:13 +00:00
|
|
|
++string;
|
2012-02-27 21:14:46 +00:00
|
|
|
} else if (*string == '$') {
|
|
|
|
base = 16;
|
2014-11-30 17:00:13 +00:00
|
|
|
++string;
|
2012-02-27 21:14:46 +00:00
|
|
|
} else if ((*string == '0') && ((string[1] == 'x') || (string[1] == 'X'))) {
|
|
|
|
base = 16;
|
|
|
|
string += 2;
|
|
|
|
}
|
|
|
|
result = strtol(string, &end, base);
|
|
|
|
if (*end)
|
|
|
|
could_not_parse(end);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// set program counter
|
2020-04-14 00:28:31 +00:00
|
|
|
static void set_starting_pc(const char expression[])
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2020-04-14 00:28:31 +00:00
|
|
|
start_address = string_to_number(expression);
|
2014-03-10 00:17:10 +00:00
|
|
|
if ((start_address > -1) && (start_address < 65536))
|
2012-02-27 21:14:46 +00:00
|
|
|
return;
|
2020-07-04 13:43:20 +00:00
|
|
|
|
2012-02-27 21:14:46 +00:00
|
|
|
fprintf(stderr, "%sProgram counter out of range (0-0xffff).\n", cliargs_error);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// set initial memory contents
|
2020-04-14 00:28:31 +00:00
|
|
|
static void set_mem_contents(const char expression[])
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2020-04-14 00:28:31 +00:00
|
|
|
fill_value = string_to_number(expression);
|
2012-02-27 21:14:46 +00:00
|
|
|
if ((fill_value >= -128) && (fill_value <= 255))
|
|
|
|
return;
|
2020-07-04 13:43:20 +00:00
|
|
|
|
2012-02-27 21:14:46 +00:00
|
|
|
fprintf(stderr, "%sInitmem value out of range (0-0xff).\n", cliargs_error);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-07 00:12:10 +00:00
|
|
|
// define symbol
|
|
|
|
static void define_symbol(const char definition[])
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
|
|
|
const char *walk = definition;
|
|
|
|
signed long value;
|
|
|
|
|
|
|
|
// copy definition to GlobalDynaBuf until '=' reached
|
2024-01-29 22:02:28 +00:00
|
|
|
dynabuf_clear(GlobalDynaBuf);
|
2012-02-27 21:14:46 +00:00
|
|
|
while ((*walk != '=') && (*walk != '\0'))
|
2024-01-29 22:02:28 +00:00
|
|
|
dynabuf_append(GlobalDynaBuf, *walk++);
|
2012-02-27 21:14:46 +00:00
|
|
|
if ((*walk == '\0') || (walk[1] == '\0'))
|
|
|
|
could_not_parse(definition);
|
2024-01-28 19:38:20 +00:00
|
|
|
// TODO - if first char is double quote, maybe interpret as string instead of number?
|
|
|
|
value = string_to_number(walk + 1);
|
2024-01-29 22:02:28 +00:00
|
|
|
dynabuf_append(GlobalDynaBuf, '\0');
|
2014-06-07 00:12:10 +00:00
|
|
|
symbol_define(value);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-31 20:55:38 +00:00
|
|
|
struct dialect {
|
|
|
|
enum version dialect;
|
|
|
|
const char *version;
|
|
|
|
const char *description;
|
|
|
|
};
|
|
|
|
struct dialect dialects[] = {
|
|
|
|
{VER_OLDEST_SUPPORTED, "0.85", "(the oldest version supported)"},
|
|
|
|
{VER_DEPRECATE_REALPC, "0.86", "\"!realpc\" gives a warning, \"!to\" wants a file format"},
|
2020-06-01 17:49:46 +00:00
|
|
|
// {VER_SHORTER_SETPC_WARNING, "0.93", "\"*=\" in offset assembly gives shorter warning but still switches off"},
|
2020-05-31 20:55:38 +00:00
|
|
|
{VER_RIGHTASSOCIATIVEPOWEROF, "0.94.6", "\"power of\" is now right-associative"},
|
|
|
|
// {VER_, "0.94.7", "empty code segments are no longer included in output file"},
|
2020-06-01 17:49:46 +00:00
|
|
|
{VER_DISABLED_OBSOLETE_STUFF, "0.94.8", "\"*=\" works inside \"!pseudopc\", disabled \"!cbm/!realpc/!subzone\""},
|
2020-05-31 20:55:38 +00:00
|
|
|
{VER_NEWFORSYNTAX, "0.94.12", "new \"!for\" syntax"},
|
|
|
|
// {VER_, "0.95.2", "changed ANC#8 from 0x2b to 0x0b"},
|
2020-06-28 18:56:55 +00:00
|
|
|
{VER_BACKSLASHESCAPING, "0.97", "backslash escaping and strings"},
|
2020-05-31 20:55:38 +00:00
|
|
|
// {VER_CURRENT, "default", "default"},
|
|
|
|
{VER_FUTURE, "future", "enable all experimental features"},
|
|
|
|
{0, NULL, NULL} // NULLs terminate
|
|
|
|
};
|
|
|
|
|
|
|
|
// choose dialect (mimic behaviour of different version)
|
|
|
|
static void set_dialect(const char version[])
|
|
|
|
{
|
|
|
|
struct dialect *dia;
|
|
|
|
|
|
|
|
// caution, version may be NULL!
|
|
|
|
if (version) {
|
|
|
|
// scan array
|
|
|
|
for (dia = dialects; dia->version; ++dia) {
|
|
|
|
if (strcmp(version, dia->version) == 0) {
|
|
|
|
config.wanted_version = dia->dialect;
|
|
|
|
return; // found
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fputs("Error: Unknown dialect specifier.\n", stderr);
|
|
|
|
} else {
|
|
|
|
fputs("Error: No dialect specified.\n", stderr);
|
|
|
|
}
|
|
|
|
// output table of possible versions and die
|
|
|
|
fputs(
|
|
|
|
"Supported dialects are:\n"
|
|
|
|
"\n"
|
|
|
|
"\tdialect\t\tdescription\n"
|
|
|
|
"\t-------\t\t-----------\n", stderr);
|
|
|
|
for (dia = dialects; dia->version; ++dia)
|
|
|
|
fprintf(stderr, "\t%s\t\t%s\n", dia->version, dia->description);
|
|
|
|
fputc('\n', stderr);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-27 21:14:46 +00:00
|
|
|
// handle long options (like "--example"). Return unknown string.
|
|
|
|
static const char *long_option(const char *string)
|
|
|
|
{
|
|
|
|
if (strcmp(string, OPTION_HELP) == 0)
|
|
|
|
show_help_and_exit();
|
|
|
|
else if (strcmp(string, OPTION_FORMAT) == 0)
|
2020-06-09 15:58:48 +00:00
|
|
|
set_output_format(cliargs_get_next()); // NULL is ok (handled like unknown)
|
2012-02-27 21:14:46 +00:00
|
|
|
else if (strcmp(string, OPTION_OUTFILE) == 0)
|
|
|
|
output_filename = cliargs_safe_get_next(name_outfile);
|
2014-11-22 01:36:02 +00:00
|
|
|
else if (strcmp(string, OPTION_LABELDUMP) == 0) // old
|
|
|
|
symbollist_filename = cliargs_safe_get_next(arg_symbollist);
|
|
|
|
else if (strcmp(string, OPTION_SYMBOLLIST) == 0) // new
|
|
|
|
symbollist_filename = cliargs_safe_get_next(arg_symbollist);
|
2014-11-23 23:40:01 +00:00
|
|
|
else if (strcmp(string, OPTION_VICELABELS) == 0)
|
|
|
|
vicelabels_filename = cliargs_safe_get_next(arg_vicelabels);
|
2014-11-22 01:36:02 +00:00
|
|
|
else if (strcmp(string, OPTION_REPORT) == 0)
|
|
|
|
report_filename = cliargs_safe_get_next(arg_reportfile);
|
2012-02-27 21:14:46 +00:00
|
|
|
else if (strcmp(string, OPTION_SETPC) == 0)
|
2020-04-14 00:28:31 +00:00
|
|
|
set_starting_pc(cliargs_safe_get_next("program counter"));
|
2012-02-27 21:14:46 +00:00
|
|
|
else if (strcmp(string, OPTION_CPU) == 0)
|
2020-06-09 15:58:48 +00:00
|
|
|
set_starting_cpu(cliargs_get_next()); // NULL is ok (handled like unknown)
|
2012-02-27 21:14:46 +00:00
|
|
|
else if (strcmp(string, OPTION_INITMEM) == 0)
|
2020-04-14 00:28:31 +00:00
|
|
|
set_mem_contents(cliargs_safe_get_next("initmem value"));
|
2012-02-27 21:14:46 +00:00
|
|
|
else if (strcmp(string, OPTION_MAXERRORS) == 0)
|
2017-10-29 23:29:07 +00:00
|
|
|
config.max_errors = string_to_number(cliargs_safe_get_next("maximum error count"));
|
2012-02-27 21:14:46 +00:00
|
|
|
else if (strcmp(string, OPTION_MAXDEPTH) == 0)
|
2014-11-22 01:36:02 +00:00
|
|
|
macro_recursions_left = (source_recursions_left = string_to_number(cliargs_safe_get_next("recursion depth")));
|
2012-02-27 21:14:46 +00:00
|
|
|
// else if (strcmp(string, "strictsyntax") == 0)
|
|
|
|
// strict_syntax = TRUE;
|
|
|
|
else if (strcmp(string, OPTION_USE_STDOUT) == 0)
|
2020-04-14 00:28:31 +00:00
|
|
|
config.msg_stream = stdout;
|
2014-11-23 23:40:01 +00:00
|
|
|
else if (strcmp(string, OPTION_MSVC) == 0)
|
2017-10-29 23:29:07 +00:00
|
|
|
config.format_msvc = TRUE;
|
|
|
|
else if (strcmp(string, OPTION_FULLSTOP) == 0)
|
|
|
|
config.pseudoop_prefix = '.';
|
2020-04-14 00:28:31 +00:00
|
|
|
else if (strcmp(string, OPTION_IGNORE_ZEROES) == 0)
|
|
|
|
config.honor_leading_zeroes = FALSE;
|
|
|
|
else if (strcmp(string, OPTION_STRICT_SEGMENTS) == 0)
|
|
|
|
config.segment_warning_is_error = TRUE;
|
2024-02-10 17:21:54 +00:00
|
|
|
else if (strcmp(string, OPTION_STRICT) == 0)
|
|
|
|
config.all_warnings_are_errors = TRUE;
|
2020-05-31 20:55:38 +00:00
|
|
|
else if (strcmp(string, OPTION_DIALECT) == 0)
|
|
|
|
set_dialect(cliargs_get_next()); // NULL is ok (handled like unknown)
|
2024-02-10 12:43:56 +00:00
|
|
|
else if (strcmp(string, OPTION_DEBUGLEVEL) == 0)
|
|
|
|
config.debuglevel = string_to_number(cliargs_safe_get_next("debug level"));
|
2020-05-18 21:51:43 +00:00
|
|
|
else if (strcmp(string, OPTION_TEST) == 0) {
|
2020-06-22 13:39:27 +00:00
|
|
|
config.wanted_version = VER_FUTURE;
|
2020-05-01 21:01:23 +00:00
|
|
|
config.test_new_features = TRUE;
|
2020-05-18 21:51:43 +00:00
|
|
|
} PLATFORM_LONGOPTION_CODE
|
2017-03-10 12:19:15 +00:00
|
|
|
else if (strcmp(string, OPTION_COLOR) == 0)
|
2017-10-29 23:29:07 +00:00
|
|
|
config.format_color = TRUE;
|
2012-02-27 21:14:46 +00:00
|
|
|
else if (strcmp(string, OPTION_VERSION) == 0)
|
|
|
|
show_version(TRUE);
|
2014-11-22 01:36:02 +00:00
|
|
|
else
|
|
|
|
return string;
|
2012-02-27 21:14:46 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handle short options (like "-e"). Return unknown character.
|
|
|
|
static char short_option(const char *argument)
|
|
|
|
{
|
|
|
|
while (*argument) {
|
|
|
|
switch (*argument) {
|
|
|
|
case 'D': // "-D" define constants
|
2014-06-07 00:12:10 +00:00
|
|
|
define_symbol(argument + 1);
|
2012-02-27 21:14:46 +00:00
|
|
|
goto done;
|
|
|
|
case 'f': // "-f" selects output format
|
2020-06-09 15:58:48 +00:00
|
|
|
set_output_format(cliargs_get_next()); // NULL is ok (handled like unknown)
|
2012-02-27 21:14:46 +00:00
|
|
|
break;
|
2017-12-22 22:55:36 +00:00
|
|
|
case 'h': // "-h" shows help
|
|
|
|
show_help_and_exit();
|
2012-02-27 21:14:46 +00:00
|
|
|
break;
|
2017-12-22 22:55:36 +00:00
|
|
|
case 'I': // "-I" adds an include directory
|
|
|
|
if (argument[1])
|
|
|
|
includepaths_add(argument + 1);
|
|
|
|
else
|
|
|
|
includepaths_add(cliargs_safe_get_next("include path"));
|
|
|
|
goto done;
|
2014-11-22 01:36:02 +00:00
|
|
|
case 'l': // "-l" selects symbol list filename
|
|
|
|
symbollist_filename = cliargs_safe_get_next(arg_symbollist);
|
|
|
|
break;
|
2017-12-22 22:55:36 +00:00
|
|
|
case 'o': // "-o" selects output filename
|
|
|
|
output_filename = cliargs_safe_get_next(name_outfile);
|
|
|
|
break;
|
2014-11-22 01:36:02 +00:00
|
|
|
case 'r': // "-r" selects report filename
|
|
|
|
report_filename = cliargs_safe_get_next(arg_reportfile);
|
2012-02-27 21:14:46 +00:00
|
|
|
break;
|
|
|
|
case 'v': // "-v" changes verbosity
|
2017-10-29 23:29:07 +00:00
|
|
|
++config.process_verbosity;
|
2012-02-27 21:14:46 +00:00
|
|
|
if ((argument[1] >= '0') && (argument[1] <= '9'))
|
2017-10-29 23:29:07 +00:00
|
|
|
config.process_verbosity = *(++argument) - '0';
|
2012-02-27 21:14:46 +00:00
|
|
|
break;
|
|
|
|
// platform specific switches are inserted here
|
|
|
|
PLATFORM_SHORTOPTION_CODE
|
|
|
|
case 'V': // "-V" shows version
|
|
|
|
show_version(TRUE);
|
|
|
|
break;
|
|
|
|
case 'W': // "-W" tunes warning level
|
|
|
|
if (strcmp(argument + 1, OPTIONWNO_LABEL_INDENT) == 0) {
|
2017-10-29 23:29:07 +00:00
|
|
|
config.warn_on_indented_labels = FALSE;
|
2012-02-27 21:14:46 +00:00
|
|
|
goto done;
|
2014-06-02 00:47:46 +00:00
|
|
|
} else if (strcmp(argument + 1, OPTIONWNO_OLD_FOR) == 0) {
|
2020-05-27 17:32:48 +00:00
|
|
|
config.wanted_version = VER_NEWFORSYNTAX - 1;
|
2014-06-02 00:47:46 +00:00
|
|
|
goto done;
|
2020-06-22 20:32:38 +00:00
|
|
|
} else if (strcmp(argument + 1, OPTIONWNO_BIN_LEN) == 0) {
|
|
|
|
config.warn_bin_mask = 0;
|
|
|
|
goto done;
|
2014-06-02 00:47:46 +00:00
|
|
|
} else if (strcmp(argument + 1, OPTIONWTYPE_MISMATCH) == 0) {
|
2017-10-29 23:29:07 +00:00
|
|
|
config.warn_on_type_mismatch = TRUE;
|
2014-06-02 00:47:46 +00:00
|
|
|
goto done;
|
2012-02-27 21:14:46 +00:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%sUnknown warning level.\n", cliargs_error);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: // unknown ones: program termination
|
|
|
|
return *argument;
|
|
|
|
}
|
2014-11-30 17:00:13 +00:00
|
|
|
++argument;
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
done:
|
|
|
|
return '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// guess what
|
|
|
|
int main(int argc, const char *argv[])
|
|
|
|
{
|
2017-10-29 23:29:07 +00:00
|
|
|
config_default(&config);
|
2012-02-27 21:14:46 +00:00
|
|
|
// if called without any arguments, show usage info (not full help)
|
|
|
|
if (argc == 1)
|
|
|
|
show_help_and_exit();
|
|
|
|
cliargs_init(argc, argv);
|
2020-08-16 17:39:13 +00:00
|
|
|
// init platform-specific stuff.
|
|
|
|
// this may read the library path from an environment variable.
|
2012-02-27 21:14:46 +00:00
|
|
|
PLATFORM_INIT;
|
|
|
|
// handle command line arguments
|
|
|
|
cliargs_handle_options(short_option, long_option);
|
|
|
|
// generate list of files to process
|
2014-11-22 01:36:02 +00:00
|
|
|
cliargs_get_rest(&toplevel_src_count, &toplevel_sources, "No top level sources given");
|
2020-08-16 17:39:13 +00:00
|
|
|
// init output buffer
|
2024-02-08 19:17:07 +00:00
|
|
|
output_createbuffer(fill_value, /* use_large_buf= */ config.test_new_features);
|
2012-02-27 21:14:46 +00:00
|
|
|
if (do_actual_work())
|
|
|
|
save_output_file();
|
|
|
|
return ACME_finalize(EXIT_SUCCESS); // dump labels, if wanted
|
|
|
|
}
|