/*
 * Copyright  Robert J. Amstadt, 1993
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define VARTYPE_BYTE	0
#define VARTYPE_SIGNEDWORD	0
#define VARTYPE_WORD	1
#define VARTYPE_LONG	2
#define VARTYPE_FARPTR	3

#define FUNCTYPE_PASCAL	16
#define FUNCTYPE_C	17
#define FUNCTYPE_REG	19

#define EQUATETYPE_ABS	18

#define MAX_ORDINALS	1024

typedef struct ordinal_definition_s
{
    int valid;
    int type;
    char export_name[80];
    void *additional_data;
} ORDDEF;

typedef struct ordinal_variable_definition_s
{
    int n_values;
    int *values;
} ORDVARDEF;

typedef struct ordinal_function_definition_s
{
    int n_args_16;
    int arg_types_16[16];
    int arg_16_offsets[16];
    int arg_16_size;
    char internal_name[80];
    int n_args_32;
    int arg_indices_32[16];
} ORDFUNCDEF;

ORDDEF OrdinalDefinitions[MAX_ORDINALS];

char LowerDLLName[80];
char UpperDLLName[80];
int Limit;
int DLLId;
FILE *SpecFp;

char *ParseBuffer = NULL;
char *ParseNext;
char ParseSaveChar;
int Line;

int IsNumberString(char *s)
{
    while (*s != '\0')
	if (!isdigit(*s++))
	    return 0;

    return 1;
}

char *strlower(char *s)
{
    char *p;
    
    for(p = s; *p != '\0'; p++)
	*p = tolower(*p);

    return s;
}

char *strupper(char *s)
{
    char *p;
    
    for(p = s; *p != '\0'; p++)
	*p = toupper(*p);

    return s;
}

int stricmp(char *s1, char *s2)
{
    if (strlen(s1) != strlen(s2))
	return -1;
    
    while (*s1 != '\0')
	if (*s1++ != *s2++)
	    return -1;
    
    return 0;
}

char *
GetTokenInLine(void)
{
    char *p;
    char *token;

    if (ParseNext != ParseBuffer)
    {
	if (ParseSaveChar == '\0')
	    return NULL;
	*ParseNext = ParseSaveChar;
    }
    
    /*
     * Remove initial white space.
     */
    for (p = ParseNext; isspace(*p); p++)
	;
    
    if (*p == '\0')
	return NULL;
    
    /*
     * Find end of token.
     */
    token = p++;
    if (*token != '(' && *token != ')')
	while (*p != '\0' && *p != '(' && *p != ')' && !isspace(*p))
	    p++;
    
    ParseSaveChar = *p;
    ParseNext = p;
    *p = '\0';

    return token;
}

char *
GetToken(void)
{
    char *token;

    if (ParseBuffer == NULL)
    {
	ParseBuffer = malloc(512);
	ParseNext = ParseBuffer;
	Line++;
	if (fgets(ParseBuffer, 511, SpecFp) == NULL)
	    return NULL;
    }

    while ((token = GetTokenInLine()) == NULL)
    {
	ParseNext = ParseBuffer;
	Line++;
	if (fgets(ParseBuffer, 511, SpecFp) == NULL)
	    return NULL;
    }

    return token;
}

int
ParseVariable(int ordinal, int type)
{
    ORDDEF *odp;
    ORDVARDEF *vdp;
    char export_name[80];
    char *token;
    char *endptr;
    int *value_array;
    int n_values;
    int value_array_size;
    
    strcpy(export_name, GetToken());

    token = GetToken();
    if (*token != '(')
    {
	fprintf(stderr, "%d: Expected '(' got '%s'\n", Line, token);
	exit(1);
    }

    n_values = 0;
    value_array_size = 25;
    value_array = malloc(sizeof(*value_array) * value_array_size);
    
    while ((token = GetToken()) != NULL)
    {
	if (*token == ')')
	    break;

	value_array[n_values++] = strtol(token, &endptr, 0);
	if (n_values == value_array_size)
	{
	    value_array_size += 25;
	    value_array = realloc(value_array, 
				  sizeof(*value_array) * value_array_size);
	}
	
	if (endptr == NULL || *endptr != '\0')
	{
	    fprintf(stderr, "%d: Expected number value, got '%s'\n", Line,
		    token);
	    exit(1);
	}
    }
    
    if (token == NULL)
    {
	fprintf(stderr, "%d: End of file in variable declaration\n", Line);
	exit(1);
    }

    if (ordinal >= MAX_ORDINALS)
    {
	fprintf(stderr, "%d: Ordinal number too large\n", Line);
	exit(1);
    }
    
    odp = &OrdinalDefinitions[ordinal];
    odp->valid = 1;
    odp->type = type;
    strcpy(odp->export_name, export_name);
    
    vdp = malloc(sizeof(*vdp));
    odp->additional_data = vdp;
    
    vdp->n_values = n_values;
    vdp->values = realloc(value_array, sizeof(*value_array) * n_values);

    return 0;
}

int
ParseExportFunction(int ordinal, int type)
{
    char *token;
    ORDDEF *odp;
    ORDFUNCDEF *fdp;
    int arg_types[16];
    int i;
    int arg_num;
    int current_offset;
    int arg_size;
	
    
    if (ordinal >= MAX_ORDINALS)
    {
	fprintf(stderr, "%d: Ordinal number too large\n", Line);
	exit(1);
    }
    
    odp = &OrdinalDefinitions[ordinal];
    strcpy(odp->export_name, GetToken());
    odp->valid = 1;
    odp->type = type;
    fdp = malloc(sizeof(*fdp));
    odp->additional_data = fdp;

    token = GetToken();
    if (*token != '(')
    {
	fprintf(stderr, "%d: Expected '(' got '%s'\n", Line, token);
	exit(1);
    }

    fdp->arg_16_size = 0;
    for (i = 0; i < 16; i++)
    {
	token = GetToken();
	if (*token == ')')
	    break;

	if (stricmp(token, "byte") == 0 || stricmp(token, "word") == 0)
	{
	    fdp->arg_types_16[i] = VARTYPE_WORD;
	    fdp->arg_16_size += 2;
	    fdp->arg_16_offsets[i] = 2;
	}
	else if (stricmp(token, "s_byte") == 0 || 
		 stricmp(token, "s_word") == 0)
	{
	    fdp->arg_types_16[i] = VARTYPE_SIGNEDWORD;
	    fdp->arg_16_size += 2;
	    fdp->arg_16_offsets[i] = 2;
	}
	else if (stricmp(token, "long") == 0 || stricmp(token, "s_long") == 0)
	{
	    fdp->arg_types_16[i] = VARTYPE_LONG;
	    fdp->arg_16_size += 4;
	    fdp->arg_16_offsets[i] = 4;
	}
	else if (stricmp(token, "ptr") == 0)
	{
	    fdp->arg_types_16[i] = VARTYPE_FARPTR;
	    fdp->arg_16_size += 4;
	    fdp->arg_16_offsets[i] = 4;
	}
	else
	{
	    fprintf(stderr, "%d: Unknown variable type '%s'\n", Line, token);
	    exit(1);
	}
    }
    fdp->n_args_16 = i;

    if (type == FUNCTYPE_PASCAL || type == FUNCTYPE_REG)
    {
	current_offset = 0;
	for (i--; i >= 0; i--)
	{
	    arg_size = fdp->arg_16_offsets[i];
	    fdp->arg_16_offsets[i] = current_offset;
	    current_offset += arg_size;
	}
    }
    else
    {
	current_offset = 0;
	for (i = 0; i < fdp->n_args_16; i++)
	{
	    arg_size = fdp->arg_16_offsets[i];
	    fdp->arg_16_offsets[i] = current_offset;
	    current_offset += arg_size;
	}
    }

    strcpy(fdp->internal_name, GetToken());
    token = GetToken();
    if (*token != '(')
    {
	fprintf(stderr, "%d: Expected '(' got '%s'\n", Line, token);
	exit(1);
    }
    for (i = 0; i < 16; i++)
    {
	token = GetToken();
	if (*token == ')')
	    break;

	fdp->arg_indices_32[i] = atoi(token);
	if (fdp->arg_indices_32[i] < 1 || 
	    fdp->arg_indices_32[i] > fdp->n_args_16)
	{
	    fprintf(stderr, "%d: Bad argument index %d\n", Line,
		    fdp->arg_indices_32[i]);
	    exit(1);
	}
    }
    fdp->n_args_32 = i;

    return 0;
}

int
ParseEquate(int ordinal)
{
    ORDDEF *odp;
    char *token;
    char *endptr;
    int value;
    
    if (ordinal >= MAX_ORDINALS)
    {
	fprintf(stderr, "%d: Ordinal number too large\n", Line);
	exit(1);
    }
    
    odp = &OrdinalDefinitions[ordinal];
    strcpy(odp->export_name, GetToken());

    token = GetToken();
    value = strtol(token, &endptr, 0);
    if (endptr == NULL || *endptr != '\0')
    {
	fprintf(stderr, "%d: Expected number value, got '%s'\n", Line,
		token);
	exit(1);
    }

    odp->valid = 1;
    odp->type = EQUATETYPE_ABS;
    odp->additional_data = (void *) value;

    return 0;
}

int
ParseOrdinal(int ordinal)
{
    char *token;
    
    token = GetToken();
    if (token == NULL)
    {
	fprintf(stderr, "%d: Expected type after ordinal\n", Line);
	exit(1);
    }

    if (stricmp(token, "byte") == 0)
	return ParseVariable(ordinal, VARTYPE_BYTE);
    else if (stricmp(token, "word") == 0)
	return ParseVariable(ordinal, VARTYPE_WORD);
    else if (stricmp(token, "long") == 0)
	return ParseVariable(ordinal, VARTYPE_LONG);
    else if (stricmp(token, "c") == 0)
	return ParseExportFunction(ordinal, FUNCTYPE_C);
    else if (stricmp(token, "p") == 0)
	return ParseExportFunction(ordinal, FUNCTYPE_PASCAL);
    else if (stricmp(token, "pascal") == 0)
	return ParseExportFunction(ordinal, FUNCTYPE_PASCAL);
    else if (stricmp(token, "register") == 0)
	return ParseExportFunction(ordinal, FUNCTYPE_REG);
    else if (stricmp(token, "equate") == 0)
	return ParseEquate(ordinal);
    else
    {
	fprintf(stderr, 
		"%d: Expected type after ordinal, found '%s' instead\n",
		Line, token);
	exit(1);
    }
}

int
ParseTopLevel(void)
{
    char *token;
    
    while ((token = GetToken()) != NULL)
    {
	if (stricmp(token, "name") == 0)
	{
	    strcpy(LowerDLLName, GetToken());
	    strlower(LowerDLLName);

	    strcpy(UpperDLLName, LowerDLLName);
	    strupper(UpperDLLName);
	}
	else if (stricmp(token, "id") == 0)
	{
	    token = GetToken();
	    if (!IsNumberString(token))
	    {
		fprintf(stderr, "%d: Expected number after id\n", Line);
		exit(1);
	    }
	    
	    DLLId = atoi(token);
	}
	else if (stricmp(token, "length") == 0)
	{
	    token = GetToken();
	    if (!IsNumberString(token))
	    {
		fprintf(stderr, "%d: Expected number after length\n", Line);
		exit(1);
	    }

	    Limit = atoi(token);
	}
	else if (IsNumberString(token))
	{
	    int ordinal;
	    int rv;
	    
	    ordinal = atoi(token);
	    if ((rv = ParseOrdinal(ordinal)) < 0)
		return rv;
	}
	else
	{
	    fprintf(stderr, 
		    "%d: Expected name, id, length or ordinal\n", Line);
	    exit(1);
	}
    }

    return 0;
}

void
OutputVariableCode(FILE *fp, char *storage, ORDDEF *odp)
{
    ORDVARDEF *vdp;
    int i;

    fprintf(fp, "_%s_Ordinal_%d:\n", UpperDLLName, i);

    vdp = odp->additional_data;
    for (i = 0; i < vdp->n_values; i++)
    {
	if ((i & 7) == 0)
	    fprintf(fp, "\t%s\t", storage);
	    
	fprintf(fp, "%d", vdp->values[i]);
	
	if ((i & 7) == 7 || i == vdp->n_values - 1)
	    fprintf(fp, "\n");
	else
	    fprintf(fp, ", ");
    }
    fprintf(fp, "\n");
}

main(int argc, char **argv)
{
    ORDDEF *odp;
    ORDFUNCDEF *fdp;
    FILE *fp;
    char filename[80];
    char buffer[80];
    char *p;
    int i;
    
    if (argc < 2)
    {
	fprintf(stderr, "usage: build SPECNAME\n");
	exit(1);
    }

    SpecFp = fopen(argv[1], "r");
    if (SpecFp == NULL)
    {
	fprintf(stderr, "Could not open specification file, '%s'\n", argv[1]);
	exit(1);
    }

    ParseTopLevel();

    sprintf(filename, "dll_%s.S", LowerDLLName);
    fp = fopen(filename, "w");

    fprintf(fp, "\t.globl _%s_Dispatch\n", UpperDLLName);
    fprintf(fp, "_%s_Dispatch:\n", UpperDLLName);
    fprintf(fp, "\torl\t$0x%08x,%%eax\n", DLLId << 16);
    fprintf(fp, "\tjmp\t_CallTo32\n\n");

    odp = OrdinalDefinitions;
    for (i = 0; i <= Limit; i++, odp++)
    {
	fprintf(fp, "\t.globl _%s_Ordinal_%d\n", UpperDLLName, i);

	if (!odp->valid)
	{
	    fprintf(fp, "_%s_Ordinal_%d:\n", UpperDLLName, i);
	    fprintf(fp, "\tmovl\t$%d,%%eax\n", i);
	    fprintf(fp, "\tpushw\t$0\n");
	    fprintf(fp, "\tjmp\t_%s_Dispatch\n\n", UpperDLLName);
	}
	else
	{
	    fdp = odp->additional_data;
	    
	    switch (odp->type)
	    {
	      case EQUATETYPE_ABS:
		fprintf(fp, "_%s_Ordinal_%d = %d\n\n", 
			UpperDLLName, i, (int) odp->additional_data);
		break;

	      case VARTYPE_BYTE:
		OutputVariableCode(fp, ".byte", odp);
		break;

	      case VARTYPE_WORD:
		OutputVariableCode(fp, ".word", odp);
		break;

	      case VARTYPE_LONG:
		OutputVariableCode(fp, ".long", odp);
		break;

	      case FUNCTYPE_REG:
		fprintf(fp, "_%s_Ordinal_%d:\n", UpperDLLName, i);
		fprintf(fp, "\tpushw\t%%ax\n");
		fprintf(fp, "\tpushw\t%%cx\n");
		fprintf(fp, "\tpushw\t%%dx\n");
		fprintf(fp, "\tpushw\t%%bx\n");
		fprintf(fp, "\tpushw\t%%sp\n");
		fprintf(fp, "\tpushw\t%%bp\n");
		fprintf(fp, "\tpushw\t%%si\n");
		fprintf(fp, "\tpushw\t%%di\n");
		fprintf(fp, "\tpushw\t%%ds\n");
		fprintf(fp, "\tpushw\t%%es\n");
		fprintf(fp, "\tmovl\t%%ebp,%%eax\n");
		fprintf(fp, "\tmovw\t%%esp,%%ebp\n");
		fprintf(fp, "\tpushl\t20(%%ebp)\n");
		fprintf(fp, "\tmovl\t%%eax,%%ebp\n");
		fprintf(fp, "\tmovl\t$%d,%%eax\n", i);
		fprintf(fp, "\tpushw\t$24\n");
		fprintf(fp, "\tjmp\t_%s_Dispatch\n\n", UpperDLLName);
		break;

	      case FUNCTYPE_PASCAL:
		fprintf(fp, "_%s_Ordinal_%d:\n", UpperDLLName, i);
		fprintf(fp, "\tmovl\t$%d,%%eax\n", i);
		fprintf(fp, "\tpushw\t$%d\n", fdp->arg_16_size);
		fprintf(fp, "\tjmp\t_%s_Dispatch\n\n", UpperDLLName);
		break;
		
	      case FUNCTYPE_C:
	      default:
		fprintf(fp, "_%s_Ordinal_%d:\n", UpperDLLName, i);
		fprintf(fp, "\tmovl\t$%d,%%eax\n", i);
		fprintf(fp, "\tpushw\t$0\n");
		fprintf(fp, "\tjmp\t_%s_Dispatch\n\n", UpperDLLName);
		break;
	    }
	}
    }

    fclose(fp);

    sprintf(filename, "dll_%s_tab.c", LowerDLLName);
    fp = fopen(filename, "w");

    fprintf(fp, "#include <stdio.h>\n");
    fprintf(fp, "#include <stdlib.h>\n");
    fprintf(fp, "#include \042dlls.h\042\n\n");

    for (i = 0; i <= Limit; i++)
    {
	fprintf(fp, "extern void %s_Ordinal_%d();\n", UpperDLLName, i);
    }
    
    odp = OrdinalDefinitions;
    for (i = 0; i <= Limit; i++, odp++)
    {
	if (odp->valid && 
	    (odp->type == FUNCTYPE_PASCAL || odp->type == FUNCTYPE_C ||
	     odp->type == FUNCTYPE_REG))
	{
	    fdp = odp->additional_data;
	    fprintf(fp, "extern int %s();\n", fdp->internal_name);
	}
    }
    
    fprintf(fp, "\nstruct dll_table_entry_s %s_table[%d] =\n", 
	    UpperDLLName, Limit + 1);
    fprintf(fp, "{\n");
    odp = OrdinalDefinitions;
    for (i = 0; i <= Limit; i++, odp++)
    {
	fdp = odp->additional_data;

	if (!odp->valid)
	    odp->type = -1;
	
	switch (odp->type)
	{
	  case FUNCTYPE_PASCAL:
	  case FUNCTYPE_REG:
	    fprintf(fp, "    { 0x23, %s_Ordinal_%d, ", UpperDLLName, i);
	    fprintf(fp, "\042%s\042, ", odp->export_name);
	    fprintf(fp, "%s, DLL_HANDLERTYPE_PASCAL, ", fdp->internal_name);
	    fprintf(fp, "%d, ", fdp->n_args_32);
	    if (fdp->n_args_32 > 0)
	    {
		int argnum;
		
		fprintf(fp, "\n      {\n");
		for (argnum = 0; argnum < fdp->n_args_32; argnum++)
		{
		    fprintf(fp, "        { %d, %d },\n",
			    fdp->arg_16_offsets[fdp->arg_indices_32[argnum]-1],
			    fdp->arg_types_16[argnum]);
		}
		fprintf(fp, "      }\n    ");
	    }
	    fprintf(fp, "}, \n");
	    break;
		
	  case FUNCTYPE_C:
	    fprintf(fp, "    { 0x23, %s_Ordinal_%d, ", UpperDLLName, i);
	    fprintf(fp, "\042%s\042, ", odp->export_name);
	    fprintf(fp, "%s, DLL_HANDLERTYPE_C, ", fdp->internal_name);
	    fprintf(fp, "%d, ", fdp->n_args_32);
	    if (fdp->n_args_32 > 0)
	    {
		int argnum;
		
		fprintf(fp, "\n      {\n");
		for (argnum = 0; argnum < fdp->n_args_32; argnum++)
		{
		    fprintf(fp, "        { %d, %d },\n",
			    fdp->arg_16_offsets[fdp->arg_indices_32[argnum]-1],
			    fdp->arg_types_16[argnum]);
		}
		fprintf(fp, "      }\n    ");
	    }
	    fprintf(fp, "}, \n");
	    break;
	    
	  default:
	    fprintf(fp, "    { 0x23, %s_Ordinal_%d, \042\042, NULL },\n", 
		    UpperDLLName, i);
	    break;
	}
    }
    fprintf(fp, "};\n");

    fclose(fp);
}