#ifndef lint
static char rcsid[] = "$Id: specfile.c,v 1.9 1994/04/05 23:57:31 deraadt Exp $";
#endif /* not lint */

#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include "y.tab.h"
#include "config.h"
#include "specfile.h"

#define is_paren(x) ((x == '(') || (x == ')'))
struct file_keyword {
    char *key;
    int token;
} file_kw_table[] = {
    "compile-with",            T_COMPILE_WITH,
    "config-dependent",        T_CONFIG_DEPENDENT,
    "device-driver",           T_DEVICE_DRIVER,
    "optional",                T_OPTIONAL,
    "or",                      T_OR,
    "not",                     T_NOT,
    "requires",                T_REQUIRES,
    "standard",                T_STANDARD,
    "needs-count",	       T_NEEDS_COUNT,
    };

extern struct file_list *fltail_lookup(),*new_fent(),*fl_lookup();
static char *current_file;
static int current_line;
static jmp_buf parse_problem;

int file_tok(token)
     char *token;
{
    int i, length;

    if (is_paren(*token))
	return (*token == '(' ?  T_LEFTPAREN : T_RIGHTPAREN);
    for (i =0; i <(sizeof(file_kw_table)/sizeof(file_kw_table[0])); i++) {
	if (!strcmp(file_kw_table[i].key, token))
	    return file_kw_table[i].token;
    }
    length = strlen(token);
    for (i = 0; i < length; i++) {
	if (!isalnum(token[i]) && (token[i] != '_') &&
	    (token[i] != '-')) return -1;
    }
    return T_IDENTIFIER;
}

struct name_expr *
alloc_name_expr(name)
     char *name;
{
    struct name_expr *new;
    int token,type;

    type = 0;
    token = file_tok(name);
    switch (token) {
    case T_OR: 
    case T_NOT: 
    case T_IDENTIFIER: 
    case T_LEFTPAREN:
    case T_RIGHTPAREN:
	type = token;
	break;
    default: return NULL;
    }
    new = malloc(sizeof(struct name_expr));
    new->type = type;
    if (type == T_IDENTIFIER) new->name = ns(name);
    else new->name = NULL;
    new->left = new->right = new->next = NULL;
    return new;
}

void parse_err(char *emessage)
{
    fprintf(stderr, "%s:%d: %s\n", current_file, current_line, emessage);
    if (!fatal_errors) longjmp(parse_problem,1);
    exit(1);
}

delete_expr(expr)
     struct name_expr *expr;
{
    if (expr->type == T_IDENTIFIER)
	free(expr->name);
    free(expr);
}
struct name_expr *yank_node(expr)
     struct name_expr **expr;
{
    struct name_expr *node;

    if (*expr == NULL) return NULL;
    node = *expr;
    *expr = (*expr)->next;
    return node;
}
struct name_expr *yank_expr(expr)
     struct name_expr **expr;
{
    struct name_expr *node,*tail, *subexpr,*next;

    node = yank_node(expr);
    if (node == NULL) return NULL;
    if (node->type == T_LEFTPAREN) 
	for (tail = subexpr = NULL;;) {
	    next = yank_expr(expr);
	    if (!next) parse_err("missing ')'");
	    if (next->type == T_RIGHTPAREN) {
		if (subexpr == NULL)
		    parse_err("empty inner expression i.e '()' ");
		node->left = subexpr;
		node->type = EXPR_GROUP;
		break;
	    } else {
		if (subexpr == NULL)
		    tail = subexpr = next;
		else {
		    tail->next = next;
		    tail = next;
		}
		tail->next = NULL;
	    }
	}
    return(node);
}

struct name_expr *
paren_squish(tree)
     struct name_expr *tree;
{
    struct name_expr *result,*expr,*tail;
    
    result = tail = NULL;

    while ((expr = yank_expr(&tree)) != NULL) {
	if (expr->type == T_RIGHTPAREN)
	    parse_err("unexpected ')'");
	if (result == NULL) {
	    tail = result = expr;
	}
	else {
	    tail->next = expr;
	    tail = expr;
	}
	tail->next = NULL;
    }
    return(result);
}

struct name_expr *
not_squish(tree)
     struct name_expr *tree;
{
    struct name_expr *result,*next,*tail,*node;

    tail = result = next = NULL;
    
    while ((next = yank_node(&tree)) != NULL) {
	if (next->type == EXPR_GROUP)
	    next->left = not_squish(next->left);
	if (next->type == T_NOT) {
	    int notlevel = 1;
	    
	    node = yank_node(&tree);
	    while (node->type == T_NOT) {
		++notlevel;
		node = yank_node(&tree);
	    }
	    if (node == NULL)
		parse_err("no expression following 'not'");
	    if (node->type == T_OR)
		parse_err("nothing between 'not' and 'or'");
	    if (notlevel % 2 != 1)
		next = node;
	    else
		next->left = node;
	}
	
	/* add the node to our result plan */
	if (result == NULL)
	    tail = result = next;
	else {
	    tail->next = next;
	    tail = next;
	}
	tail->next = NULL;
    }
    return(result);
}
struct name_expr *
or_squish(tree)
     struct name_expr *tree;
{
    struct name_expr *next, *tail,*result;
    
    tail = result = next = NULL;
    
    while ((next = yank_node(&tree)) != NULL) {
	
	if (next->type == EXPR_GROUP)
	    next->left = or_squish(next->left);
	
	if (next->type == T_NOT)
	    next->left = or_squish(next->left);
	
	if (next->type == T_OR) {
	    if (result == NULL)
		parse_err("no expression before 'or'");
	    next->left = result;
	    next->right = or_squish(tree);
	    if (next->right == NULL)
		parse_err("no expression after 'or'");
	    next->next = NULL;
	    return(next);
	}
	
	/* add the node to our result plan */
	if (result == NULL)
	    tail = result = next;
	else {
	    tail->next = next;
	    tail = next;
	}
	tail->next = NULL;
    }
    return(result);
}

struct name_expr *
parse_name_expr(fp,seed, read_ahead)
     FILE *fp;
     char *seed, **read_ahead;
{
    struct name_expr *list, *tail,*new,*current;
    char *str;


    list = NULL;
    *read_ahead = NULL;
    if (seed) {
	list = alloc_name_expr(seed);
	if (list == NULL) {
	    *read_ahead = seed;
	    return NULL;
	}
    }
    tail = list;
    for (;;) {
	str = get_word(fp);
	if ((str == (char *)EOF) || str == NULL) {
	    *read_ahead = str;
	    break;
	}
	new = alloc_name_expr(str);
	if (!new) {
	    *read_ahead = str;
	    break;
	}
	if (tail) tail->next = new;
	else {
	    list = new;
	}
	tail = new;
    }
    list = paren_squish(list);
    list = not_squish(list);
    list = or_squish(list);
    return list;
}
int is_simple(expr)
     struct name_expr *expr;
{
    return expr && expr->type == T_IDENTIFIER;
}

int f_not(expr,explain)
     struct name_expr *expr;
     int explain;
{
    int result;

    result = !depend_check(expr->left,explain);
    return result;
}
int f_or(expr,explain)
     struct name_expr *expr;
     int explain;
{
    int result;

    if (depend_check(expr->left,explain))
	return 1;
    if (depend_check(expr->right,explain))
	return 1;
    return 0;
}
int f_identifier(expr,explain)
     struct name_expr *expr;
     int explain;
{
    struct opt *op;
    struct device *dp;
    struct cputype *cp;

    for (op = opt; op != 0; op = op->op_next)
	if (opteq(op->op_name, expr->name)) return 1;
    for (cp = cputype; cp != 0; cp = cp->cpu_next)
	if (opteq(cp->cpu_name, expr->name)) return 1;
    for (dp = dtab; dp != 0; dp = dp->d_next)
	if (eq(dp->d_name, expr->name) &&
	    !(dp->d_type == PSEUDO_DEVICE && dp->d_flags && dp->d_slave == 0)) return 1;
    return 0;
}
print_expr(expr)
     struct name_expr *expr;
{
    struct name_expr *current;
    for (current = expr; current != NULL; current= current->next) {
	switch (current->type) {
	case T_NOT:
	    fprintf (stderr, "not ");
	    print_expr(current->left);
	    break;
	case T_OR:
	    print_expr(current->left);
	    fprintf (stderr, "or ");
	    print_expr(current->right);
	    break;
	case EXPR_GROUP:
	    fprintf (stderr, "(");
	    print_expr(current->left);
	    fprintf (stderr, ")");
	    break;
	case T_IDENTIFIER:
	    fprintf(stderr,"%s ", current->name);
	    break;
	default:
	    parse_err("unknown expression type");
	}
    }
}
int depend_check(expr, explain)
     struct name_expr *expr;
     int explain;
{
    struct name_expr *current;
    int result;

    for (current= expr; current; current= current->next) {
	switch(current->type) {
	case T_NOT:
	    result = f_not(current,0);
	    break;
	case T_OR:
	    result = f_or(current,0);
	    break;
	case EXPR_GROUP:
	    result = depend_check(current->left, 0);
	    break;
	case T_IDENTIFIER:
	    result = f_identifier(current,0);
	    break;
	}
	if (result) continue;
	return 0;
    }
    return 1;
}



read_file(filename, fatal_on_open, override)
     char *filename;
     int fatal_on_open, override;
{
    FILE *fp;
    size_t length;
    char ebuf[1024];
    
    fp = fopen(filename, "r");
    if (!fp)
	if (fatal_on_open) {
	    perror(filename);
	    exit(1);
	}
	else return;
    current_line = 0;
    current_file = filename;
    for (;;) {
	char *str, *kf_name, *read_ahead,*compile_with;
	extern char *get_word(),*get_quoted_word();
	int token,optional,driver,needs_count,config_depend,
            is_dup,filetype,is_option;
	struct name_expr *depends_on,*requires;
	struct file_list *tp,*tmp, *fl,*pf;	
	enum {BEFORE_FILENAME,BEFORE_SPEC,BEFORE_DEPENDS,PAST_DEPENDS,
		  SPECIALS} parse_state;


	if (setjmp(parse_problem)) {
	    while (1) {
		str = get_word(fp);
		if (!str || (str == (char *) EOF)) break;
	    }
	    if (!str) current_line++;
	    continue;
	}
	str = get_word(fp);
	current_line++;
	if (str == NULL) continue;
	if (str == (char *) EOF) break;
	if (*str == '#') {
	    fprintf(stderr, "shouldn't get here");
	    exit(1);
	}
	parse_state = BEFORE_FILENAME;
	kf_name = read_ahead = compile_with = NULL;
	optional= driver = config_depend = filetype = needs_count = 0;
	depends_on = requires = NULL;
	is_dup = 0;
	is_option = 0;
	while ((str != NULL) && (str != (char *)EOF)) {
	    switch (parse_state) {
	    case BEFORE_FILENAME: {
		kf_name = ns(str);
                if (strncmp(kf_name, "OPTIONS/", 8) == 0) {
                    kf_name = ns(index(kf_name, '/') + 1);
                    is_option++;
                }
		parse_state = BEFORE_SPEC;
		break;
	    }
	    case BEFORE_SPEC: {
		token = file_tok(str);
		if ((token != T_OPTIONAL) && (token != T_STANDARD))
		    parse_err("unexpected token starts inclusion specification");
		optional = (token == T_OPTIONAL);
		parse_state = BEFORE_DEPENDS;
		break;
	    }
	    case BEFORE_DEPENDS: {
		depends_on = parse_name_expr(fp,str, &read_ahead);
		str = read_ahead;
		parse_state = PAST_DEPENDS;
		continue;
		break;
	    }
	    case PAST_DEPENDS:
	    case SPECIALS: 
		token = file_tok(str);
		switch (token) {
		case T_COMPILE_WITH:  {
		    str = get_quoted_word(fp);
		    if ((str == 0) || (str == (char *) EOF))
			parse_err("missing compile command string");
		    compile_with = ns(str);
		}
		case T_CONFIG_DEPENDENT: {
		    config_depend = 1;
		    break;
		}
		case T_DEVICE_DRIVER: {
		    driver = 1;
		    needs_count = 0;
		    break;
		}
		case T_REQUIRES: {
		    requires = parse_name_expr(fp,NULL, &read_ahead);
		    if (!requires) 
			parse_err("'requires' but no expression");
		    str = read_ahead;			
		    continue;
		    break;
		}
		case T_NEEDS_COUNT: {
		    if (!driver) needs_count = 1;
		    break;
		}
		default:
		    parse_err("unexpected token");
		}
		break;
	    default:
		parse_err("unknown state");
	    }
	    str = get_word(fp);
	}
	if (parse_state == BEFORE_SPEC)
	    parse_err("filename, but no specification");
	if (is_option) {
            struct device dev;
            register struct opt *op;
            struct opt *lop = 0;
 
            /*
             *  Allocate a pseudo-device entry which we will insert into
             *  the device list below.  The flags field is set non-zero to
             *  indicate an internal entry rather than one generated from
             *  the configuration file.  The slave field is set to define
             *  the corresponding symbol as 0 should we fail to find the
             *  option in the option list.
             */
            init_dev(&dev);
            dev.d_type = PSEUDO_DEVICE;
            dev.d_name = ns(kf_name);
            dev.d_slave = 0;
            dev.d_flags++;
            for (op=opt; op; lop=op, op=op->op_next) {
                char *od = raisestr(ns(kf_name));
 
                /*
                 *  Found an option which matches the current device
                 *  dependency identifier.  Set the slave field to
                 *  define the option in the header file.
                 */
                if (strcmp(op->op_name, od) == 0) {
                    dev.d_slave = 1;
                    if (lop == 0)
                        opt = op->op_next;
                    else
                        lop->op_next = op->op_next;
                    free(op);
                    op = 0;
                }
                free(od);
                if (op == 0)
                        break;
            }
            newdev(&dev);
            needs_count = 0;
	    driver = 1;
            filetype = INVISIBLE;
	}
	else {
            if (!kf_name)
                parse_err("no filename specified");
            fl = fl_lookup(kf_name);
            if (fl && !override) {
                (void) sprintf(ebuf, "duplicate file name '%s'", kf_name);
                parse_err(ebuf);
            }
            if ((pf = fl_lookup(kf_name)) &&
                (pf->f_type != INVISIBLE || (pf->f_flags | DUPLICATE)))
                is_dup = 1;
            else
                is_dup = 0;
            if (override && ((tmp = fltail_lookup(kf_name)) != 0)) {
                    fprintf(stderr, "%s:%d: Local file %s overrides %s.\n",
                            current_file, current_line, kf_name, tmp->f_fn);
		    tmp->f_type = INVISIBLE;
	    }
            if (!optional) {
                if (driver)
                    parse_err("'standard' incompatible with 'device-driver'");
                if (depends_on && !needs_count)
                    parse_err("'standard' can't have dependencies");
	    }
            else if (!depends_on) 
                parse_err("'optional' requires dependency specification");
            if (is_simple(depends_on) &&
                eq("profiling-routine", depends_on->name)) filetype = PROFILING;
            else if (!optional || depend_check(depends_on,0)) filetype = NORMAL;
            else filetype = INVISIBLE;

            if (filetype == NORMAL && requires && !depend_check(requires,0)) {
                fprintf(stderr, "%s:%d: requirement expression failed: ",
                        current_file, current_line);
                print_expr(requires);
                fprintf(stderr, "\n");
                parse_err("requirements not met");
	    }
	}
	tp = new_fent();
	tp->f_fn = kf_name;
	tp->f_type = filetype;
	if (driver)
	    tp->f_needs = depends_on;
	else
	    tp->f_needs = NULL;
	if (needs_count)
	    tp->f_countname = depends_on;
	else
	    tp->f_countname = NULL;
	tp->f_was_driver = driver;
	tp->f_needs_count = needs_count;
	tp->f_special = compile_with;
	tp->f_flags = 0;
	tp->f_flags |= (config_depend ? CONFIGDEP : 0);
	tp->f_flags |= (is_dup ? DUPLICATE : 0);
    }
    return;

}
