1 // ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code.
2 // Copyright (C) 1998-2020 Marco Baye
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 #include "acme.h"
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include "alu.h"
22 #include "cliargs.h"
23 #include "config.h"
24 #include "cpu.h"
25 #include "dynabuf.h"
26 #include "encoding.h"
27 #include "flow.h"
28 #include "global.h"
29 #include "input.h"
30 #include "macro.h"
31 #include "mnemo.h"
32 #include "output.h"
33 #include "platform.h"
34 #include "pseudoopcodes.h"
35 #include "section.h"
36 #include "symbol.h"
37 #include "version.h"
38 
39 
40 // constants
41 static const char	FILE_WRITETEXT[]	= "w";
42 static const char	FILE_WRITEBINARY[]	= "wb";
43 // names for error messages
44 static const char	name_outfile[]		= "output filename";
45 static const char	arg_symbollist[]	= "symbol list filename";
46 static const char	arg_reportfile[]	= "report filename";
47 static const char	arg_vicelabels[]	= "VICE labels filename";
48 // long options
49 #define OPTION_HELP		"help"
50 #define OPTION_FORMAT		"format"
51 #define OPTION_OUTFILE		"outfile"
52 #define OPTION_LABELDUMP	"labeldump"	// old
53 #define OPTION_SYMBOLLIST	"symbollist"	// new
54 #define OPTION_VICELABELS	"vicelabels"
55 #define OPTION_REPORT		"report"
56 #define OPTION_SETPC		"setpc"
57 #define OPTION_CPU		"cpu"
58 #define OPTION_INITMEM		"initmem"
59 #define OPTION_MAXERRORS	"maxerrors"
60 #define OPTION_MAXDEPTH		"maxdepth"
61 #define OPTION_USE_STDOUT	"use-stdout"
62 #define OPTION_VERSION		"version"
63 #define OPTION_MSVC		"msvc"
64 #define OPTION_COLOR		"color"
65 #define OPTION_FULLSTOP		"fullstop"
66 #define OPTION_IGNORE_ZEROES	"ignore-zeroes"
67 #define OPTION_STRICT_SEGMENTS	"strict-segments"
68 #define OPTION_DIALECT		"dialect"
69 #define OPTION_TEST		"test"
70 // options for "-W"
71 #define OPTIONWNO_LABEL_INDENT	"no-label-indent"
72 #define OPTIONWNO_OLD_FOR	"no-old-for"
73 #define OPTIONWNO_BIN_LEN	"no-bin-len"
74 #define OPTIONWTYPE_MISMATCH	"type-mismatch"
75 
76 
77 // variables
78 static const char	**toplevel_sources;
79 static int		toplevel_src_count	= 0;
80 #define ILLEGAL_START_ADDRESS	(-1)
81 static signed long	start_address		= ILLEGAL_START_ADDRESS;
82 static signed long	fill_value		= MEMINIT_USE_DEFAULT;
83 static const struct cpu_type	*default_cpu	= NULL;
84 const char		*symbollist_filename	= NULL;
85 const char		*vicelabels_filename	= NULL;
86 const char		*output_filename	= NULL;
87 const char		*report_filename	= NULL;
88 // maximum recursion depth for macro calls and "!source"
89 signed long	macro_recursions_left	= MAX_NESTING;
90 signed long	source_recursions_left	= MAX_NESTING;
91 
92 
93 // show release and platform info (and exit, if wanted)
show_version(int exit_after)94 static void show_version(int exit_after)
95 {
96 	puts(
97 "This is ACME, release " RELEASE " (\"" CODENAME "\"), " CHANGE_DATE " " CHANGE_YEAR "\n"
98 "  " PLATFORM_VERSION);
99 	if (exit_after)
100 		exit(EXIT_SUCCESS);
101 }
102 
103 
104 // show full help (headline, release/platform/version, copyright, dedication,
105 // warranty disclaimer, usage) and exit program (SUCCESS)
show_help_and_exit(void)106 static void show_help_and_exit(void)
107 {
108 	puts(
109 "ACME - the ACME Crossassembler for Multiple Environments\n"
110 "  Copyright (C) 1998-" CHANGE_YEAR " Marco Baye");
111 	show_version(FALSE);
112 	puts(
113 "ACME comes with ABSOLUTELY NO WARRANTY; for details read the help file.\n"
114 "  This is free software, and you are welcome to redistribute it under\n"
115 "  certain conditions; as outlined in the GNU General Public License.\n"
116 "Dedicated to the wisest being I ever had the pleasure of reading\n"
117 "  books of (currently spending some time dead for tax reasons).\n"
118 "The newest version can be found at the ACME homepage:\n"
119 "  " HOME_PAGE "\n"
120 "\n"
121 "Usage:\n"
122 "acme [OPTION...] [FILE]...\n"
123 "\n"
124 "Options:\n"
125 "  -h, --" OPTION_HELP "             show this help and exit\n"
126 "  -f, --" OPTION_FORMAT " FORMAT    set output file format\n"
127 "  -o, --" OPTION_OUTFILE " FILE     set output file name\n"
128 "  -r, --" OPTION_REPORT " FILE      set report file name\n"
129 "  -l, --" OPTION_SYMBOLLIST " FILE  set symbol list file name\n"
130 "      --" OPTION_LABELDUMP "        (old name for --" OPTION_SYMBOLLIST ")\n"
131 "      --" OPTION_VICELABELS " FILE  set file name for label dump in VICE format\n"
132 "      --" OPTION_SETPC " VALUE      set program counter\n"
133 "      --" OPTION_CPU " CPU          set target processor\n"
134 "      --" OPTION_INITMEM " VALUE    define 'empty' memory\n"
135 "      --" OPTION_MAXERRORS " NUMBER set number of errors before exiting\n"
136 "      --" OPTION_MAXDEPTH " NUMBER  set recursion depth for macro calls and !src\n"
137 "      --" OPTION_IGNORE_ZEROES "    do not determine number size by leading zeroes\n"
138 "      --" OPTION_STRICT_SEGMENTS "  turn segment overlap warnings into errors\n"
139 "  -vDIGIT                set verbosity level\n"
140 "  -DSYMBOL=VALUE         define global symbol\n"
141 "  -I PATH/TO/DIR         add search path for input files\n"
142 // TODO: replace these:
143 "  -W" OPTIONWNO_LABEL_INDENT "      suppress warnings about indented labels\n"
144 "  -W" OPTIONWNO_OLD_FOR "           (old, use \"--dialect 0.94.8\" instead)\n"
145 "  -W" OPTIONWNO_BIN_LEN "           suppress warnings about lengths of binary literals\n"
146 "  -W" OPTIONWTYPE_MISMATCH "        enable type checking (warn about type mismatch)\n"
147 // with this line and add a separate function:
148 //"  -W                     show warning level options\n"
149 "      --" OPTION_USE_STDOUT "       fix for 'Relaunch64' IDE (see docs)\n"
150 "      --" OPTION_MSVC "             output errors in MS VS format\n"
151 "      --" OPTION_COLOR "            uses ANSI color codes for error output\n"
152 "      --" OPTION_FULLSTOP "         use '.' as pseudo opcode prefix\n"
153 "      --" OPTION_DIALECT " VERSION  behave like different version\n"
154 "      --" OPTION_TEST "             enable experimental features\n"
155 PLATFORM_OPTION_HELP
156 "  -V, --" OPTION_VERSION "          show version and exit\n");
157 	exit(EXIT_SUCCESS);
158 }
159 
160 
161 // initialise report struct
report_init(struct report * report)162 static void report_init(struct report *report)
163 {
164 	report->fd = NULL;
165 	report->asc_used = 0;
166 	report->bin_used = 0;
167 	report->last_input = NULL;
168 }
169 // open report file
report_open(struct report * report,const char * filename)170 static int report_open(struct report *report, const char *filename)
171 {
172 	report->fd = fopen(filename, FILE_WRITETEXT);
173 	if (report->fd == NULL) {
174 		fprintf(stderr, "Error: Cannot open report file \"%s\".\n", filename);
175 		return 1;
176 	}
177 	return 0;	// success
178 }
179 // close report file
report_close(struct report * report)180 static void report_close(struct report *report)
181 {
182 	if (report && report->fd) {
183 		fclose(report->fd);
184 		report->fd = NULL;
185 	}
186 }
187 
188 
189 // error handling
190 
191 // tidy up before exiting by saving symbol list and close other output files
ACME_finalize(int exit_code)192 int ACME_finalize(int exit_code)
193 {
194 	FILE	*fd;
195 
196 	report_close(report);
197 	if (symbollist_filename) {
198 		fd = fopen(symbollist_filename, FILE_WRITETEXT);	// FIXME - what if filename is given via !sl in sub-dir? fix path!
199 		if (fd) {
200 			symbols_list(fd);
201 			fclose(fd);
202 			PLATFORM_SETFILETYPE_TEXT(symbollist_filename);
203 		} else {
204 			fprintf(stderr, "Error: Cannot open symbol list file \"%s\".\n", symbollist_filename);
205 			exit_code = EXIT_FAILURE;
206 		}
207 	}
208 	if (vicelabels_filename) {
209 		fd = fopen(vicelabels_filename, FILE_WRITETEXT);
210 		if (fd) {
211 			symbols_vicelabels(fd);
212 			fclose(fd);
213 			PLATFORM_SETFILETYPE_TEXT(vicelabels_filename);
214 		} else {
215 			fprintf(stderr, "Error: Cannot open VICE label dump file \"%s\".\n", vicelabels_filename);
216 			exit_code = EXIT_FAILURE;
217 		}
218 	}
219 	return exit_code;
220 }
221 
222 
223 // save output file
save_output_file(void)224 static void save_output_file(void)
225 {
226 	FILE	*fd;
227 
228 	// if no output file chosen, tell user and do nothing
229 	if (output_filename == NULL) {
230 		fputs("No output file specified (use the \"-o\" option or the \"!to\" pseudo opcode).\n", stderr);
231 		return;
232 	}
233 	fd = fopen(output_filename, FILE_WRITEBINARY);	// FIXME - what if filename is given via !to in sub-dir? fix path!
234 	if (fd == NULL) {
235 		fprintf(stderr, "Error: Cannot open output file \"%s\".\n",
236 			output_filename);
237 		return;
238 	}
239 	Output_save_file(fd);
240 	fclose(fd);
241 }
242 
243 
244 // increment pass number and perform a single pass
perform_pass(void)245 static void perform_pass(void)
246 {
247 	FILE	*fd;
248 	int	ii;
249 
250 	++pass.number;
251 	// call modules' "pass init" functions
252 	Output_passinit();	// disable output, PC undefined
253 	cputype_passinit(default_cpu);	// set default cpu type
254 	// if start address was given on command line, use it:
255 	if (start_address != ILLEGAL_START_ADDRESS)
256 		vcpu_set_pc(start_address, 0);
257 	encoding_passinit();	// set default encoding
258 	section_passinit();	// set initial zone (untitled)
259 	// init variables
260 	pass.undefined_count = 0;
261 	//pass.needvalue_count = 0;	FIXME - use
262 	pass.error_count = 0;
263 	// Process toplevel files
264 	for (ii = 0; ii < toplevel_src_count; ++ii) {
265 		if ((fd = fopen(toplevel_sources[ii], FILE_READBINARY))) {
266 			flow_parse_and_close_file(fd, toplevel_sources[ii]);
267 		} else {
268 			fprintf(stderr, "Error: Cannot open toplevel file \"%s\".\n", toplevel_sources[ii]);
269 			if (toplevel_sources[ii][0] == '-')
270 				fprintf(stderr, "Options (starting with '-') must be given _before_ source files!\n");
271  			++pass.error_count;
272 		}
273 	}
274 	Output_end_segment();
275 /*	TODO:
276 	if --save-start is given, parse arg string
277 	if --save-limit is given, parse arg string
278 */
279 	if (pass.error_count)
280 		exit(ACME_finalize(EXIT_FAILURE));
281 }
282 
283 
284 static struct report	global_report;
285 // do passes until done (or errors occurred). Return whether output is ready.
do_actual_work(void)286 static boolean do_actual_work(void)
287 {
288 	int	undefs_before;	// number of undefined results in previous pass
289 
290 	report = &global_report;	// let global pointer point to something
291 	report_init(report);	// we must init struct before doing passes
292 	if (config.process_verbosity > 1)
293 		puts("First pass.");
294 	pass.complain_about_undefined = FALSE;	// disable until error pass needed
295 	pass.number = -1;	// pre-init, will be incremented by perform_pass()
296 	perform_pass();	// first pass
297 	// pretend there has been a previous pass, with one more undefined result
298 	undefs_before = pass.undefined_count + 1;
299 	// keep doing passes as long as the number of undefined results keeps decreasing.
300 	// stop on zero (FIXME - zero-check pass.needvalue_count instead!)
301 	while (pass.undefined_count && (pass.undefined_count < undefs_before)) {
302 		undefs_before = pass.undefined_count;
303 		if (config.process_verbosity > 1)
304 			puts("Further pass.");
305 		perform_pass();
306 	}
307 	// any errors left?
308 	if (pass.undefined_count == 0) {	// FIXME - use pass.needvalue_count instead!
309 		// if listing report is wanted and there were no errors,
310 		// do another pass to generate listing report
311 		if (report_filename) {
312 			if (config.process_verbosity > 1)
313 				puts("Extra pass to generate listing report.");
314 			if (report_open(report, report_filename) == 0) {
315 				perform_pass();
316 				report_close(report);
317 			}
318 		}
319 		return TRUE;
320 	}
321 	// There are still errors (unsolvable by doing further passes),
322 	// so perform additional pass to find and show them.
323 	if (config.process_verbosity > 1)
324 		puts("Extra pass needed to find error.");
325 	pass.complain_about_undefined = TRUE;	// activate error output
326 	perform_pass();	// perform pass, but now show "value undefined"
327 	return FALSE;
328 }
329 
330 
331 // copy string to DynaBuf
keyword_to_dynabuf(const char keyword[])332 static void keyword_to_dynabuf(const char keyword[])
333 {
334 	DYNABUF_CLEAR(GlobalDynaBuf);
335 	DynaBuf_add_string(GlobalDynaBuf, keyword);
336 	DynaBuf_append(GlobalDynaBuf, '\0');
337 	DynaBuf_to_lower(GlobalDynaBuf, GlobalDynaBuf);	// convert to lower case
338 }
339 
340 
341 // set output format (the output format tree must be set up at this point!)
set_output_format(const char format_name[])342 static void set_output_format(const char format_name[])
343 {
344 	// caution, name may be NULL!
345 	if (format_name) {
346 		keyword_to_dynabuf(format_name);
347 		if (!outputfile_set_format())
348 			return;	// ok
349 
350 		fputs("Error: Unknown output format.\n", stderr);
351 	} else {
352 		fputs("Error: No output format specified.\n", stderr);
353 	}
354 	fprintf(stderr, "Supported formats are:\n\n\t%s\n\n", outputfile_formats);
355 	exit(EXIT_FAILURE);
356 }
357 
358 
359 // set CPU type (the cpu type tree must be set up at this point!)
set_starting_cpu(const char cpu_name[])360 static void set_starting_cpu(const char cpu_name[])
361 {
362 	const struct cpu_type	*new_cpu_type;
363 
364 	// caution, name may be NULL!
365 	if (cpu_name) {
366 		keyword_to_dynabuf(cpu_name);
367 		new_cpu_type = cputype_find();
368 		if (new_cpu_type) {
369 			default_cpu = new_cpu_type;
370 			return;	// ok
371 		}
372 		fputs("Error: Unknown CPU type.\n", stderr);
373 	} else {
374 		fputs("Error: No CPU type specified.\n", stderr);
375 	}
376 	fprintf(stderr, "Supported types are:\n\n\t%s\n\n", cputype_names);
377 	exit(EXIT_FAILURE);
378 }
379 
380 
could_not_parse(const char strange[])381 static void could_not_parse(const char strange[])
382 {
383 	fprintf(stderr, "%sCould not parse '%s'.\n", cliargs_error, strange);
384 	exit(EXIT_FAILURE);
385 }
386 
387 
388 // return signed long representation of string.
389 // copes with hexadecimal if prefixed with "$", "0x" or "0X".
390 // copes with octal if prefixed with "&".	FIXME - add "0o" prefix?
391 // copes with binary if prefixed with "%".	FIXME - add "0b" prefix!
392 // assumes decimal otherwise.
string_to_number(const char * string)393 static signed long string_to_number(const char *string)
394 {
395 	signed long	result;
396 	char		*end;
397 	int		base	= 10;
398 
399 	if (*string == '%') {
400 		base = 2;
401 		++string;
402 	} else if (*string == '&') {
403 		base = 8;
404 		++string;
405 	} else if (*string == '$') {
406 		base = 16;
407 		++string;
408 	} else if ((*string == '0') && ((string[1] == 'x') || (string[1] == 'X'))) {
409 		base = 16;
410 		string += 2;
411 	}
412 	result = strtol(string, &end, base);
413 	if (*end)
414 		could_not_parse(end);
415 	return result;
416 }
417 
418 
419 // set program counter
set_starting_pc(const char expression[])420 static void set_starting_pc(const char expression[])
421 {
422 	start_address = string_to_number(expression);
423 	if ((start_address > -1) && (start_address < 65536))
424 		return;
425 
426 	fprintf(stderr, "%sProgram counter out of range (0-0xffff).\n", cliargs_error);
427 	exit(EXIT_FAILURE);
428 }
429 
430 
431 // set initial memory contents
set_mem_contents(const char expression[])432 static void set_mem_contents(const char expression[])
433 {
434 	fill_value = string_to_number(expression);
435 	if ((fill_value >= -128) && (fill_value <= 255))
436 		return;
437 
438 	fprintf(stderr, "%sInitmem value out of range (0-0xff).\n", cliargs_error);
439 	exit(EXIT_FAILURE);
440 }
441 
442 
443 // define symbol
define_symbol(const char definition[])444 static void define_symbol(const char definition[])
445 {
446 	const char	*walk	= definition;
447 	signed long	value;
448 
449 	// copy definition to GlobalDynaBuf until '=' reached
450 	DYNABUF_CLEAR(GlobalDynaBuf);
451 	while ((*walk != '=') && (*walk != '\0'))
452 		DynaBuf_append(GlobalDynaBuf, *walk++);
453 	if ((*walk == '\0') || (walk[1] == '\0'))
454 		could_not_parse(definition);
455 	value =  string_to_number(walk + 1);
456 	DynaBuf_append(GlobalDynaBuf, '\0');
457 	symbol_define(value);
458 }
459 
460 
461 struct dialect {
462 	enum version	dialect;
463 	const char	*version;
464 	const char	*description;
465 };
466 struct dialect	dialects[]	= {
467 	{VER_OLDEST_SUPPORTED,		"0.85",		"(the oldest version supported)"},
468 	{VER_DEPRECATE_REALPC,		"0.86",		"\"!realpc\" gives a warning, \"!to\" wants a file format"},
469 //	{VER_SHORTER_SETPC_WARNING,	"0.93",		"\"*=\" in offset assembly gives shorter warning but still switches off"},
470 	{VER_RIGHTASSOCIATIVEPOWEROF,	"0.94.6",	"\"power of\" is now right-associative"},
471 //	{VER_,				"0.94.7",	"empty code segments are no longer included in output file"},
472 	{VER_DISABLED_OBSOLETE_STUFF,	"0.94.8",	"\"*=\" works inside \"!pseudopc\", disabled \"!cbm/!realpc/!subzone\""},
473 	{VER_NEWFORSYNTAX,		"0.94.12",	"new \"!for\" syntax"},
474 //	{VER_,				"0.95.2",	"changed ANC#8 from 0x2b to 0x0b"},
475 	{VER_BACKSLASHESCAPING,		"0.97",		"backslash escaping and strings"},
476 //	{VER_CURRENT,			"default",	"default"},
477 	{VER_FUTURE,			"future",	"enable all experimental features"},
478 	{0,				NULL,		NULL}	// NULLs terminate
479 };
480 
481 // choose dialect (mimic behaviour of different version)
set_dialect(const char version[])482 static void set_dialect(const char version[])
483 {
484 	struct dialect	*dia;
485 
486 	// caution, version may be NULL!
487 	if (version) {
488 		// scan array
489 		for (dia = dialects; dia->version; ++dia) {
490 			if (strcmp(version, dia->version) == 0) {
491 				config.wanted_version = dia->dialect;
492 				return;	// found
493 			}
494 		}
495 		fputs("Error: Unknown dialect specifier.\n", stderr);
496 	} else {
497 		fputs("Error: No dialect specified.\n", stderr);
498 	}
499 	// output table of possible versions and die
500 	fputs(
501 "Supported dialects are:\n"
502 "\n"
503 "\tdialect\t\tdescription\n"
504 "\t-------\t\t-----------\n", stderr);
505 	for (dia = dialects; dia->version; ++dia)
506 		fprintf(stderr, "\t%s\t\t%s\n", dia->version, dia->description);
507 	fputc('\n', stderr);
508 	exit(EXIT_FAILURE);
509 }
510 
511 
512 // handle long options (like "--example"). Return unknown string.
long_option(const char * string)513 static const char *long_option(const char *string)
514 {
515 	if (strcmp(string, OPTION_HELP) == 0)
516 		show_help_and_exit();
517 	else if (strcmp(string, OPTION_FORMAT) == 0)
518 		set_output_format(cliargs_get_next());	// NULL is ok (handled like unknown)
519 	else if (strcmp(string, OPTION_OUTFILE) == 0)
520 		output_filename = cliargs_safe_get_next(name_outfile);
521 	else if (strcmp(string, OPTION_LABELDUMP) == 0)	// old
522 		symbollist_filename = cliargs_safe_get_next(arg_symbollist);
523 	else if (strcmp(string, OPTION_SYMBOLLIST) == 0)	// new
524 		symbollist_filename = cliargs_safe_get_next(arg_symbollist);
525 	else if (strcmp(string, OPTION_VICELABELS) == 0)
526 		vicelabels_filename = cliargs_safe_get_next(arg_vicelabels);
527 	else if (strcmp(string, OPTION_REPORT) == 0)
528 		report_filename = cliargs_safe_get_next(arg_reportfile);
529 	else if (strcmp(string, OPTION_SETPC) == 0)
530 		set_starting_pc(cliargs_safe_get_next("program counter"));
531 	else if (strcmp(string, OPTION_CPU) == 0)
532 		set_starting_cpu(cliargs_get_next());	// NULL is ok (handled like unknown)
533 	else if (strcmp(string, OPTION_INITMEM) == 0)
534 		set_mem_contents(cliargs_safe_get_next("initmem value"));
535 	else if (strcmp(string, OPTION_MAXERRORS) == 0)
536 		config.max_errors = string_to_number(cliargs_safe_get_next("maximum error count"));
537 	else if (strcmp(string, OPTION_MAXDEPTH) == 0)
538 		macro_recursions_left = (source_recursions_left = string_to_number(cliargs_safe_get_next("recursion depth")));
539 //	else if (strcmp(string, "strictsyntax") == 0)
540 //		strict_syntax = TRUE;
541 	else if (strcmp(string, OPTION_USE_STDOUT) == 0)
542 		config.msg_stream = stdout;
543 	else if (strcmp(string, OPTION_MSVC) == 0)
544 		config.format_msvc = TRUE;
545 	else if (strcmp(string, OPTION_FULLSTOP) == 0)
546 		config.pseudoop_prefix = '.';
547 	else if (strcmp(string, OPTION_IGNORE_ZEROES) == 0)
548 		config.honor_leading_zeroes = FALSE;
549 	else if (strcmp(string, OPTION_STRICT_SEGMENTS) == 0)
550 		config.segment_warning_is_error = TRUE;
551 	else if (strcmp(string, OPTION_DIALECT) == 0)
552 		set_dialect(cliargs_get_next());	// NULL is ok (handled like unknown)
553 	else if (strcmp(string, OPTION_TEST) == 0) {
554 		config.wanted_version = VER_FUTURE;
555 		config.test_new_features = TRUE;
556 	} PLATFORM_LONGOPTION_CODE
557 	else if (strcmp(string, OPTION_COLOR) == 0)
558 		config.format_color = TRUE;
559 	else if (strcmp(string, OPTION_VERSION) == 0)
560 		show_version(TRUE);
561 	else
562 		return string;
563 	return NULL;
564 }
565 
566 
567 // handle short options (like "-e"). Return unknown character.
short_option(const char * argument)568 static char short_option(const char *argument)
569 {
570 	while (*argument) {
571 		switch (*argument) {
572 		case 'D':	// "-D" define constants
573 			define_symbol(argument + 1);
574 			goto done;
575 		case 'f':	// "-f" selects output format
576 			set_output_format(cliargs_get_next());	// NULL is ok (handled like unknown)
577 			break;
578 		case 'h':	// "-h" shows help
579 			show_help_and_exit();
580 			break;
581 		case 'I':	// "-I" adds an include directory
582 			if (argument[1])
583 				includepaths_add(argument + 1);
584 			else
585 				includepaths_add(cliargs_safe_get_next("include path"));
586 			goto done;
587 		case 'l':	// "-l" selects symbol list filename
588 			symbollist_filename = cliargs_safe_get_next(arg_symbollist);
589 			break;
590 		case 'o':	// "-o" selects output filename
591 			output_filename = cliargs_safe_get_next(name_outfile);
592 			break;
593 		case 'r':	// "-r" selects report filename
594 			report_filename = cliargs_safe_get_next(arg_reportfile);
595 			break;
596 		case 'v':	// "-v" changes verbosity
597 			++config.process_verbosity;
598 			if ((argument[1] >= '0') && (argument[1] <= '9'))
599 				config.process_verbosity = *(++argument) - '0';
600 			break;
601 		// platform specific switches are inserted here
602 			PLATFORM_SHORTOPTION_CODE
603 		case 'V':	// "-V" shows version
604 			show_version(TRUE);
605 			break;
606 		case 'W':	// "-W" tunes warning level
607 			if (strcmp(argument + 1, OPTIONWNO_LABEL_INDENT) == 0) {
608 				config.warn_on_indented_labels = FALSE;
609 				goto done;
610 			} else if (strcmp(argument + 1, OPTIONWNO_OLD_FOR) == 0) {
611 				config.wanted_version = VER_NEWFORSYNTAX - 1;
612 				goto done;
613 			} else if (strcmp(argument + 1, OPTIONWNO_BIN_LEN) == 0) {
614 				config.warn_bin_mask = 0;
615 				goto done;
616 			} else if (strcmp(argument + 1, OPTIONWTYPE_MISMATCH) == 0) {
617 				config.warn_on_type_mismatch = TRUE;
618 				goto done;
619 			} else {
620 				fprintf(stderr, "%sUnknown warning level.\n", cliargs_error);
621 				exit(EXIT_FAILURE);
622 			}
623 			break;
624 		default:	// unknown ones: program termination
625 			return *argument;
626 		}
627 		++argument;
628 	}
629 done:
630 	return '\0';
631 }
632 
633 
634 // guess what
main(int argc,const char * argv[])635 int main(int argc, const char *argv[])
636 {
637 	config_default(&config);
638 	// if called without any arguments, show usage info (not full help)
639 	if (argc == 1)
640 		show_help_and_exit();
641 	cliargs_init(argc, argv);
642 	// init platform-specific stuff.
643 	// this may read the library path from an environment variable.
644 	PLATFORM_INIT;
645 	// handle command line arguments
646 	cliargs_handle_options(short_option, long_option);
647 	// generate list of files to process
648 	cliargs_get_rest(&toplevel_src_count, &toplevel_sources, "No top level sources given");
649 	// init output buffer
650 	Output_init(fill_value, config.test_new_features);
651 	if (do_actual_work())
652 		save_output_file();
653 	return ACME_finalize(EXIT_SUCCESS);	// dump labels, if wanted
654 }
655