/*
*   $Id: sml.c,v 1.3 2003/02/23 17:38:52 darren Exp $
*
*   Copyright (c) 2002, Venkatesh Prasad Ranganath and 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 SML language files.
*/

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

#include <string.h>

#include "entry.h"
#include "parse.h"
#include "read.h"
#include "vstring.h"

/*
 *   DATA DECLARATIONS
 */
typedef enum {
    K_NONE = -1,
    K_EXCEPTION,
    K_FUNCTION,
    K_FUNCTOR,
    K_SIGNATURE,
    K_STRUCTURE,
    K_TYPE,
    K_VAL
} smlKind;

/*
 *   DATA DEFINITIONS
 */
static kindOption SmlKinds[] = {
    { TRUE, 'e', "exception", "exception declarations" },
    { TRUE, 'f', "function",  "function definitions" },
    { TRUE, 'c', "functor",   "functor definitions" },
    { TRUE, 's', "signature", "signature declarations" },
    { TRUE, 'r', "structure", "structure declarations" },
    { TRUE, 't', "type",      "type definitions" },
    { TRUE, 'v', "value",     "value bindings" }
};

static struct {
    const char *keyword;
    smlKind kind;
} SmlKeywordTypes [] = {
    { "abstype",   K_TYPE      },
    { "datatype",  K_TYPE      },
    { "exception", K_EXCEPTION },
    { "functor",   K_FUNCTOR   },
    { "fun",       K_FUNCTION  },
    { "signature", K_SIGNATURE },
    { "structure", K_STRUCTURE },
    { "type",      K_TYPE      },
    { "val",       K_VAL       }
};

static unsigned int CommentLevel = 0;

/*
 * FUNCTION DEFINITIONS
 */

static void makeSmlTag (smlKind type, vString *name)
{
    tagEntryInfo tag;
    initTagEntry (&tag, vStringValue (name));
    tag.kindName = SmlKinds [type].name;
    tag.kind = SmlKinds [type].letter;
    makeTagEntry (&tag);
}

static const unsigned char *skipSpace (const unsigned char *cp)
{
    while (isspace ((int) *cp))
	++cp;
    return cp;
}

static boolean isIdentifier (int c)
{
    boolean result = FALSE;
    /* Consider '_' as an delimiter to aid user in tracking it's usage. */
    const char *const alternateIdentifiers = "!%&$#+-<>=/?@\\~'^|*_";
    if (isalnum (c))
	result = TRUE;
    else if (c != '\0'  &&  strchr (alternateIdentifiers, c) != NULL)
	result = TRUE;
    return result;
}

static const unsigned char *parseIdentifier (
	const unsigned char *cp, vString *const identifier)
{
    boolean stringLit = FALSE;
    vStringClear (identifier);
    while (*cp != '\0'  &&  (!isIdentifier ((int) *cp) || stringLit))
    {
	int oneback = *cp;
	cp++;
	if (oneback == '('  &&  *cp == '*'  &&  stringLit == FALSE)
	{
	    CommentLevel++;
	    return ++cp;
	}
	if (*cp == '"' && oneback != '\\')
	{
	    stringLit = TRUE;
	    continue;
	}
	if (stringLit && *cp == '"' && oneback != '\\')
	    stringLit = FALSE;
    }
    if (strcmp ((const char *) cp, "") == 0 || cp == NULL)
	return cp;

    while (isIdentifier ((int) *cp))
    {
	vStringPut (identifier, (int) *cp);
	cp++;
    }
    vStringTerminate (identifier);
    return cp;
}

static smlKind findNextIdentifier (const unsigned char **cp)
{
    smlKind result = K_NONE;
    vString *const identifier = vStringNew ();
    unsigned int count = sizeof (SmlKeywordTypes) / sizeof (SmlKeywordTypes [0]);
    unsigned int i;
    *cp = parseIdentifier (*cp, identifier);
    for (i = 0  ;  i < count  &&  result == K_NONE ;  ++i)
    {
	const char *id = vStringValue (identifier);
	if (strcmp (id, SmlKeywordTypes [i].keyword) == 0)
	    result = SmlKeywordTypes [i].kind;
    }
    return result;
}

static void findSmlTags (void)
{
    vString *const identifier = vStringNew ();
    const unsigned char *line;

    while ((line = fileReadLine ()) != NULL)
    {
	const unsigned char *cp = skipSpace (line);
	do
	{
	    smlKind foundTag;
	    if (CommentLevel != 0)
	    {
		cp = (const unsigned char *) strstr ((const char *) cp, "*)");
		if (cp == NULL)
		    continue;
		else
		{
		    --CommentLevel;
		    cp += 2;
		}
	    }
	    foundTag = findNextIdentifier (&cp);
	    if (foundTag != K_NONE)
	    {
		cp = skipSpace (cp);
		cp = parseIdentifier (cp, identifier);
		makeSmlTag (foundTag, identifier);
	    }
	    if (strstr ((const char *) cp, "(*") != NULL)
	    {
		cp += 2;
		cp = (const unsigned char *) strstr ((const char *) cp, "*)");
		if (cp == NULL)
		    ++CommentLevel;
	    }
	} while (cp != NULL  &&  strcmp ((const char *) cp, "") != 0);
    }
    vStringDelete (identifier);
}

extern parserDefinition *SmlParser (void)
{
    static const char *const extensions[] = { "sml", "sig", NULL };
    parserDefinition *def = parserNew ("SML");
    def->kinds = SmlKinds;
    def->kindCount = KIND_COUNT (SmlKinds);
    def->extensions = extensions;
    def->parser = findSmlTags;
    return def;
}

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