Login

Subversion Repositories NedoOS

Rev

Blame | Last modification | View Log | Download | RSS feed

/* 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;

    va_start (ap, format);
    status = errors_add_vfmt (&errors, NULL, 1024, format, ap);
    va_end (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)
        fprintf (stderr, "%s" NL, p->msg);
}

void exit_on_errors (void)
{
    if (errors.list.count)
    {
        fprintf (stderr, "Errors: %u. Stopped." NL, errors.list.count);
        exit (EXIT_FAILURE);
    }
}

void error_exit (const char *format, ...)
{
    va_list ap;

    va_start (ap, format);
    vfprintf (stderr, format, ap);
    va_end (ap);
    exit (EXIT_FAILURE);
}

void show_title (void)
{
    fprintf (stdout,
        "%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)
{
    fprintf (stdout,
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;

    result = malloc (strlen (a) + 1 + strlen (b) + 1);  // including terminating zero
    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);
            free (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);
            free (tmp);
            if (!inc_real_tmp)
            {
                // Fail
                _perror ("_make_path");
                goto _local_exit;
            }
            tmp = inc_real_tmp;
            inc_real_tmp = resolve_full_path (tmp);
            free (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);
                free (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)
        free (src_base_tmp);
    if (inc_real_tmp)
        free (inc_real_tmp);
    if (inc_base_tmp)
        free (inc_base_tmp);
    if (inc_user_tmp)
        free (inc_user_tmp);
    if (inc_real_res)
        free (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)
                free (t);
            t = malloc (tl);
            if (!t)
            {
                // Fail
                _perror ("malloc");
                goto _loop_exit;
            }
        }
        memcpy (t, s, len);
        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)
                free (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)
        free (inc_name);
_local_exit:
    asm_file_free (&file);
    if (t)
        free (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;

    f = fopen (name, "w");
    if (!f)
    {
        // Fail
        _perror ("fopen");
        return true;
    }

    if (target_names_print (&v_target_names, f))
        return true;    // Fail

    if (fprintf (f, ": ") < 0)
        return true;    // Fail

    if (prerequisites_print (&v_prerequisites, f))
        return true;    // Fail

    if (fprintf (f, NL) < 0)
        return true;    // Fail

    fclose (f);

    return false;       // Success
}

int main (int argc, char **argv)
{
    unsigned i;

    setlocale (LC_CTYPE, "C");

    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))
                    exit (EXIT_FAILURE);
                break;
            }
            if (include_paths_add_with_check (&v_include_paths, argv[i], v_base_path_real, NULL))
                exit (EXIT_FAILURE);
            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))
                    exit (EXIT_FAILURE);
                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))
                    exit (EXIT_FAILURE);
                break;
            }
            if (target_names_add (&v_target_names, argv[i], NULL))
                exit (EXIT_FAILURE);
            i++;
        }
        else if (strcmp (argv[i], "--syntax") == 0)
        {
            i++;
            if (i == argc)
            {
                if (add_missing_arg_error ("--syntax", i))
                    exit (EXIT_FAILURE);
                break;
            }
            if (!_str_to_syntax (argv[i], &v_syntax))
                if (add_error ("Unknown syntax '%s' (#%u).", argv[i], i))
                    exit (EXIT_FAILURE);
            i++;
        }
        else if (argv[i][0] == '-')
        {
            if (add_error ("Unknown option '%s' (#%u).", argv[i], i))
                exit (EXIT_FAILURE);
            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))
                    exit (EXIT_FAILURE);
            }
            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."))
                exit (EXIT_FAILURE);
        }
        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."))
                exit (EXIT_FAILURE);
        }
        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 ();
            exit (EXIT_SUCCESS);
        }
        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."))
                exit (EXIT_FAILURE);
        }
        if (!v_output_name || !strcmp (v_output_name, ""))
        {
            if (add_error ("No output name was specified."))
                exit (EXIT_FAILURE);
        }
        if (!v_input_sources.list.count)
        {
            if (add_error ("No source files were specified."))
                exit (EXIT_FAILURE);
        }
        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))
                exit (EXIT_FAILURE);
        }
        _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;
}