/*(( "Kopf" */
/* -----------------------------------------------------------------------------

   $Id: semantics_c.c,v 1.5 1997/07/17 00:24:10 mshopf Exp mshopf $

   GoldED API client, ©1995 Matthias Hopf.
   Compiled with SasC.

   C and C++ semantics parser

   ------------------------------------------------------------------------------
*/

/*)) */
#include "semantics.h"

/*
 * uncomment this if you want recursive comments like:   / * / *  xxx  * /  * /
 */
// #define RECURSIVE_COMMENTS

/*(( "Private prototypes" */

int isBlockKeyword (char *);
int isAttrKeyword (char *);

FUNC (DoStd);

FUNC (BLOCK);
FUNC (BLOCK_ONE);
FUNC (CMD_STD);
FUNC (CMD_EQ);
FUNC (ROUND_BRACK);
FUNC (SQUARE_BRACK);
FUNC (COMMENT);
FUNC (CPPCOMMENT);
FUNC (PRAEPROC);
FUNC (STRING_DOUBLE);
FUNC (STRING_SINGLE);

/*)) */
/*(( "CIndent ()" */

/****** Main routine ******/
void    Indent_C (sc_t *c)
{
    BLOCK (c, 0);
    if (c->CurrentLine <= c->EndIndent)
	Error (c, UNMATCHED_BRACE_ERROR);
    debug (Dbug, ("\n\n"));             // Do it always...
}

/*)) */
/*(( "IsWord_C ()" */

int IsWord_C (sc_t *c, char *buf, int len, int maxlen)
{
    char *t;
    switch (len) {
    case 0:
    case 1:
	return MATCH_TRUE;

    case 2:
	switch (buf [0]) {
	case '/':
	    switch (buf [1]) {
	    case '/':
	    case '*':
		return MATCH_EXACT;
	    }
	    break;
	case '*':
	    switch (buf [1]) {
	    case '/':
		return MATCH_EXACT;
	    case '*':                   /* for  / *  ... ** ... * / multiline comments */
		if (len < maxlen && buf [2] == '/') /* mind the '* /' */
		    return MATCH_FALSE;
		else
		    return MATCH_EXACT;
	    }
	    break;
	case '.':
	    switch (buf [1]) {
	    case '.':
		if (len < maxlen && buf [2] == '.') /* potential ... */
		    return MATCH_TRUE;
		return MATCH_FALSE;     /* we have no '..' in C */
	    }
	    break;
	case '+':
	    switch (buf [1]) {
	    case '+':
		return MATCH_EXACT;
	    }
	    break;
	case '-':
	    switch (buf [1]) {
	    case '>':
	    case '-':
		return MATCH_EXACT;
	    }
	    break;
	case ':':
	    switch (buf [1]) {
	    case ':':
		return MATCH_EXACT;
	    }
	    break;
	case '&':
	    switch (buf [1]) {
	    case '&':
		return MATCH_TRUE;      /* &&= possible */
	    }
	    break;
	case '|':
	    switch (buf [1]) {
	    case '|':
		return MATCH_TRUE;      /* ||= possible */
	    }
	    break;
	case '^':
	    switch (buf [1]) {
	    case '^':
		return MATCH_TRUE;      /* ^^= possible */
	    }
	    break;
	case '>':
	    switch (buf [1]) {
	    case '>':
		return MATCH_TRUE;      /* >>= possible */
	    }
	    break;
	case '<':
	    switch (buf [1]) {
	    case '<':
		return MATCH_TRUE;      /* <<= possible */
	    }
	    break;
	}
	switch (buf [1]) {
	case '=':                       /* ==, !=, +=, *=, ...*/
	    switch (buf [0]) {
	    case '!':
	    case '=':
	    case '>':
	    case '<':
	    case '+':
	    case '-':
	    case '*':
	    case '/':
	    case '%':
	    case '|':
	    case '&':
	    case '^':
		return MATCH_EXACT;
	    }
	    return MATCH_FALSE;
	}
	break;

    case 3:
	switch (buf [2]) {
	case '.':                       /* printf (char *format, ...) */
	    if (buf [1] == '.' && buf [2] == '.')
		return MATCH_EXACT;
	    return MATCH_FALSE;
	case '=':                       /* >>=, &&=, ...*/
	    if (buf [0] == buf [1])
		switch (buf [0]) {
		case '>':
		case '<':
		case '&':
		case '|':
		case '^':
		    return MATCH_EXACT;
		}
		return MATCH_FALSE;

	}
	break;
    }

    if (isalpha (buf [0]) || buf[0] == '_')
    {
	for (t = buf+1; t < & buf [len]; t++)
	    if (!isalnum (*t) && *t != '_')
		return MATCH_FALSE;
	return MATCH_TRUE;
    }
    if (isdigit (buf [0]) || buf [0] == '.' || buf [0] == '-' || buf [0] == '+')
    {
	for (t = buf+1; t < & buf [len]; t++)
	    if (!isdigit (*t) && *t != '.' && *t != 'e')
		return MATCH_FALSE;
	return MATCH_TRUE;
    }
    return MATCH_FALSE;
}


/*)) */
/*(( "KeyPress_C ()" */

void KeyPress_C (sc_t *c, int key)
{
    int Line   = c->Edit->Line;
    int Col    = c->Edit->Column;
    int Len, i;
    char *Text;
    int StartLine = -1, EndLine = -1;

    if (c->Edit->CurrentBuffer)
    {
	Text = c->Edit->CurrentBuffer;
	Len  = c->Edit->CurrentLen;
    }
    else
    {
	Text = c->Edit->TextNodes [Line] .Text;
	Len  = c->Edit->TextNodes [Line] .Len;
    }


    switch (key) {

    case '*':
	    /* only when first char in line or before or after '/' or '*' */
	for (i = 0; i < Col; i++)
	    if (! isspace (Text [i]))
		break;
	if (i == Col)
	{
	    StartLine = EndLine = Line;
	    break;
	}
	    /* no break!*/

    case '/':
	    /* only before or or after '/' or '*' */
	if (Col != 0)
	{
	    switch (Text [Col-1]) {
	    case '/':
	    case '*':
		StartLine = EndLine = Line;
	    }
	}
	if (Col < Len-1)
	{
	    switch (Text [Col+1]) {
	    case '/':
	    case '*':
		StartLine = EndLine = Line;
	    }
	}
	break;

    case ':':
    case '{':
    case '}':
	StartLine = EndLine = Line;
	break;

    case '\n':
    case '\15':
	StartLine = Line > 0 ? Line-1 : 0;
	EndLine   = Line;
    }

    if (StartLine >= 0)
    {
	InitIndent (c, StartLine, EndLine, MODE_LINE | MODE_CURSOR);
	Indent_C (c);
	CleanupIndent (c);
    }
}


/*)) */
/*(( "Semantics structure" */

/****** Method structure ******/
struct Semantic C_Sem =
{
    "c",
    "C and C++ semantics parser",
    "V0.1 ©1995 Matthias Hopf",
    IsWord_C,
    KeyPress_C,
    Indent_C,
    { 4, 0, -2, 2, 1, 4, 40 }
} ;

/*)) */

/*(( "isBlockKeyword ()" */

/****** Test if a character string is a block keyword ******/
int     isBlockKeyword (char *t)
{
	/* 'if'  needs special treatment due to 'else' */
	/* 'try' needs special treatment due to 'catch' */
    return (streq (t, "if") || streq (t, "else") || streq (t, "do")
	    || streq (t, "for") || streq (t, "while")
	    || streq (t, "switch") || streq (t, "catch"));
}

/*)) */
/*(( "isAttrKeyword ()" */

/****** Test if a character string is a attribute keyword ******/
/* int, char, etc. are not listed here, because they are types! */
int     isAttrKeyword (char *t)
{
    return (   streq (t, "enum")     || streq (t, "struct")    || streq (t, "union")
	       || streq (t, "auto")     || streq (t, "static")    || streq (t, "extern")
	       || streq (t, "const")    || streq (t, "unsigned")  || streq (t, "signed")
	       || streq (t, "register") || streq (t, "volatile")
	       || streq (t, "template") || streq (t, "friend")    || streq (t, "class")
	       || streq (t, "inline")   || streq (t, "virtual")   || streq (t, "asm"));
}

/*)) */
/*(( "DoStd ()" */

/****** Handling of standard situations ******/
FUNC (DoStd)
{
    debug (D_PARSER, ("DoStd[%s](%ld)\t", W, I));
    dcheck;

    switch (W[0]) {
    case '#':
	if (C == 0)
	    CALL (PRAEPROC, 0);
	else
	    ERROR (SYNTAX_ERROR);
	return;

    case '(':
	UPFIRST (I);
	CALL (ROUND_BRACK, N);
	INDENT  (I);                    /* Try to indent ')' */
	return;

    case '[':
	UPFIRST (I);
	CALL (SQUARE_BRACK, N);
	INDENT  (I);                    /* Try to indent ')' */
	return;

    case '"':
	UPFIRST (I);
	CALL (STRING_DOUBLE, C+1);
	return;

    case '\'':
	UPFIRST (I);
	CALL (STRING_SINGLE, C+1);
	return;

    case '/':
	switch (W[1]) {
	case '/':
	    if (BOL)
	    {
		if (C == 0)
		    INDENT (0);
		else
		    INDENT (I+CONFIG.Comment_Level);
	    }
	    else
		INDENT (CONFIG.LineComment_Abs);
	    CALL (CPPCOMMENT, 0);       /* new indentation level is irrelelvant (one line comment) */
	    return;
	case '*':
	    if (BOL)
	    {
		if (C == 0)
		    INDENT (0);
		else
		    INDENT (I+CONFIG.Comment_Level);
	    }
	    else
		INDENT (CONFIG.LineComment_Abs);
	    CALL (COMMENT, C);
	    return;
		/* no default */
	}
	    /* no break */

    default:
	ERROR (INTERNAL_ERROR);
	return;
    }
}

/*)) */
/*(( "BLOCK ()" */

FUNC (BLOCK)
{
    debug (D_PARSER, ("BLOCK(%ld)\t", I));
    dcheck;

    SETBOL;                             /* Always indent first thing after '{' */

    DOGET
    {
	c->BlockIndent = I;

	switch (W[0]) {                 // no test for length == 1 !
	case '(':
	case '[':
	case '#':
	    CALL1 (DoStd);
	    break;

	case '{':
	    INDENT (I + CONFIG.Brace_Level);
	    CALL   (BLOCK, I + CONFIG.Block_Level);
	    INDENT (I + CONFIG.Brace_Level);
	    break;

	case '}':
	    return;

	case ';':
	    UPFIRST (I);
	    break;

	case ',':
	    CALL (CMD_STD, I + CONFIG.Cont_Level);
	    break;

	case '"':
	case '\'':
	    UPFIRST (I);
	    UNGET;
	    CALL (CMD_STD, C+1);
	    break;

	case '=':                       /* struct bla foo = { }; */
	    if (W[1] == '\0')
	    {
		UPFIRST (I);
		CALL (CMD_EQ, N);
		break;
	    }
		// no break!!!, e.g. >=
	case ')':
	case ']':
	case ':':
	case '?':
	    ERROR (SYNTAX_ERROR);

	case '\\':
	    if (EOL && W[1] == '\0')
		break;
	    ERROR (SYNTAX_ERROR);

	case '/':                       // Comment
	    if (W[1] != 0)
	    {
		CALL1 (DoStd);
		break;
	    }
		// no break!!!
	default:
	    UPFIRST (I);
	    if (streq (W, "else"))
	    {
		PEEK;
		if (streq (P, "if"))
		{
		    GET;
		    INDENT (I);
		}
		CALL (BLOCK_ONE, I + CONFIG.Block_Level);
	    }
	    else if (isBlockKeyword (W)) // else is already handled
		CALL (BLOCK_ONE, I + CONFIG.Block_Level);

	    else if (! isAttrKeyword (W))
	    {
		if (I == 0)             /* First level indentation will stay 0 (function definitions) */
		    CALL (CMD_STD, 0);
		else
		{
		    if (EOL)
			CALL (CMD_STD, I + CONFIG.Cont_Level);
		    else
			CALL (CMD_STD, N);
		}
		PEEK;                   /* have we encountered a label? (special case!) */
		if (P[0] == ':' && P[1] == '\0')
		{
			/* the current line is indented as a label if, and only if
			 * a) it was only preceeded by *one* alphanummeric word
			 * b) a 'case' is found as the first word in the line.
			 * public:, private:, etc. are handled by case a).
			 */
		    char *Text;
		    int  Len, i;

		    GetText (c, &Text, &Len);/* get first word in line */
		    for (i = 0; i <= Len; i++)
			if (! isspace (Text[i]))
			    break;
		    if (i+5 <= Len)     /* check for 'case' */
		    {
			if (strncmp (&Text[i], "case", 4) == 0)
			{
			    if (isspace (Text[i+4]))
				i = -1;
			}
		    }
		    if (i != -1)
		    {                   /* else check whether ':' comes immedeately after the first word */
			for (    ; i < Len; i++)
			    if (! isalnum (Text[i]))
				break;
			for (    ; i < Len; i++)
			    if (! isspace (Text[i]))
				break;
			if (Text[i] == ':')
			    i = -1;
		    }

		    if (i == -1)
		    {                   /* Ok, we *really* got a label */
			GET;
			GetText (c, &Text, &Len);
			for (i = 0; i <= C; i++)
			    if (! isspace (Text [i]))
				break;
			Move (c, i, c->BlockIndent + CONFIG.Label_Level);
			I = c->BlockIndent;
			break;
		    }
			
		    GET;                /* otherwise this was some C++ statement */
		}
	    }

	}
    }
}

/*)) */
/*(( "BLOCK_ONE ()" */

FUNC (BLOCK_ONE)
{
    int Initial;

    debug (D_PARSER, ("BLOCK_ONE(%ld)\t", I));
    dcheck;

    Initial = I - CONFIG.Block_Level;

    SETBOL;                             /* Always indent first thing after '{' */

    DOGET
    {
	switch (W[0]) {                 // no test for length == 1 !
	case '(':
	case '[':
	case '#':
	    CALL1 (DoStd);
	    break;

	case '{':
	    INDENT (Initial + CONFIG.Brace_Level);
	    CALL   (BLOCK, Initial + CONFIG.Block_Level);
	    INDENT (Initial + CONFIG.Brace_Level);
	    return;

	case ';':
	    UPFIRST (I);
	    return;

	case ',':
	    CALL (CMD_STD, I + CONFIG.Cont_Level);
	    break;

	case '}':
	case '=':                       // incl. ==
	case ')':
	case ']':
	case ':':
	case '?':
	case '"':
	case '\'':
	    ERROR (SYNTAX_ERROR);

	case '\\':
	    if (EOL && W[1] == '\0')
		break;
	    ERROR (SYNTAX_ERROR);

	case '/':                       // Comment
	    if (W[1] != 0)
	    {
		CALL1 (DoStd);
		break;
	    }
		// no break!!!
	default:
	    UPFIRST (I);
	    if (streq (W, "if"))
	    {
		for (;;)
		{
		    CALL (BLOCK_ONE, I + CONFIG.Block_Level);
		    PEEK;
		    if (streq (P, "else"))
		    {
			GET;
			UPFIRST (I);
			PEEK;
			if (streq (P, "if"))
			{
			    GET;
			    INDENT (I);
			    continue;
			}
			CALL (BLOCK_ONE, I + CONFIG.Block_Level);
		    }
		    return;
		}
	    }
	    else if (isBlockKeyword (W)) // if is already handled
		CALL (BLOCK_ONE, I + CONFIG.Block_Level);

	    else if (! isAttrKeyword (W))
	    {
		if (I == 0)             /* First level indentation will stay 0 (function definitions) */
		    CALL (CMD_STD, 0);
		else
		{
		    if (EOL)
			CALL (CMD_STD, I + CONFIG.Cont_Level);
		    else
			CALL (CMD_STD, N);
		}
	    }
	}
    }
}

/*)) */
/*(( "BLOCK_SET ()" */

FUNC (BLOCK_SET)
{
    debug (D_PARSER, ("BLOCK_SET(%ld)\t", I));
    dcheck;


    if (EOL)                            /* Only indent first thing after '{' when we are in a new line */
	SETBOL;
    else
	I = N;                          /* Otherwise we take the next char position as intendation */

    DOGET
    {
	c->BlockIndent = I;

	switch (W[0]) {                 // no test for length == 1 !
	case '(':
	case '[':
	case '#':
	    CALL1 (DoStd);
	    break;

	case '{':
	    INDENT (I + CONFIG.Brace_Level);
	    CALL   (BLOCK_SET, I + CONFIG.Block_Level);
	    INDENT (I + CONFIG.Brace_Level);
	    break;

	case '}':
	    return;

	case ',':
	    UPFIRST (I);
	    break;

	case '"':
	case '\'':
	    UPFIRST (I);
	    UNGET;
	    CALL (CMD_STD, N);
	    break;

	case ';':
	case '=':                       // incl. ==
	case ')':
	case ']':
	case ':':
	case '?':
	    ERROR (SYNTAX_ERROR);

	case '\\':
	    if (EOL && W[1] == '\0')
		break;
	    ERROR (SYNTAX_ERROR);

	case '/':                       // Comment
	    if (W[1] != 0)
	    {
		CALL1 (DoStd);
		break;
	    }
		// no break!!!
	default:
	    UPFIRST (I);
	    if (isBlockKeyword (W))
		ERROR (SUSPICIOUS_ERROR);
	    else if (! isAttrKeyword (W))
	    {
		if (EOL)
		    CALL (CMD_STD, I + CONFIG.Cont_Level);
		else
		    CALL (CMD_STD, N);
	    }
	}
    }
}

/*)) */
/*(( "ROUND_BRACK ()" */

FUNC (ROUND_BRACK)
{
    debug (D_PARSER, ("ROUND_BRACK(%ld)\t", I));
    dcheck;

    DOGET
    {
	switch (W[0]) {                 // no test for length == 1 !
	case '(':
	case '[':
	case '#':
	case '\'':
	case '"':
	    CALL1 (DoStd);
	    break;

	case ')':
	    return;                     /* Indented by DoStd */

	case ';':                       /* for (;;) */
	    UPFIRST (I);
	    break;

	case '{':
	case '}':
	case ']':
	    ERROR (SYNTAX_ERROR);

	case '\\':
	    if (W[1] == 0)
		break;
	    ERROR (SYNTAX_ERROR);

	case '/':                       // Comment
	    if (W[1] != 0)
	    {
		CALL1 (DoStd);
		break;
	    }
		// no break!!!
	default:
	    UPFIRST (I);
	    if (isBlockKeyword (W))
		ERROR (SUSPICIOUS_ERROR);
	    else
		CALL1 (CMD_STD);
	}
    }
}

/*)) */
/*(( "SQUARE_BRACK ()" */

FUNC (SQUARE_BRACK)
{
    debug (D_PARSER, ("SQUARE_BRACK(%ld)\t", I));
    dcheck;

    DOGET
    {
	switch (W[0]) {                 // no test for length == 1 !
	case '(':
	case '[':
	case '#':
	case '\'':
	case '"':
	    CALL1 (DoStd);
	    break;

	case ']':
	    return;                     /* Indented by DoStd */

	case ';':                       /* for (;;) */
	case '{':
	case '}':
	case ')':
	case '=':
	case ':':
	case '?':
	    ERROR (SYNTAX_ERROR);

	case '\\':
	    if (W[1] == 0)
		break;
	    ERROR (SYNTAX_ERROR);

	case '/':                       // Comment
	    if (W[1] != 0)
	    {
		CALL1 (DoStd);
		break;
	    }
		// no break!!!
	default:
	    UPFIRST (I);
	    if (isBlockKeyword (W))
		ERROR (SUSPICIOUS_ERROR);
	    else
		CALL1 (CMD_STD);
	}
    }
}

/*)) */
/*(( "CMD_STD ()" */

FUNC (CMD_STD)
{
    char real [4];

    debug (D_PARSER, ("CMD_STD(%ld)\t", I));
    dcheck;

    DOGET
    {
	strncpy (real, W, 3);
	if (W[1] == '=' || (W[1] != '\0' && W[2] == '='))
	    W[0] = '=';

	switch (W[0]) {                 // no test for length == 1 !
	case '(':
	case '[':
	case '"':
	case '\'':
	case '#':
	    CALL1 (DoStd);
	    break;

	case '}':
	case ',':
	case ';':
	case ')':
	case ']':
	    UNGET;
	    return;

	case '=':                       // = / == / += etc.
	    if (real[1] == '=' && real [2] == '\0')
	    {
		if (real[0] == '=' || real[0] == '<' || real [0] == '>' || real [0] == '!')
		{
		    UPFIRST (I);
		    break;
		}
	    }
		// no break!!!
	case '?':
	    UPFIRST (I);
	    CALL (CMD_EQ, N);
	    break;;

	case ':':                       // : / ::
	    if (W[1] == '\0')
	    {
		UNGET;
		return;
	    }
	    UPFIRST (I);
	    break;

	case '{':
	    I = c->BlockIndent;
	    INDENT (I + CONFIG.Brace_Level);
	    CALL   (BLOCK, I + CONFIG.Block_Level);
	    INDENT (I + CONFIG.Brace_Level);
	    return;

	case '\\':
	    if (W[1] == 0)
		break;
	    ERROR (SYNTAX_ERROR);

	case '/':                       // Comment
	    if (W[1] != 0)
	    {
		CALL1 (DoStd);
		break;
	    }
		// no break!!!
	default:
	    UPFIRST (I);
	    if (isBlockKeyword (W))
		ERROR (SUSPICIOUS_ERROR);
	}
    }
}

/*)) */
/*(( "CMD_EQ ()" */

FUNC (CMD_EQ)
{
    debug (D_PARSER, ("CMD_EQ(%ld)\t", I));
    dcheck;

    DOGET
    {
	switch (W[0]) {                 // no test for length == 1 !
	case '(':
	case '[':
	case '"':
	case '\'':
	case '#':
	    CALL1 (DoStd);
	    break;

	case '}':
	case ',':
	case ';':
	case ')':
	case ']':
	    UNGET;
	    return;

	case '?':
	    UPFIRST (I);
	    CALL (CMD_EQ, N);
	    break;;

	case '{':
	    I = c->BlockIndent;
	    INDENT (I + CONFIG.Brace_Level);
	    CALL   (BLOCK_SET, I + CONFIG.Block_Level);
	    INDENT (I + CONFIG.Brace_Level);
	    return;

	case '\\':
	    if (W[1] == 0)
		break;
	    ERROR (SYNTAX_ERROR);

	case '/':                       // Comment
	    if (W[1] != 0)
	    {
		CALL1 (DoStd);
		break;
	    }
		// no break!!!
	default:
	    UPFIRST (I);
	    if (isBlockKeyword (W))
		ERROR (SUSPICIOUS_ERROR);
	}
    }
}

/*)) */
/*(( "COMMENT ()" */

FUNC (COMMENT)
{
    int Intro, Extro;

    debug (D_PARSER, ("COMMENT(%ld)\t", I));
    dcheck;

    Intro  = I;                         /*        Position of the '/ *' */
    Extro  = I;                         /* Future Position of the '* /' */
    I      = N;

    DOGET
    {
	if (streq (W, "*/"))
	{
	    UPDATE (Extro);
	    return;
	}
#ifdef RECURSIVE_COMMENTS
	else if (streq (W, "/*"))       /* We have recursive comments */
	    CALL1 (DoStd);
#endif
	else if (BOL)
	{
	    if (W[0] == '*' && W[1] == '\0')
	    {
		Extro = Intro + 1;
		UPDATE (Intro + 1);
	    }
	    else if (streq (W, "**"))
	    {
		Extro = Intro;
		UPDATE (Intro);
	    }
	    else
	    {
		Extro = Intro;
		UPDATE (I);
	    }
	}
    }
}

/*)) */
/*(( "CPPCOMMENT ()" */

FUNC (CPPCOMMENT)
{
    debug (D_PARSER, ("CPPCOMMENT(%ld)\t", I));
    dcheck;

    GET;
    INDENT (I + 3);
    UNGET;

    DOGET
    {
	if (EOL)
	    return;
    }
}

/*)) */
/*(( "PRAEPROC ()" */

FUNC (PRAEPROC)
{
    char *Text;
    int  Len, i;

    debug (D_PARSER, ("PRAEPROC(%ld)\t", I));
    dcheck;

    if (EOL)
	ERROR (PRAEPROCESSOR_ERROR);

    GET;                                /* get preproc directive (e.g. define) */
    if (EOL)
	return;

	/* get name including parameters */
    GetText (c, &Text, &Len);
    for (i = N; i < Len; i++)
	if (isspace (Text[i]) || Text[i] == '\\')
	    break;
    for (     ; i < Len; i++)
	if (! isspace (Text[i]))
	    break;

    C = i;
    if (Text[i] == '\\')
	SET (I + CONFIG.Cont_Level);
    else
	SET (i);

    DOGET
    {
	UPFIRST (I);
	if (! streq (W, "\\") && EOL)
	    return;
    }
}

/*)) */
/*(( "STRING_SINGLE ()" */

FUNC (STRING_SINGLE)
{
    debug (D_PARSER, ("STRING_SINGLE(%ld)\t", I));
    dcheck;

    if (EOL)
	ERROR (STRING_TERMINATION_ERROR);

    DOGET
    {
	if (W[1] == 0)
	{
	    switch (W[0]) {
	    case '\'':
		return;

	    case '\\':
		GET;                    /* skip next char (may be ' or \ ) */
	    }
	}
	if (EOL)
	    ERROR (STRING_TERMINATION_ERROR);
    }
}

/*)) */
/*(( "STRING_DOUBLE ()" */

FUNC (STRING_DOUBLE)
{
    debug (D_PARSER, ("STRING_DOUBLE(%ld)\t", I));
    dcheck;

    if (EOL)
	ERROR (STRING_TERMINATION_ERROR);

    DOGET
    {
	if (W[1] == 0)
	{
	    switch (W[0]) {
	    case '"':
		return;

	    case '\\':
		GET;                    /* skip next char (may be " or \ ) */
	    }
	}
	if (EOL)
	    ERROR (STRING_TERMINATION_ERROR);
    }
}

/*)) */

