/* main.c - main program.
aspp - simple assembler source file preprocessor.
Author: Ivan Tatarinov, <ivan-tat@ya.ru>, 2019-2020.
This is free and unencumbered software released into the public domain.
For more information, please refer to <http://unlicense.org>.
Home page: <https://gitlab.com/ivan-tat/aspp> */
#include "defs.h"
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <locale.h>
#include <ctype.h>
#include "asmfile.h"
#include "debug.h"
#include "l_err.h"
#include "l_ifile.h"
#include "l_inc.h"
#include "l_isrc.h"
#include "l_list.h"
#include "l_pre.h"
#include "l_src.h"
#include "l_tgt.h"
#include "parser.h"
#include "platform.h"
#define PROGRAM_NAME "aspp"
#define PROGRAM_VERSION "0.1" PROGRAM_VERSION_SUFFIX
#define PROGRAM_DESCRIPTION "Simple assembler source file preprocessor."
#define PROGRAM_LICENSE \
"License: public domain, <http://unlicense.org>" NL \
"This is free software; you are free to change and redistribute it." NL \
"There is NO WARRANTY, to the extent permitted by law."
#define PROGRAM_AUTHORS \
"Author: Ivan Tatarinov, <ivan-tat@ya.ru>, 2019-2020."
#define PROGRAM_CONTACTS \
"Home page: <https://gitlab.com/ivan-tat/aspp>"
#define HELP_HINT \
"Use '-h' or '--help' to get help."
// Acts
#define ACT_NONE 0
#define ACT_SHOW_HELP 1
#define ACT_PREPROCESS 2
#define ACT_MAKE_RULE 3
// Variables
struct errors_t
errors = { { .first = NULL, .last = NULL, .count = 0 } };
unsigned v_syntax = SYNTAX_TASM;
unsigned v_act = ACT_NONE;
char v_act_show_help = 0;
char v_act_preprocess = 0;
char v_act_make_rule = 0;
char *v_base_path_real = NULL;
struct include_paths_t
v_include_paths = { { .first = NULL, .last = NULL, .count = 0 } };
struct input_sources_t
v_input_sources = { { .first = NULL, .last = NULL, .count = 0 } };
struct sources_t
v_sources = { { .first = NULL, .last = NULL, .count = 0 } };
struct target_names_t
v_target_names = { { .first = NULL, .last = NULL, .count = 0 } };
char *v_output_name;
struct prerequisites_t
v_prerequisites = { { .first = NULL, .last = NULL, .count = 0 } };
#if DEBUG == 1
void _DBG_dump_vars (void)
{
const char *s;
_DBG_ ("Input files syntax = '%s'", _syntax_to_str (v_syntax, &s) ? s : "unknown");
_DBG_include_paths_dump (&v_include_paths);
_DBG_input_sources_dump (&v_input_sources);
_DBG_target_names_dump (&v_target_names);
_DBG_ ("Output file name = '%s'", v_output_name);
}
#else // DEBUG != 1
#define _DBG_dump_vars(x)
#endif // DEBUG != 1
// Returns "false" on success.
bool add_error (const char *format, ...)
{
va_list ap;
bool status;
status = errors_add_vfmt (&errors, NULL, 1024, format, ap);
return status;
}
// Returns "false" on success.
bool add_missing_arg_error (const char *name, unsigned index)
{
return add_error ("Missing parameter for %s (argument #%u).", name, index);
}
void show_errors (void)
{
const struct error_entry_t *p;
for (p = (struct error_entry_t *) errors.list.first; p;
p = (struct error_entry_t *) p->list_entry.next)
}
void exit_on_errors (void)
{
if (errors.list.count)
{
fprintf (stderr
, "Errors: %u. Stopped." NL
, errors.
list.
count);
}
}
void error_exit (const char *format, ...)
{
va_list ap;
}
void show_title (void)
{
"%s (version %s) - %s" NL
"%s" NL
"%s" NL
"%s" NL,
PROGRAM_NAME,
PROGRAM_VERSION,
PROGRAM_DESCRIPTION,
PROGRAM_LICENSE,
PROGRAM_AUTHORS,
PROGRAM_CONTACTS
);
}
void show_help (void)
{
NL
"Usage:" NL
" %s [options] [filename ...] [options]" NL
NL
"Options (GCC-compatible):" NL
"-h, --help show this help and exit" NL
"-E preprocess" NL
"-I <path> include directory" NL
"-M[M] output autodepend make rule" NL
"-MF <file> autodepend output name" NL
"-MT <target> autodepend target name (can be specified multiple times)" NL
NL
"Other options:" NL
"--syntax <syntax> select source file syntax (tasm, sjasm)" NL,
PROGRAM_NAME
);
}
// Result must be freed by caller.
char *_make_path (const char *a, const char *b)
{
char *result;
if (result)
sprintf (result
, "%s" PATHSEPSTR
"%s", a
, b
);
else
{
_perror ("malloc");
}
return result;
}
// Returns "true" on success.
bool process_included_file (struct source_entry_t *src, char *f_loc, unsigned inc_flags)
{
bool ok;
char *tmp;
char *src_base;
char *src_base_tmp;
char *inc_real, *inc_base, *inc_user;
char *inc_real_tmp, *inc_base_tmp, *inc_user_tmp;
char *inc_real_res;
struct include_path_entry_t *resolved;
_DBG_ ("Source user file = '%s'", src->user);
_DBG_ ("Source base path = '%s'", src->base);
_DBG_ ("Source real file = '%s'", src->real);
_DBG_ ("Include file = '%s'", f_loc);
_DBG_ ("Include flags = 0x%X", inc_flags);
ok = false;
src_base_tmp = (char *) NULL;
inc_real_tmp = (char *) NULL;
inc_base_tmp = (char *) NULL;
inc_user_tmp = (char *) NULL;
inc_real_res = (char *) NULL;
inc_user = f_loc;
if (check_path_abs (f_loc))
{
// absolute source's path - use it as is
inc_real = f_loc;
inc_base_tmp = get_dir_name (f_loc);
if (!inc_base_tmp)
{
// Fail
_perror ("get_dir_name");
goto _local_exit;
}
inc_base = inc_base_tmp;
if (!check_file_exists (f_loc))
inc_flags &= ~SRCFL_PARSE;
if (sources_add (&v_sources, inc_real, inc_base, inc_user, inc_flags, NULL))
{
// Fail
_perror ("sources_add");
goto _local_exit;
}
// Success
ok = true;
}
else
{
// relative source's path - try to resolve real name
src_base_tmp = get_dir_name (src->user);
if (!src_base_tmp)
{
// Fail
_perror ("get_dir_name");
goto _local_exit;
}
src_base = src_base_tmp;
if (check_path_abs (src->user))
{
// Absolute path of primary source file
tmp = _make_path (src_base, src->user);
if (!tmp)
{
// Fail
_perror ("_make_path");
goto _local_exit;
}
inc_real_tmp = resolve_full_path (tmp);
if (!inc_real_tmp)
{
// Fail
_perror ("resolve_full_path");
goto _local_exit;
}
inc_real = inc_real_tmp;
inc_base = src_base;
}
else
{
// Relative path of primary source file
tmp = get_dir_name (src->real);
if (!tmp)
{
// Fail
_perror ("get_dir_name");
goto _local_exit;
}
inc_real_tmp = _make_path (tmp, f_loc);
if (!inc_real_tmp)
{
// Fail
_perror ("_make_path");
goto _local_exit;
}
tmp = inc_real_tmp;
inc_real_tmp = resolve_full_path (tmp);
if (!inc_real_tmp)
{
// Fail
_perror ("resolve_full_path");
goto _local_exit;
}
inc_real = inc_real_tmp;
inc_base = src->base;
if (strcmp (src_base
, ".") != 0)
{
inc_user_tmp = _make_path (src_base, inc_user);
if (!inc_user_tmp)
{
// Fail
_perror ("_make_path");
goto _local_exit;
}
inc_user = inc_user_tmp;
}
}
if (check_file_exists (inc_real))
{
if (sources_add (&v_sources, inc_real, inc_base, inc_user, inc_flags, NULL))
{
// Fail
_perror ("sources_add");
goto _local_exit;
}
// Success
}
else
{
_DBG_ ("'%s' not found, resolving...", f_loc);
if (!include_paths_resolve_file (&v_include_paths, f_loc, &resolved))
{
tmp = _make_path (resolved->real, f_loc);
if (!tmp)
{
// Fail
_perror ("_make_path");
goto _local_exit;
}
inc_real_res = resolve_full_path (tmp);
if (!inc_real_res)
{
// Fail
_perror ("resolve_full_path");
goto _local_exit;
}
inc_real = inc_real_res;
inc_base = resolved->real;
inc_user = f_loc;
}
else
{
inc_flags = 0;
}
if (sources_add (&v_sources, inc_real, inc_base, inc_user, 0, NULL))
{
// Fail
_perror ("sources_add");
goto _local_exit;
}
// Success
}
// Success
ok = true;
}
_local_exit:
if (src_base_tmp)
if (inc_real_tmp)
if (inc_base_tmp)
if (inc_user_tmp)
if (inc_real_res)
_DBG_ ("Done checking '%s' (%s).", f_loc, ok ? "success" : "failed");
return ok;
}
// Returns "false" on success.
bool collect_included_files (struct source_entry_t *src)
{
bool ok;
struct asm_file_t file;
char *t;
const char *s;
unsigned tl, len;
unsigned inc_flags;
char *inc_name;
get_include_proc_t *getincl;
char st;
struct included_file_entry_t *incl;
_DBG_ ("Source user file = '%s'", src->user);
_DBG_ ("Source base path = '%s'", src->base);
_DBG_ ("Source real file = '%s'", src->real);
ok = false;
// Free on exit (_local_exit):
asm_file_clear (&file);
t = (char *) NULL;
if (!_find_get_include_proc (v_syntax, &getincl))
{
// Fail
_DBG ("Unknown syntax specified.");
goto _local_exit;
}
if (!asm_file_load (&file, src->user))
{
// Fail
goto _local_exit;
}
tl = 0;
while (asm_file_next_line (&file, &s, &len))
{
// Free on exit (_loop_exit):
inc_name = (char *) NULL;
if (tl < len + 1)
{
tl = len + 1; // + terminating zero
if (t)
if (!t)
{
// Fail
_perror ("malloc");
goto _loop_exit;
}
}
t[len] = '\0';
st = getincl (t, &inc_flags, &inc_name);
switch (st)
{
case PARST_OK:
if (included_files_find (&src->included, inc_name, &incl))
{
if (included_files_add (&src->included, file.line, inc_flags, inc_name, NULL))
{
// Fail
goto _loop_exit;
}
}
else
{
// HINT: This is weird if we included this file as binary but now we want to parse it
if ((inc_flags & SRCFL_PARSE) && !(incl->flags & SRCFL_PARSE))
incl->flags |= SRCFL_PARSE;
}
if (inc_name)
inc_name = (char *) NULL;
break;
case PARST_SKIP:
goto _skip_line;
default:
// Error
goto _loop_exit;
}
_skip_line:;
}
ok = true;
goto _local_exit;
_loop_exit:
if (inc_name)
_local_exit:
asm_file_free (&file);
if (t)
_DBG_ ("Done collecting included files of '%s' (%s).", src->user, ok ? "success" : "failed");
return !ok;
}
// Returns "false" on success.
bool process_included_files_list (struct source_entry_t *src)
{
bool ok;
struct included_file_entry_t *p;
ok = false;
p = (struct included_file_entry_t *) src->included.list.first;
while (p)
{
if (!process_included_file (src, p->name, p->flags))
{
// Fail
goto _local_exit;
}
p = (struct included_file_entry_t *) p->list_entry.next;
}
ok = true;
_local_exit:
_DBG_ ("Done parsing included files of '%s' (%s).", src->user, ok ? "success" : "failed");
return !ok;
}
// Returns "false" on success.
bool parse_source (struct source_entry_t *src)
{
bool ok;
ok = false;
if (collect_included_files (src))
{
// Fail
goto _local_exit;
}
_DBG_ ("Found %u included files.", src->included.list.count);
if (src->included.list.count && process_included_files_list (src))
{
// Fail
goto _local_exit;
}
ok = true;
_local_exit:
_DBG_ ("Done parsing '%s' (%s).", src->user, ok ? "success" : "failed");
return !ok;
}
// Returns "false" on success.
bool make_rule (void)
{
struct input_source_entry_t *isrc;
struct source_entry_t *src, *last;
for (isrc = (struct input_source_entry_t *) v_input_sources.list.first; isrc;
isrc = (struct input_source_entry_t *) isrc->list_entry.next)
{
if (sources_add (&v_sources, isrc->real, isrc->base, isrc->user, SRCFL_PARSE, NULL))
return true; // Fail
}
if (v_sources.list.count)
{
src = (struct source_entry_t *) v_sources.list.first;
last = (struct source_entry_t *) v_sources.list.last;
while (src != (struct source_entry_t *) last->list_entry.next)
{
while (src != (struct source_entry_t *) last->list_entry.next)
{
if (src->flags & SRCFL_PARSE)
{
if (!parse_source (src))
{
if (prerequisites_add (&v_prerequisites, src->user, NULL))
return true; // Fail
}
else
{
show_errors ();
exit_on_errors ();
}
}
else
{
if (prerequisites_add (&v_prerequisites, src->user, NULL))
return true; // Fail
}
src = (struct source_entry_t *) src->list_entry.next;
}
src = (struct source_entry_t *) last->list_entry.next;
last = (struct source_entry_t *) v_sources.list.last;
}
}
return false; // Success
}
// Returns "false" on success.
bool write_rule (const char *name)
{
FILE *f;
if (!f)
{
// Fail
_perror ("fopen");
return true;
}
if (target_names_print (&v_target_names, f))
return true; // Fail
return true; // Fail
if (prerequisites_print (&v_prerequisites, f))
return true; // Fail
return true; // Fail
return false; // Success
}
int main (int argc, char **argv)
{
unsigned i;
if (argc == 1)
error_exit ("No parameters. %s" NL, HELP_HINT);
v_base_path_real = get_current_dir ();
if (!v_base_path_real)
error_exit ("Failed to get current directory." NL);
_DBG_ ("Base path = '%s'", v_base_path_real);
i = 1;
while (i < argc)
{
if (strcmp (argv
[i
], "-h") == 0
|| strcmp (argv
[i
], "--help") == 0)
{
v_act_show_help = 1;
i++;
}
else if (strcmp (argv
[i
], "-E") == 0)
{
v_act_preprocess = 1;
i++;
}
else if (strcmp (argv
[i
], "-I") == 0)
{
i++;
if (i == argc)
{
if (add_missing_arg_error ("-I", i))
break;
}
if (include_paths_add_with_check (&v_include_paths, argv[i], v_base_path_real, NULL))
i++;
}
else if (strcmp (argv
[i
], "-M") == 0
|| strcmp (argv
[i
], "-MM") == 0)
{
v_act_make_rule = 1;
i++;
}
else if (strcmp (argv
[i
], "-MF") == 0)
{
i++;
if (i == argc)
{
if (add_missing_arg_error ("-MF", i))
break;
}
v_output_name = argv[i];
i++;
}
else if (strcmp (argv
[i
], "-MT") == 0)
{
i++;
if (i == argc)
{
if (add_missing_arg_error ("-MT", i))
break;
}
if (target_names_add (&v_target_names, argv[i], NULL))
i++;
}
else if (strcmp (argv
[i
], "--syntax") == 0)
{
i++;
if (i == argc)
{
if (add_missing_arg_error ("--syntax", i))
break;
}
if (!_str_to_syntax (argv[i], &v_syntax))
if (add_error ("Unknown syntax '%s' (#%u).", argv[i], i))
i++;
}
else if (argv[i][0] == '-')
{
if (add_error ("Unknown option '%s' (#%u).", argv[i], i))
i++;
}
else
{
if (v_input_sources.list.count >= 1)
{
if (add_error ("Don't know what to do with input file '%s' (#%u).", argv[i], i))
}
else
{
if (input_sources_add_with_check (&v_input_sources, argv[i], v_base_path_real, NULL))
error_exit ("Input source file '%s' was not found." NL, argv[i]);
}
i++;
}
}
if (v_act_show_help)
{
if (v_act_preprocess + v_act_make_rule + v_include_paths.list.count + v_sources.list.count)
{
if (add_error ("Other arguments were ignored."))
}
v_act = ACT_SHOW_HELP;
}
else
{
if (v_act_preprocess + v_act_make_rule != 2)
{
if (add_error ("The only supported mode is when both options -E and -M are specified."))
}
v_act = ACT_MAKE_RULE;
}
if (errors.list.count)
{
if (v_act_show_help)
show_title ();
show_errors ();
if (v_act_show_help)
{
show_help ();
}
else
exit_on_errors ();
}
switch (v_act)
{
case ACT_SHOW_HELP:
show_title ();
show_help ();
break;
case ACT_MAKE_RULE:
if (!v_target_names.list.count)
{
if (add_error ("No target name was specified."))
}
if (!v_output_name
|| !strcmp (v_output_name
, ""))
{
if (add_error ("No output name was specified."))
}
if (!v_input_sources.list.count)
{
if (add_error ("No source files were specified."))
}
if (errors.list.count)
{
show_errors ();
exit_on_errors ();
}
if (!v_include_paths.list.count)
{
if (include_paths_add_with_check (&v_include_paths, ".", v_base_path_real, NULL))
}
_DBG_dump_vars ();
if (make_rule ())
error_exit ("Failed to parse sources.");
if (write_rule (v_output_name))
error_exit ("Failed to write to output file.");
break;
default:
error_exit ("Action %u is not implemented yet.", v_act);
break;
}
return EXIT_SUCCESS;
}