%{
#define EDIT_SOURCE
#define NO_OPCODES
#include "std.h"
#include "make_func.h"
#include "lex.h"
#include "preprocess.h"
#include "edit_source.h"

#ifdef WIN32
#define MSDOS
#include <process.h>
#endif

    void mf_fatal(char *);
    
    int num_buff = 0;
    int op_code, efun_code, efun1_code;
    char *oper_codes[MAX_FUNC];
    char *efun_codes[MAX_FUNC], *efun1_codes[MAX_FUNC];
    char *efun_names[MAX_FUNC], *efun1_names[MAX_FUNC];
    char *key[MAX_FUNC], *buf[MAX_FUNC];

    int min_arg = -1, limit_max = 0;
    
/*
 * arg_types is the types of all arguments. A 0 is used as a delimiter,
 * marking next argument. An argument can have several types.
 */
int arg_types[400], last_current_type;

/*
 * Store the types of the current efun. They will be copied into the
 * arg_types list if they were not already there (to save memory).
 */
int curr_arg_types[40], curr_arg_type_size;

struct type {
    char *name;
    int num;
} types[] = {
{ "void", VOID },
{ "int", INT },
{ "string", STRING },
{ "object", OBJECT },
{ "mapping", MAPPING },
{ "mixed", MIXED },
{ "unknown", UNKNOWN },
{ "float", FLOAT},
{ "function", FUNCTION},
{ "buffer", BUFFER}
};

#define NELEMS(arr) 	(sizeof arr / sizeof arr[0])
%}

%union {
    int number;
    char *string;
}

%token ID NUM DEFAULT OPERATOR

%type <number> type arg_list basic typel arg_type typel2 NUM

%type <string> ID optional_ID optional_default

%%

specs: /* empty */ | specs spec ;

spec: operator | func;
    
operator: OPERATOR op_list ';' ;
    
op_list: op | op_list ',' op ;

op: ID
    {
	char f_name[500],c;
	int i = 2;
	sprintf(f_name, "F_%s", $1);
	while ((c = f_name[i])) {
	    if (islower(c)) f_name[i++] = toupper(c);
	    else i++;
	}
	oper_codes[op_code] = (char *) malloc(i+1);
	strcpy(oper_codes[op_code], f_name);
        free($1);

	op_code++;
    } ;

optional_ID: ID | /* empty */ { $$ = ""; } ;

optional_default: /* empty */ { $$="DEFAULT_NONE"; } 
                | DEFAULT ':' NUM
                  {
		      static char buf[40];
                      sprintf(buf, "%i", $3);
                      $$ = buf;
		  }
                | DEFAULT ':' ID 
                  { 
                      if (strcmp($3, "F__THIS_OBJECT"))
                          yyerror("Illegal default");
                      $$ = "DEFAULT_THIS_OBJECT";
                  } ;

func: type ID optional_ID '(' arg_list optional_default ')' ';'
    {
	char buff[500];
	char f_name[500];
	int i, len;
	if (min_arg == -1)
	    min_arg = $5;
        if (min_arg > 4) mf_fatal("min_arg > 4\n");
	if ($3[0] == '\0') {
	    if (strlen($2) + 1 + 2 > sizeof f_name)
		mf_fatal("A local buffer was too small!(1)\n");
	    sprintf(f_name, "F_%s", $2);
	    len = strlen(f_name);
	    for (i=0; i < len; i++) {
		if (islower(f_name[i]))
		    f_name[i] = toupper(f_name[i]);
	    }
            if (min_arg == 1 && !limit_max && $5 == 1) {
	        efun1_codes[efun1_code] = (char *) malloc(len + 1);
	        strcpy(efun1_codes[efun1_code], f_name);
		efun1_names[efun1_code] = (char *) malloc(len - 1);
		strcpy(efun1_names[efun1_code], $2);
	        efun1_code++;
	    } else {
	        efun_codes[efun_code] = (char *) malloc(len + 1);
	        strcpy(efun_codes[efun_code], f_name);
		efun_names[efun_code] = (char *) malloc(len - 1);
		strcpy(efun_names[efun_code], $2);
	        efun_code++;
	    }
	} else {
	    if (strlen($3) + 1 + 17 > sizeof f_name)
		mf_fatal("A local buffer was too small(2)!\n");
	    sprintf(f_name, "F_%s | F_ALIAS_FLAG", $3);
	    len = strlen(f_name);
	    for (i=0; i < len; i++) {
		if (islower(f_name[i]))
		    f_name[i] = toupper(f_name[i]);
	    }
	    free($3);
	}
	for(i=0; i < last_current_type; i++) {
	    int j;
	    for (j = 0; j+i<last_current_type && j < curr_arg_type_size; j++)
	    {
		if (curr_arg_types[j] != arg_types[i+j])
		    break;
	    }
	    if (j == curr_arg_type_size)
		break;
	}
	if (i == last_current_type) {
	    int j;
	    for (j=0; j < curr_arg_type_size; j++) {
		arg_types[last_current_type++] = curr_arg_types[j];
		if (last_current_type == NELEMS(arg_types))
		    yyerror("Array 'arg_types' is too small");
	    }
	}
        if (!strcmp($2, "call_other") && !lookup_define("CAST_CALL_OTHERS")) {
	    $1 = MIXED;
	}
     	sprintf(buff, "{\"%s\",%s,0,0,%d,%d,%s,%s,%s,%s,%s,%d,%s},\n",
		$2, f_name, min_arg, limit_max ? -1 : $5, 
		$1 != VOID ? ctype($1) : "TYPE_NOVALUE",
		etype(0), etype(1), etype(2), etype(3), i, $6);
	if (strlen(buff) > sizeof buff)
	    mf_fatal("Local buffer overwritten !\n");

        key[num_buff] = $2;
	buf[num_buff] = (char *) malloc(strlen(buff) + 1);
        strcpy(buf[num_buff], buff);
        num_buff++;
	min_arg = -1;
	limit_max = 0;
	curr_arg_type_size = 0;
    } ;

type: basic | basic '*' { $$ = $1 | 0x10000; };

basic: ID
    {
	int i = NELEMS(types);
	$$ = 0;
	while (i--){
	    if (strcmp($1, types[i].name) == 0) {
		$$ = types[i].num;
		break;
	    }
	}
	if (!$$) {
		char buf[256];
		sprintf(buf, "Invalid type: %s", $1);
		yyerror(buf);
	}
        free($1);
    };

arg_list: /* empty */		{ $$ = 0; }
	| typel2			{ $$ = 1; if ($1) min_arg = 0; }
	| arg_list ',' typel2 	{ $$ = $1 + 1; if ($3) min_arg = $$ - 1; } ;

typel2: typel
    {
	$$ = $1;
	curr_arg_types[curr_arg_type_size++] = 0;
	if (curr_arg_type_size == NELEMS(curr_arg_types))
	    yyerror("Too many arguments");
    } ;

arg_type: type
    {
	if ($1 != VOID) {
	    curr_arg_types[curr_arg_type_size++] = $1;
	    if (curr_arg_type_size == NELEMS(curr_arg_types))
		yyerror("Too many arguments");
	}
	$$ = $1;
    } ;

typel: arg_type			{ $$ = ($1 == VOID && min_arg == -1); }
     | typel '|' arg_type 	{ $$ = (min_arg == -1 && ($1 || $3 == VOID));}
     | '.' '.' '.'		{ $$ = min_arg == -1 ; limit_max = 1; } ;

%%

char *ctype P1(int, n)
{
    static char buff[100];	/* 100 is such a comfortable size :-) */
    char *p = (char *)NULL;

    if (n & 0x10000)
	strcpy(buff, "TYPE_MOD_ARRAY|");
    else
	buff[0] = '\0';
    n &= ~0x10000;
    switch(n) {
	case FLOAT: p = "TYPE_REAL"; break;
	case FUNCTION: p = "TYPE_FUNCTION"; break;
	case VOID: p = "TYPE_VOID"; break;
	case STRING: p = "TYPE_STRING"; break;
	case INT: p = "TYPE_NUMBER"; break;
	case OBJECT: p = "TYPE_OBJECT"; break;
	case MAPPING: p = "TYPE_MAPPING"; break;
	case BUFFER: p = "TYPE_BUFFER"; break;
	case MIXED: p = "TYPE_ANY"; break;
	case UNKNOWN: p = "TYPE_UNKNOWN"; break;
	default: yyerror("Bad type!");
    }
    strcat(buff, p);
    if (strlen(buff) + 1 > sizeof buff)
	mf_fatal("Local buffer overwritten in ctype()");
    return buff;
}

char *etype1 P1(int, n)
{
    if (n & 0x10000)
	return "T_ARRAY";
    switch(n) {
    case FLOAT:
    return "T_REAL";
    case FUNCTION:
    return "T_FUNCTION";
    case INT:
	return "T_NUMBER";
    case OBJECT:
	return "T_OBJECT";
    case MAPPING:
	return "T_MAPPING";
    case STRING:
	return "T_STRING";
    case BUFFER:
	return "T_BUFFER";
    case MIXED:
	return "T_ANY";	/* 0 means any type */
    default:
	yyerror("Illegal type for argument");
    }
    return "What?";
}

char *etype P1(int, n)
{
    int i;
    int local_size = 100;
    char *buff = (char *)malloc(local_size);

    for (i=0; i < curr_arg_type_size; i++) {
	if (n == 0)
	    break;
	if (curr_arg_types[i] == 0)
	    n--;
    }
    if (i == curr_arg_type_size)
	return "T_ANY";
    buff[0] = '\0';
    for(; curr_arg_types[i] != 0; i++) {
	char *p;
	if (curr_arg_types[i] == VOID)
	    continue;
	if (buff[0] != '\0')
	    strcat(buff, "|");
	p = etype1(curr_arg_types[i]);
	/*
	 * The number 2 below is to include the zero-byte and the next
	 * '|' (which may not come).
	 */
	if (strlen(p) + strlen(buff) + 2 > local_size) {
	    fprintf(stderr, "Buffer overflow!\n");
	    exit(1);
	}
	strcat(buff, etype1(curr_arg_types[i]));
    }
    if (!strcmp(buff, "")) 
      strcpy(buff, "T_ANY");
    return buff;
}

int ident PROT((int));

int yylex() {
    register int c;
    
    for(;;) {
	switch(c = getc(yyin)){
	case ' ':
	case '\t':
	    continue;
	case '\n':
	    current_line++;
	    continue;
	case '!':
	{
	    char buff[2048];
	    fgets(buff, 2047, yyin);
	    fprintf(stderr, "Configuration problem: %s\n", buff);
	    exit(-1);
	}
	case '#':
	{
	    int line;

	    char aBuf[2048], fname[2048];
	    fgets(aBuf, 2047, yyin);
	    if (sscanf(aBuf, "%d \"%[^\"]\"", &line, fname)) {
		current_line = line;
		if (current_file) free(current_file);
		current_file = (char*)malloc(strlen(fname) + 1);
		strcpy(current_file, fname);
	    } else
	    if (sscanf(aBuf, "%d", &line)) current_line = line;
            current_line++;
	    continue;
	}
	case EOF:
	    fclose(yyin);
	    return -1;
	default:
	    if ((c >= '0' && c <= '9') || c == '-') {
		int v;
		int neg = 0;
		v = 0;
		if (c == '-') {
		    neg = 1;
		    c = '0';
		}
		do {
		    v = v * 10 + (c - '0');
		    c = getc(yyin);
		} while (c >= '0' && c <= '9');
		ungetc(c, yyin);
		yylval.number = (neg ? -v : v);
		return NUM;
	    }
	    if (isalunum(c))
		return ident(c);
	    return c;
	}
    }
}

int ident P1(int, c)
{
    char buff[100];
    int len;

    for (len = 0; isalunum(c); c = getc(yyin)) {
	buff[len++] = c;
	if (len == sizeof buff - 1) {
	    yyerror("Too long indentifier");
	    break;
	}
    }
    (void)ungetc(c, yyin);
    buff[len] = '\0';
    if (!strcmp(buff, "default"))
	return DEFAULT;
    if (!strcmp(buff, "operator"))
	return OPERATOR;

    yylval.string = (char *)malloc(strlen(buff)+1);
    strcpy(yylval.string, buff);
    return ID;
}

