/*
*   $Id: verilog.c,v 1.3 2003/07/17 04:31:49 darren Exp $
* 
*   Copyright (c) 2003, Darren Hiebert
* 
*   This source code is released for free distribution under the terms of the
*   GNU General Public License.
* 
*   This module contains functions for generating tags for the Verilog HDL
*   (Hardware Description Language).
* 
*   Language definition documents:
*	http://www.eg.bucknell.edu/~cs320/1995-fall/verilog-manual.html
*	http://www.sutherland-hdl.com/on-line_ref_guide/vlog_ref_top.html
*	http://home.europa.com/~celiac/VerilogBNF.html
*	http://eesun.free.fr/DOC/VERILOG/verilog_manual1.html
*/

/*
 *   INCLUDE FILES
 */
#include "general.h"	/* must always come first */

#include <string.h>
#include <setjmp.h>

#include "debug.h"
#include "keyword.h"
#include "parse.h"
#include "read.h"
#include "vstring.h"

/*
 *   DATA DECLARATIONS
 */
typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;

typedef enum {
    K_UNDEFINED = -1,
    K_CONSTANT,
    K_EVENT,
    K_FUNCTION,
    K_MODULE,
    K_NET,
    K_PORT,
    K_REGISTER,
    K_TASK
} verilogKind;

typedef struct {
    const char *keyword;
    verilogKind kind;
} keywordAssoc;

/*
 *   DATA DEFINITIONS
 */
static int Ungetc;
static int Lang_verilog;
static jmp_buf Exception;

static kindOption VerilogKinds [] = {
 { TRUE, 'c', "constant",  "constants (define, parameter, specparam)" },
 { TRUE, 'e', "event",     "events" },
 { TRUE, 'f', "function",  "functions" },
 { TRUE, 'm', "module",    "modules" },
 { TRUE, 'n', "net",       "net data types" },
 { TRUE, 'p', "port",      "ports" },
 { TRUE, 'r', "register",  "register data types" },
 { TRUE, 't', "task",      "tasks" }
};

static keywordAssoc VerilogKeywordTable [] = {
    { "`define",   K_CONSTANT },
    { "event",     K_EVENT },
    { "function",  K_FUNCTION },
    { "inout",     K_PORT },
    { "input",     K_PORT },
    { "integer",   K_REGISTER },
    { "module",    K_MODULE },
    { "output",    K_PORT },
    { "parameter", K_CONSTANT },
    { "real",      K_REGISTER },
    { "realtime",  K_REGISTER },
    { "reg",       K_REGISTER },
    { "specparam", K_CONSTANT },
    { "supply0",   K_NET },
    { "supply1",   K_NET },
    { "task",      K_TASK },
    { "time",      K_REGISTER },
    { "tri0",      K_NET },
    { "tri1",      K_NET },
    { "triand",    K_NET },
    { "tri",       K_NET },
    { "trior",     K_NET },
    { "trireg",    K_NET },
    { "wand",      K_NET },
    { "wire",      K_NET },
    { "wor",       K_NET }
};

/*
 *   FUNCTION DEFINITIONS
 */

static void initialize (const langType language)
{
    size_t i;
    const size_t count = sizeof (VerilogKeywordTable) /
			 sizeof (VerilogKeywordTable [0]);
    Lang_verilog = language;
    for (i = 0  ;  i < count  ;  ++i)
    {
	const keywordAssoc* const p = &VerilogKeywordTable [i];
	addKeyword (p->keyword, language, (int) p->kind);
    }
}

static void vUngetc (int c)
{
    Assert (Ungetc == '\0');
    Ungetc = c;
}

static int vGetc (void)
{
    int c;
    if (Ungetc == '\0')
	c = fileGetc ();
    else
    {
	c = Ungetc;
	Ungetc = '\0';
    }
    if (c == '/')
    {
	int c2 = fileGetc ();
	if (c2 == EOF)
	    longjmp (Exception, (int) ExceptionEOF);
	else if (c2 == '/')   /* strip comment until end-of-line */
	{
	    do
		c = fileGetc ();
	    while (c != '\n'  &&  c != EOF);
	}
	else if (c2 == '*')    /* strip block comment */
	{
	    do
	    {
		do
		    c = fileGetc ();
		while (c != '*'  &&  c != EOF);
		if (c != EOF)
		    c = fileGetc ();
	    } while (c != '/'  &&  c != EOF);
	    if (c != EOF)
		c = ' ';      /* comment equivalent to white space */
	}
	else
	    Ungetc = c2;
    }
    else if (c == '"')        /* strip string contents */
    {
	int c2;
	do
	    c2 = fileGetc ();
	while (c2 != '"'  &&  c2 != EOF);
	c = '@';
    }
    if (c == EOF)
	longjmp (Exception, (int) ExceptionEOF);
    return c;
}

static boolean isIdentifierCharacter (const int c)
{
    return (boolean)(isalnum (c)  ||  c == '_'  ||  c == '`');
}

static int skipWhite (int c)
{
    while (isspace (c))
	c = vGetc ();
    return c;
}

static int skipPastMatch (const char *const pair)
{
    const int begin = pair [0], end = pair [1];
    int matchLevel = 1;
    int c;
    do
    {
	c = vGetc ();
	if (c == begin)
	    ++matchLevel;
	else if (c == end)
	    --matchLevel;
    }
    while (matchLevel > 0);
    return vGetc ();
}

static boolean readIdentifier (vString *const name, int c)
{
    vStringClear (name);
    if (isIdentifierCharacter (c))
    {
	while (isIdentifierCharacter (c))
	{
	    vStringPut (name, c);
	    c = vGetc ();
	}
	vUngetc (c);
	vStringTerminate (name);
    }
    return (boolean)(name->length > 0);
}

static void tagNameList (const verilogKind kind, int c)
{
    vString *name = vStringNew ();
    boolean repeat;
    Assert (isIdentifierCharacter (c));
    do
    { 
	repeat = FALSE;
	if (isIdentifierCharacter (c))
	{
	    readIdentifier (name, c);
	    makeSimpleTag (name, VerilogKinds, kind);
	}
	else
	    break;
	c = skipWhite (vGetc ());
	if (c == '[')
	    c = skipPastMatch ("[]");
	c = skipWhite (c);
	if (c == '=')
	{
	    if (c == '{')
		skipPastMatch ("{}");
	    else
	    {
		do
		    c = vGetc ();
		while (c != ','  &&  c != ';');
	    }
	}
	if (c == ',')
	{
	    c = skipWhite (vGetc ());
	    repeat = TRUE;
	}
	else
	    repeat = FALSE;
    } while (repeat);
    vStringDelete (name);
    vUngetc (c);
}

static void findTag (vString *const name)
{
    const verilogKind kind = (verilogKind)
	    lookupKeyword (vStringValue (name), Lang_verilog);
    if (kind != K_UNDEFINED)
    {
	int c = skipWhite (vGetc ());

	/* Many keywords can have bit width.
	*   reg [3:0] net_name;
	*   inout [(`DBUSWIDTH-1):0] databus;
	*/
	if (c == '(')
	    c = skipPastMatch ("()");
	c = skipWhite (c);
	if (c == '[')
	    c = skipPastMatch ("[]");
	c = skipWhite (c);
	if (c == '#')
	{
	    c = vGetc ();
	    if (c == '(')
		c = skipPastMatch ("()");
	}
	c = skipWhite (c);
	if (isIdentifierCharacter (c))
	    tagNameList (kind, c);
    }
}

static void findVerilogTags (void)
{
    vString *const name = vStringNew ();
    volatile boolean newStatement = TRUE;
    volatile int c = '\0';
    exception_t exception = (exception_t) setjmp (Exception);

    if (exception == ExceptionNone) while (c != EOF)
    {
	c = vGetc ();
	switch (c)
	{
	    case ';':
	    case '\n':
		newStatement = TRUE;
		break;

	    case ' ':
	    case '\t':
		break;

	    default:
		if (newStatement && readIdentifier (name, c))
		    findTag (name);
		newStatement = FALSE;
		break;
	}
    }
    vStringDelete (name);
}

extern parserDefinition* VerilogParser (void)
{
    static const char *const extensions [] = { "v", NULL };
    parserDefinition* def = parserNew ("Verilog");
    def->kinds      = VerilogKinds;
    def->kindCount  = KIND_COUNT (VerilogKinds);
    def->extensions = extensions;
    def->parser     = findVerilogTags;
    def->initialize = initialize;
    return def;
}

/* vi:set tabstop=8 shiftwidth=4: */
