root/usr/src/cmd/abi/spectrans/spec2map/versions.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1997-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "xlator.h"
#include "util.h"
#include "bucket.h"
#include "errlog.h"

/* Types: */
#define TRUE    1
#define FALSE   0
#define MAXLINE 1024


typedef enum {
        PARENT, UNCLE
} RELATION;


/* Statics: */
/* The parser is a dfa, driven by the following: */
static FILE *Fp;
static const char *Filename;
static char Previous[MAXLINE];
static char LeftMostChild[MAXLINE];
static int Selected = FALSE;
static int Line;
static int Errors;


/* The grammar is: */
static int arch(void);
static int comment(void);
static int arch_name(void);
static int set_list(void);
static int set(void);

/* The supporting code is: */
static int accept_token(char *);
static void skip_to(char *);

/* And the tokenizer is: */
static char *tokenize(char *);
static char *currtok(void);
static char *nexttok(void);
static char *skipb(char *);
static char *skipover(char *);
static char *CurrTok = NULL;

static int set_parents(void);

static table_t *Vers;
static table_t *Varch;

static void init_tables(void);

static void add_valid_arch(char *);
static void add_valid_version(char *vers_name);


#define in_specials(c)  ((c) == '{' || (c) == '}' || (c) == '+' || \
        (c) == '-' || (c) == ';' || (c) == ':' || (c) == ',' || \
        (c) == '[' || (c) == ']')

#define eq(s1, s2)      (strcmp((s1), (s2)) == 0)


/*
 * parse_versions -- parse the file whose name is passed, return
 *      the number of (fatal) errors encountered. Currently only
 *      knows about reading set files and writing vers files.
 */
int
parse_versions(const char *fileName)
{

        /* Prime the set-file parser dfa: */
        assert(fileName != NULL, "passed null filename to parse_versions");
        errlog(BEGIN, "parse_versions(%s) {", fileName);


        if ((Fp = fopen(fileName, "r")) == NULL) {
                (void) fprintf(stderr, "Cannot open version file \"%s\"\n",
                    fileName);
                errlog(END, "} /* parse_versions */");
                return (1);
        }
        Filename = fileName;
        Line = 0;

        errlog(VERBOSE, "reading set file %s looking for architecture %s",
            Filename, TargetArchStr);

        /* Run the dfa. */
        while (arch())
                continue;

        (void) fclose(Fp);
        /* print_all_buckets(); */
        errlog(END, "} /* parse_versions */");
        return (Errors);
}


/*
 * The parser. This implements the grammar:
 *    setfile::= (arch())+ <EOF>
 *             | <EOF>
 *    arch::= <ARCHITECTURE> "{" (set_list())* "}"
 *    set_list::= (set())+ ";"
 *    set::= <IDENTIFIER> ["[" "WEAK" "]"] ":" "{" (ancestors) "}" ";"
 *    ancestors::= <IDENTIFIER> | <ancestors> "," <IDENTIFIER>
 *    where <ARCHITECTURE> and <IDENTIFIER> are tokens.
 */
static int
arch(void)
{
        int olderrors;

        errlog(BEGIN, "arch() {");
        if (comment()) {
                errlog(END, "} /* arch */");
                return (TRUE);
        }
        if (arch_name() == FALSE) {
                errlog(END, "} /* arch */");
                return (FALSE);
        }
        if (accept_token("{") == FALSE) {
                errlog(END, "} /* arch */");
                return (FALSE);
        }

        olderrors = Errors;
        if (set_list() == FALSE) {
                if (olderrors != Errors) {
                        errlog(END, "} /* arch */");
                        return (FALSE);
                }
        }

        errlog(END, "} /* arch */");
        return (TRUE);
}

static int
comment(void)
{
        char *token = currtok();

        if (token == NULL || *token != '#') {
                return (FALSE);
        } else {
                /* Swallow token. */
                token =  nexttok();
                return (TRUE);
        }
}

static int
arch_name(void)
{
        char *token = currtok();

        errlog(BEGIN, "arch_name() {");
        errlog(VERBOSE, "token = '%s';",
                token ? token : "<NULL>");

        if (token == NULL) {
                errlog(END, "} /* arch_name */");
                return (FALSE);

        } else if (in_specials(*token)) {
                /* It's not an architecture */
                Selected = FALSE;

                /* Report a syntax error: TBD */
                errlog(INPUT | ERROR, "found special char. %c "
                    "while looking for an architecture name",
                    *token);

                skip_to("}");   /* The follower set for arch_name. */
                errlog(END, "} /* arch name */");

                Errors++;
                return (FALSE);

        } else if (!eq(token, TargetArchStr)) {
                /* It's an architecture ... */
                errlog(VERBOSE, "Begin unselected architecture: %s", token);
                add_valid_arch(token);
                (void) nexttok();

                /* ... but the the wrong one. */
                Selected = FALSE;
                errlog(END, "} /* arch name */");
                return (TRUE);
        } else {
                /* Found the right architecture. */
                errlog(VERBOSE, "Begin selected architecture: %s", token);
                add_valid_arch(token);
                (void) nexttok();
                Selected = TRUE;
                errlog(END, "} /* arch name */");
                return (TRUE);
        }
}


static int
set_list(void)
{
        int olderrors;
        char *token = currtok();

        errlog(BEGIN, "set_list() {");
        errlog(VERBOSE, "token = '%s'",
            (token) ? token : "<NULL>");
        if (set() == FALSE) {
                errlog(END, "} /* set_list */");
                return (FALSE);
        }

        olderrors = Errors;
        while (set()) {
                continue;
        }
        if (olderrors != Errors) {
                errlog(END, "} /* set_list */");
                return (FALSE);
        }

        errlog(END, "} /* set_list */");
        return (TRUE);
}


static int
set(void)
{
        char *token = currtok();
        int has_parent = 0;

        errlog(BEGIN, "set() {");
        errlog(VERBOSE, "token = '%s'",
            (token) ? token : "<NULL>");

        if (in_specials(*token)) {
                errlog(INPUT|ERROR, "unexpected token \"%s\" found. "
                    "Version name expected", token);
                Errors++;
                errlog(END, "} /* set */");
                return (FALSE);
        }

        errlog(VERBOSE, "Begin Version: %s", token);
        *Previous = '\0';
        if (Selected) {
                if (add_parent(token, Previous, 0) == FALSE) {
                        errlog(INPUT | ERROR, "unable to add a parent version "
                            "from the set file");
                        Errors++;
                        errlog(END, "} /* set */");
                        return (FALSE);
                }
        }

        add_valid_version(token);
        (void) strncpy(LeftMostChild, token, MAXLINE);
        LeftMostChild[MAXLINE-1] = '\0';
        (void) strncpy(Previous, token, MAXLINE);
        Previous[MAXLINE-1] = '\0';

        token = nexttok();

        switch (*token) {
                case ':':
                        errlog(VERBOSE, "token ':' found");
                        (void) accept_token(":");
                        if (set_parents() == FALSE) {
                                errlog(END, "} /* set */");
                                return (FALSE);
                        }
                        if (accept_token(";") == FALSE) {
                                errlog(END, "} /* set */");
                                return (FALSE);
                        }
                        errlog(VERBOSE, "End Version");
                        break;

                case ';':
                        errlog(VERBOSE, "token ';' found");
                        (void) accept_token(";");
                        errlog(VERBOSE, "End version ':'");
                        break;

                case '[':
                        (void) accept_token("[");
                        if (accept_token("WEAK") == FALSE) {
                                errlog(END, "} /* set */");
                                return (FALSE);
                        }
                        if (accept_token("]") == FALSE) {
                                errlog(END, "} /* set */");
                                return (FALSE);
                        }
                        token = currtok();
                        if (eq(token, ":")) {
                                (void) accept_token(":");
                                has_parent = 1;
                        } else if (eq(token, ";")) {
                                (void) accept_token(";");
                        } else {
                                errlog(ERROR|INPUT,
                                    "Unexpected token \"%s\" found. ':'"
                                    "or ';' expected.", token);
                                Errors++;
                                errlog(END, "} /* set */");
                                return (FALSE);
                        }
                        errlog(VERBOSE, "WEAK version detected\n");
                        if (Selected)
                                set_weak(LeftMostChild, TRUE);

                        if (has_parent) {
                                if (set_parents() == FALSE) {
                                        errlog(END, "} /* set */");
                                        return (FALSE);
                                }
                                if (accept_token(";") == FALSE) {
                                        errlog(END, "} /* set */");
                                        return (FALSE);
                                }
                        }
                        errlog(VERBOSE, "End Version");
                        break;
                default:
                        /* CSTYLED */
                        errlog(ERROR|INPUT,
                            "Unexpected token \"%s\" found. ';' expected.",
                            token);
                        Errors++;
                        errlog(END, "} /* set */");
                        return (FALSE);
        }

        token = currtok();
        if (eq(token, "}")) {
                (void) accept_token("}");
                errlog(VERBOSE, "End architecture");
                errlog(END, "} /* set */");
                return (FALSE);
        }

        errlog(END, "} /* set */");
        return (TRUE);
}

static int
set_parents(void)
{
        char *token = currtok();
        int uncle;

        errlog(BEGIN, "set_parents() {");
        errlog(VERBOSE, "token = '%s'",
            (token) ? token : "<NULL>");

        if (accept_token("{") == FALSE) {
                errlog(INPUT|ERROR, "set_parents(): Unexpected token: %s\n",
                    token);
                Errors++;
                errlog(END, "} /* set_parents */");
                return (FALSE);
        }

        token = currtok();

        if (in_specials(*token)) {
                errlog(INPUT|ERROR, "set_parents(): Unexpected token: %c "
                    "found. Version token expected", *token);
                Errors++;
                errlog(END, "} /* set_parents */");
                return (FALSE);
        }

        uncle = 0;
        while (token && *token != '}') {
                errlog(VERBOSE, "Begin parent list: %s\n", token);
                if (Selected) {
                        if (uncle)
                                (void) add_uncle(token, LeftMostChild, 0);
                        else
                                (void) add_parent(token, Previous, 0);
                }
                (void) strncpy(Previous, token, MAXLINE);
                add_valid_version(token);
                Previous[MAXLINE-1] = '\0';

                token = nexttok();

                if (*token == ',') {
                        token = nexttok();
                        /* following identifiers are all uncles */
                        uncle = 1;
                        continue;
                }

                if (*token == '}') {
                        if (accept_token("}") == FALSE) {
                                errlog(END, "} /* set_parents */");
                                return (FALSE);
                        }
                        errlog(VERBOSE, "set_parent: End of parent list");
                        errlog(END, "} /* set_parents */");
                        return (TRUE);
                }

                errlog(INPUT|ERROR,
                    "set_parents(): Unexpected token \"%s\" "
                    "found. ',' or '}' were expected", token);
                Errors++;
                errlog(END, "} /* set_parents */");
                return (FALSE);
        }
        errlog(END, "} /* set_parents */");
        return (TRUE);
}


/*
 * parser support routines
 */


/*
 * accept_token -- get a specified token or complain loudly.
 */
static int
accept_token(char *expected)
{
        char *token = currtok();

        assert(expected != NULL, "null token passed to accept_token");
        errlog(OTHER | TRACING, "accept_token, at %s expecting %s",
                (token) ? token : "<NULL>", expected);

        if (token == NULL) {
                /* We're at EOF */
                return (TRUE);
        }
        if (eq(token, expected)) {
                (void) nexttok();
                return (TRUE);
        } else {
                errlog(INPUT | ERROR,
                        "accept_token, found %s while looking for %s",
                        (token) ? token : "<NULL>", expected);
                ++Errors;
                return (FALSE);
        }
}

static void
skip_to(char *target)
{
        char *token = currtok();

        assert(target != NULL, "null target passed to skip_to");
        while (token && !eq(token, target)) {
                errlog(VERBOSE, "skipping over %s",
                        (token) ? token : "<NULL>");
                token = nexttok();
        }
}


/*
 * tokenizer -- below the grammar lives this, like a troll
 *      under a bridge.
 */


/*
 * skipb -- skip over blanks (whitespace, actually), stopping
 *      on first non-blank.
 */
static char *
skipb(char *p)
{

        while (*p && isspace(*p))
                ++p;
        return (p);
}

/*
 * skipover -- skip over non-separators (alnum, . and _, actually),
 *      stopping on first separator.
 */
static char *
skipover(char *p)
{

        while (*p && (isalnum(*p) || (*p == '_' || *p == '.')))
                ++p;
        return (p);
}


/*
 * currtok/nexttok -- get the current/next token
 */
static char *
currtok(void)
{

        if (CurrTok == NULL) {
                (void) nexttok();
        }
        return (CurrTok);
}

static char *
nexttok(void)
{
        static char line[MAXLINE];
        char *p;

        if ((p = tokenize(NULL)) == NULL) {
                /* We're at an end of line. */
                do {
                        if (fgets(line, sizeof (line), Fp) == NULL) {
                                /* Which is also end of file. */
                                CurrTok = NULL;
                                return (NULL);
                        }
                        ++Line;
                        seterrline(Line, Filename, "", line);
                } while ((p = tokenize(line)) == NULL);
        }
        CurrTok = p;
        return (p);
}



/*
 * tokenize -- a version of the standard strtok with specific behavior.
 */
static char *
tokenize(char *line)
{
        static char *p = NULL;
        static char saved = 0;
        char *q;

        if (line == NULL && p == NULL) {
                /* It's the very first time */
                return (NULL);
        } else if (line != NULL) {
                /* Initialize with a new line */
                q = skipb(line);
        } else {
                /* Restore previous line. */
                *p = saved;
                q = skipb(p);
        }
        /* q is at the beginning of a token or at EOL, p is irrelevant. */

        if (*q == '\0') {
                /* It's at EOL. */
                p = q;
        } else if (in_specials(*q)) {
                /* We have a special-character token. */
                p = q + 1;
        } else if (*q == '#') {
                /* The whole rest of the line is a comment token. */
                return (NULL);
        } else {
                /* We have a word token. */
                p = skipover(q);
        }
        saved = *p;
        *p = '\0';

        if (p == q) {
                /* End of line */
                return (NULL);
        } else {
                return (q);
        }
}


/*
 * valid_version -- see if a version string was mentioned in the set file.
 */
int
valid_version(const char *vers_name)
{

        if (Vers == NULL) {
                init_tables();
        }
        return (in_stringtable(Vers, vers_name));
}

/*
 * valid_arch -- see if the arch was mentioned in the set file.
 */
int
valid_arch(const char *arch_name)
{

        if (Vers == NULL) {
                init_tables();
        }
        return (in_stringtable(Varch, arch_name));
}

/*
 * add_valid_version and _arch -- add a name to the table.
 */
static void
add_valid_version(char *vers_name)
{
        errlog(BEGIN, "add_valid_version(\"%s\") {", vers_name);
        if (Vers == NULL) {
                init_tables();
        }
        Vers = add_to_stringtable(Vers, vers_name);
        errlog(END, "}");
}

static void
add_valid_arch(char *arch_name)
{

        errlog(BEGIN, "add_valid_arch(\"%s\") {", arch_name);
        if (Vers == NULL) {
                init_tables();
        }
        Varch = add_to_stringtable(Varch, arch_name);
        errlog(END, "}");
}

/*
 * init_tables -- creat them when first used.
 */
static void
init_tables(void)
{
        Vers = create_stringtable(TABLE_INITIAL);
        Varch = create_stringtable(TABLE_INITIAL);
}