/*
 * $Id: regex.c,v 1.1 2000/08/07 23:15:04 emmerson Exp $
 *
 * This module implements the POSIX.2 regular expression API (yes, that's
 * numeral two) via the LDM regular expression API and library.
 */

#include "udposix.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "regex.h"		/* LDM/POSIX.2 interface */

/*
 * Allow us to get at the LDM's regular expression library.
 */
#undef regcomp
#undef regexec
#undef regerror
#undef regfree

#include "regexp.h"		/* LDM's regular expression API */

typedef struct errentry {
    int		errcode;
    const char	*msg;
} Errentry;

static Errentry	errtable[]	= {
    {REG_SUCCESS, "success"},
    {REG_NOMATCH, "no match"},
    {REG_BADPAT,  "bad regular expression"},
    {REG_ESPACE,  "malloc() failed"},
    {REG_EINVAL,  "invalid argument"},
    {0,           NULL}
};


/*
 * Return a lower-case version of a string.
 */
    static int
makelower(string, lowstring)
    const char	*string;
    char	**lowstring;	/* Lower-case version of `string' in 
				 * allocated memory.  Should be free(3)ed 
				 * by caller */
{
    int		status;

    if (NULL == (*lowstring = malloc(strlen(string)+1))) {
	status	= REG_ESPACE;
    } else {
	register char	*cp;

	(void) strcpy(*lowstring, string);
	for (cp = *lowstring; *cp; ++cp)
	    if (isupper(*cp))
		*cp	= tolower(*cp);

	status	= REG_SUCCESS;
    }

    return status;
}


/*
 * Compile a regular expression (internal use only).
 */
    static int
compile(preg, pattern, cflags)
    regex_t	*preg;
    const char	*pattern;
    int		cflags;
{
    int		status;

    preg->regexp	= regcomp((char*)pattern);
    if (NULL == preg->regexp) {
	status	= REG_BADPAT;
    } else {
	preg->cflags	= cflags;
	if (0 == cflags & REG_NOSUB || 0 == cflags & REG_EXTENDED) {
	    status	= REG_EINVAL;
	} else {
	    status	= REG_SUCCESS;
	}
    }

    return status;
}


/*
 * Compile a regular expression.
 */
    int
ldm_regcomp(preg, pattern, cflags)
    regex_t	*preg;
    const char	*pattern;
    int		cflags;
{
    int		status;

    if (NULL == preg || NULL == pattern) {
	status	= REG_EINVAL;
    } else {
	if (cflags & REG_ICASE) {
	    char	*lowstring;

	    if (0 == (status = makelower(pattern, &lowstring))) {
		status	= compile(preg, lowstring, cflags);
		(void) free((voidp)lowstring);
	    }
	} else {
	    status	= compile(preg, pattern, cflags);
	}
    }

    return status;
}


/*
 * Execute a regular expression program (internal use only).
 */
    static int
execute(preg, string, nmatch, pmatch)
    const regex_t	*preg;
    const char		*string;
    int			nmatch;
    regmatch_t		*pmatch;
{
    int			status;

    if (!regexec(preg->regexp, (char*)string)) {
	status	= REG_NOMATCH;
    } else {
	if (0 == nmatch || 0 == preg->cflags & REG_NOSUB) {
	    status	= REG_SUCCESS;
	} else {
	    if (NULL == pmatch) {
		status	= REG_EINVAL;
	    } else {
		int	i;

		for (i = 0; i < NSUBEXP && preg->regexp->startp[i]; ++i) {
		    pmatch[i].rm_so	= preg->regexp->startp[i] - string;
		    pmatch[i].rm_eo	= preg->regexp->endp[i] - string;
		}

		status	= REG_SUCCESS;
	    }
	}
    }

    return status;
}


/*
 * Execute a regular expression program.
 */
    int
ldm_regexec(preg, string, nmatch, pmatch, eflags)
    const regex_t	*preg;
    const char		*string;
    size_t		nmatch;
    regmatch_t		*pmatch;
    int			eflags;
{
    int			status;

    if (NULL == preg || NULL == string || eflags) {
	status	= REG_EINVAL;
    } else {
	if (preg->cflags & REG_ICASE) {
	    char	*lowstring;

	    if (0 == (status = makelower(string, &lowstring))) {
		status	= execute(preg, lowstring, nmatch, pmatch);
		(void) free((voidp)lowstring);
	    }
	} else {
	    status	= execute(preg, string, nmatch, pmatch);
	}
    }

    return status;
}


/*
 * Return an error string associated with an error number.
 */
    size_t
ldm_regerror(errcode, preg, errbuf, errbuf_size)
    int			errcode;
    /*ARGSUSED*/
    const regex_t	*preg;
    char		*errbuf;
    size_t		errbuf_size;
{
    int			status;
    Errentry		*entry;
    static Errentry	invalid_errcode	= {0, "invalid errcode"};

    for (entry = errtable; NULL != entry->msg; ++entry)
	if (errcode == entry->errcode)
	    break;

    if (NULL == entry->msg)
	entry	= &invalid_errcode;

    if (0 != errbuf_size) {
	(void) strncpy(errbuf, entry->msg, errbuf_size);
	errbuf[errbuf_size-1]	= 0;
    }

    status	= strlen(entry->msg) + 1;

    return status;
}


/*
 * Free memory associated with a regular expression program.
 */
    void
ldm_regfree(preg)
    regex_t	*preg;
{
    (void) free((voidp)preg->regexp);
}
