// clang-format off
/*
*   $Id: sml.c 536 2007-06-02 06:09:00Z elliotth $
*
*   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 "third_party/ctags/general.h"  /* must always come first */

#include "libc/mem/alg.h"
#include "libc/str/str.h"

#include "third_party/ctags/entry.h"
#include "third_party/ctags/parse.h"
#include "third_party/ctags/read.h"
#include "third_party/ctags/vstring.h"

/*
 *   DATA DECLARATIONS
 */
typedef enum {
	K_AND = -2,
	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      },
	{ "and",       K_AND       },
	{ "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;
	}
	vStringDelete (identifier);
	return result;
}

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

	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);
				if (foundTag == K_AND)
					makeSmlTag (lastTag, identifier);
				else
				{
					makeSmlTag (foundTag, identifier);
					lastTag = foundTag;
				}
			}
			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=4 shiftwidth=4: */
