/*
* $Id: funcCase.c,v 4.17 2007/07/04 20:51:12 bkorb Exp $
*
* Time-stamp: "2007-07-04 11:25:51 bkorb"
* Last Committed: $Date: 2007/07/04 20:51:12 $
*
* This module implements the CASE text function.
*/
/*=--subblock=exparg=arg_name,arg_desc,arg_optional,arg_list=*/
/*
* This file is part of AutoGen.
* AutoGen copyright (c) 1992-2007 by Bruce Korb - all rights reserved
*
* AutoGen is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AutoGen is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#undef IS_LOW
#define IS_LOW(c) (((c) <= 'z') && ((c) >= 'a'))
#ifndef _toupper
# ifdef __toupper
# define _toupper(c) __toupper(c)
# else
# define _toupper(c) toupper(c)
# endif
#endif
#define PTRUP(p) STMTS(if(IS_LOW(*(p)))*(p)=_toupper(*(p));(p)++)
tSCC zBadRe[] = "Invalid regular expression: error %d (%s):\n%s";
typedef tSuccess (tSelectProc)( tCC* pzText, tCC* pzMatch );
static tSelectProc
Select_Compare,
Select_Compare_End,
Select_Compare_Start,
Select_Compare_Full,
Select_Equivalent,
Select_Equivalent_End,
Select_Equivalent_Start,
Select_Equivalent_Full,
Select_Match,
Select_Match_End,
Select_Match_Start,
Select_Match_Full,
Select_Match_Always;
/*
* This is global data used to keep track of the current CASE
* statement being processed. When CASE statements nest,
* these data are copied onto the stack and restored when
* the nested CASE statement's ESAC function is found.
*/
typedef struct case_stack tCaseStack;
struct case_stack {
tMacro* pCase;
tMacro* pSelect;
};
static tCaseStack current_case;
static tLoadProc mLoad_Select;
static tpLoadProc apCaseLoad[ FUNC_CT ] = { NULL };
static tpLoadProc apSelectOnly[ FUNC_CT ] = { NULL };
/* = = = START-STATIC-FORWARD = = = */
/* static forward declarations maintained by :mkfwd */
static void
compile_re( regex_t* pRe, char* pzPat, int flags );
static void
upString( char* pz );
static tSuccess
Select_Compare( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Compare_End( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Compare_Start( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Compare_Full( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Equivalent( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Equivalent_End( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Equivalent_Start( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Equivalent_Full( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Match( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Match_End( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Match_Start( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Match_Full( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Match_Always( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Match_Existence( tCC* pzText, tCC* pzMatch );
static tSuccess
Select_Match_NonExistence( tCC* pzText, tCC* pzMatch );
static tMacro*
mLoad_Select( tTemplate* pT, tMacro* pMac, tCC** ppzScan );
/* = = = END-STATIC-FORWARD = = = */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void
compile_re( regex_t* pRe, char* pzPat, int flags )
{
int rerr = regcomp( pRe, pzPat, flags );
if (rerr != 0) {
char zEr[ 128 ];
regerror( rerr, pRe, zEr, sizeof( zEr ));
fprintf( stderr, zBadRe, rerr, zEr, pzPat );
AG_ABEND( "Bad regular expression" );
}
}
static void
upString( char* pz )
{
while (*pz != NUL)
PTRUP(pz);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_contains_p
*
* what: substring match
* general_use:
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*==*"
*
* doc: Test to see if a string contains a substring. "strstr(3)"
* will find an address.
=*/
static tSuccess
Select_Compare( tCC* pzText, tCC* pzMatch )
{
return (strstr( pzText, pzMatch )) ? SUCCESS : FAILURE;
}
SCM
ag_scm_string_contains_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
return (strstr( pzText, pzSubstr) == NULL) ? SCM_BOOL_F : SCM_BOOL_T;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_ends_with_p
*
* what: string ending
* general_use:
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*=="
*
* doc: Test to see if a string ends with a substring.
* strcmp(3) returns zero for comparing the string ends.
=*/
static tSuccess
Select_Compare_End( tCC* pzText, tCC* pzMatch )
{
size_t vlen = strlen( pzMatch );
size_t tlen = strlen( pzText );
tSuccess res;
if (tlen < vlen)
res = FAILURE;
else if (strcmp( pzText + (tlen - vlen), pzMatch ) == 0)
res = SUCCESS;
else res = FAILURE;
return res;
}
SCM
ag_scm_string_ends_with_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
return (SUCCESSFUL( Select_Compare_End( pzText, pzSubstr )))
? SCM_BOOL_T : SCM_BOOL_F;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_starts_with_p
*
* what: string starting
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "==*"
*
* doc: Test to see if a string starts with a substring.
=*/
static tSuccess
Select_Compare_Start( tCC* pzText, tCC* pzMatch )
{
size_t vlen = strlen( pzMatch );
tSuccess res;
if (strncmp( pzText, pzMatch, vlen ) == 0)
res = SUCCESS;
else res = FAILURE;
return res;
}
SCM
ag_scm_string_starts_with_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
return (SUCCESSFUL( Select_Compare_Start( pzText, pzSubstr )))
? SCM_BOOL_T : SCM_BOOL_F;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_equals_p
*
* what: string matching
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "=="
*
* doc: Test to see if two strings exactly match.
=*/
static tSuccess
Select_Compare_Full( tCC* pzText, tCC* pzMatch )
{
return (strcmp( pzText, pzMatch ) == 0) ? SUCCESS : FAILURE;
}
SCM
ag_scm_string_equals_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
return (strcmp( pzText, pzSubstr) == 0) ? SCM_BOOL_T : SCM_BOOL_F;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_contains_eqv_p
*
* what: caseless substring
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*=*"
*
* doc: Test to see if a string contains an equivalent string.
* `equivalent' means the strings match, but without regard
* to character case and certain characters are considered `equivalent'.
* Viz., '-', '_' and '^' are equivalent.
=*/
static tSuccess
Select_Equivalent( tCC* pzText, tCC* pzMatch )
{
char* pz;
tSuccess res = SUCCESS;
AGDUPSTR( pz, pzText, "equiv chars" );
upString( pz );
if (strstr( pz, pzMatch ) == NULL)
res = FAILURE;
AGFREE( (void*)pz );
return res;
}
SCM
ag_scm_string_contains_eqv_p( SCM text, SCM substr )
{
tSCC zSrch[] = "search string";
char* pzSubstr;
SCM res;
AGDUPSTR( pzSubstr, ag_scm2zchars( substr, zSrch ), "substring" );
upString( pzSubstr );
if (SUCCESSFUL( Select_Equivalent( ag_scm2zchars( text, "sample text" ),
pzSubstr )))
res = SCM_BOOL_T;
else res = SCM_BOOL_F;
AGFREE( (void*)pzSubstr );
return res;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_ends_eqv_p
*
* what: caseless string ending
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*="
*
* doc: Test to see if a string ends with an equivalent string.
=*/
static tSuccess
Select_Equivalent_End( tCC* pzText, tCC* pzMatch )
{
size_t vlen = strlen( pzMatch );
size_t tlen = strlen( pzText );
if (tlen < vlen)
return FAILURE;
return (streqvcmp( pzText + (tlen - vlen), pzMatch ) == 0)
? SUCCESS
: FAILURE;
}
SCM
ag_scm_string_ends_eqv_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
return (SUCCESSFUL( Select_Equivalent_End( pzText, pzSubstr )))
? SCM_BOOL_T : SCM_BOOL_F;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_starts_eqv_p
*
* what: caseless string start
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "=*"
*
* doc: Test to see if a string starts with an equivalent string.
=*/
static tSuccess
Select_Equivalent_Start( tCC* pzText, tCC* pzMatch )
{
size_t vlen = strlen( pzMatch );
return (strneqvcmp( pzText, pzMatch, (int)vlen ) == 0)
? SUCCESS
: FAILURE;
}
SCM
ag_scm_string_starts_eqv_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
return (SUCCESSFUL( Select_Equivalent_Start( pzText, pzSubstr )))
? SCM_BOOL_T : SCM_BOOL_F;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_eqv_p
*
* what: caseless string match
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "="
*
* doc: Test to see if two strings are equivalent. `equivalent' means the
* strings match, but without regard to character case and certain
* characters are considered `equivalent'. Viz., '-', '_' and '^' are
* equivalent. If the arguments are not strings, then the result of the
* numeric comparison is returned.
*
* This is an overloaded operation. If the arguments are not both
* strings, then the query is passed through to @code{scm_num_eq_p()}.
=*/
static tSuccess
Select_Equivalent_Full( tCC* pzText, tCC* pzMatch )
{
return (streqvcmp( pzText, pzMatch ) == 0) ? SUCCESS : FAILURE;
}
SCM
ag_scm_string_eqv_p( SCM text, SCM substr )
{
char* pzText;
char* pzSubstr;
/*
* We are overloading the "=" operator. Our arguments may be
* numbers...
*/
if (! AG_SCM_STRING_P( text ) || ! AG_SCM_STRING_P( substr ))
return scm_num_eq_p( text, substr );
pzText = ag_scm2zchars( text, "text to match" );
pzSubstr = ag_scm2zchars( substr, "match expr" );
return (streqvcmp( pzText, pzSubstr) == 0) ? SCM_BOOL_T : SCM_BOOL_F;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_has_match_p
*
* what: contained regex match
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*~~*"
*
* doc: Test to see if a string contains a pattern.
* Case is significant.
=*/
/*=gfunc string_has_eqv_match_p
*
* what: caseless regex contains
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*~*"
*
* doc: Test to see if a string contains a pattern.
* Case is not significant.
=*/
static tSuccess
Select_Match( tCC* pzText, tCC* pzMatch )
{
/*
* On the first call for this macro, compile the expression
*/
if (pCurMacro->funcPrivate == NULL) {
regex_t* pRe = AGALOC( sizeof( *pRe ), "select match re" );
compile_re(pRe, (char*)pzMatch, (int)pCurMacro->res);
pCurMacro->funcPrivate = (void*)pRe;
}
if (regexec((regex_t*)pCurMacro->funcPrivate, pzText, (size_t)0,
NULL, 0) != 0)
return FAILURE;
return SUCCESS;
}
SCM
ag_scm_string_has_match_p( SCM text, SCM substr )
{
SCM res;
regex_t re;
compile_re( &re, ag_scm2zchars( substr, "match expr" ), REG_EXTENDED );
if (regexec(&re, ag_scm2zchars( text, "text to match" ), (size_t)0,
NULL, 0) == 0)
res = SCM_BOOL_T;
else res = SCM_BOOL_F;
regfree( &re );
return res;
}
SCM
ag_scm_string_has_eqv_match_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
SCM res;
regex_t re;
compile_re( &re, pzSubstr, REG_EXTENDED | REG_ICASE );
if (regexec(&re, pzText, (size_t)0, NULL, 0) == 0)
res = SCM_BOOL_T;
else res = SCM_BOOL_F;
regfree( &re );
return res;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_end_match_p
*
* what: regex match end
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*~~"
*
* doc: Test to see if a string ends with a pattern.
* Case is significant.
=*/
/*=gfunc string_end_eqv_match_p
*
* what: caseless regex ending
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "*~"
*
* doc: Test to see if a string ends with a pattern.
* Case is not significant.
=*/
static tSuccess
Select_Match_End( tCC* pzText, tCC* pzMatch )
{
regmatch_t m[2];
/*
* On the first call for this macro, compile the expression
*/
if (pCurMacro->funcPrivate == NULL) {
regex_t* pRe = AGALOC( sizeof( *pRe ), "select match end re" );
compile_re(pRe, (char*)pzMatch, (int)pCurMacro->res);
pCurMacro->funcPrivate = (void*)pRe;
}
if (regexec((regex_t*)pCurMacro->funcPrivate, pzText, (size_t)2, m, 0) != 0)
return FAILURE;
if (m[0].rm_eo != strlen( pzText ))
return FAILURE;
return SUCCESS;
}
SCM
ag_scm_string_end_match_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
SCM res;
regex_t re;
regmatch_t m[2];
compile_re( &re, pzSubstr, REG_EXTENDED );
if (regexec(&re, pzText, (size_t)2, m, 0) != 0)
res = SCM_BOOL_F;
else if (m[0].rm_eo != strlen( pzText ))
res = SCM_BOOL_F;
else res = SCM_BOOL_T;
regfree( &re );
return res;
}
SCM
ag_scm_string_end_eqv_match_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
SCM res;
regex_t re;
regmatch_t m[2];
compile_re( &re, pzSubstr, REG_EXTENDED | REG_ICASE );
if (regexec(&re, pzText, (size_t)2, m, 0) != 0)
res = SCM_BOOL_F;
else if (m[0].rm_eo != strlen( pzText ))
res = SCM_BOOL_F;
else res = SCM_BOOL_T;
regfree( &re );
return res;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_start_match_p
*
* what: regex match start
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "~~*"
*
* doc: Test to see if a string starts with a pattern.
* Case is significant.
=*/
/*=gfunc string_start_eqv_match_p
*
* what: caseless regex start
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "~*"
*
* doc: Test to see if a string starts with a pattern.
* Case is not significant.
=*/
static tSuccess
Select_Match_Start( tCC* pzText, tCC* pzMatch )
{
regmatch_t m[2];
/*
* On the first call for this macro, compile the expression
*/
if (pCurMacro->funcPrivate == NULL) {
regex_t* pRe = AGALOC( sizeof( *pRe ), "select match start re" );
compile_re(pRe, (char*)pzMatch, (int)pCurMacro->res);
pCurMacro->funcPrivate = (void*)pRe;
}
if (regexec((regex_t*)pCurMacro->funcPrivate, pzText, (size_t)2, m, 0) != 0)
return FAILURE;
if (m[0].rm_so != 0)
return FAILURE;
return SUCCESS;
}
SCM
ag_scm_string_start_match_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
SCM res;
regex_t re;
regmatch_t m[2];
compile_re( &re, pzSubstr, REG_EXTENDED );
if (regexec(&re, pzText, (size_t)2, m, 0) != 0)
res = SCM_BOOL_F;
else if (m[0].rm_so != 0)
res = SCM_BOOL_F;
else res = SCM_BOOL_T;
regfree( &re );
return res;
}
SCM
ag_scm_string_start_eqv_match_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
SCM res;
regex_t re;
regmatch_t m[2];
compile_re( &re, pzSubstr, REG_EXTENDED | REG_ICASE );
if (regexec(&re, pzText, (size_t)2, m, 0) != 0)
res = SCM_BOOL_F;
else if (m[0].rm_so != 0)
res = SCM_BOOL_F;
else res = SCM_BOOL_T;
regfree( &re );
return res;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=gfunc string_match_p
*
* what: regex match
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "~~"
*
* doc: Test to see if a string fully matches a pattern.
* Case is significant.
=*/
/*=gfunc string_eqv_match_p
*
* what: caseless regex match
* general_use:
*
* exparg: text, text to test for pattern
* exparg: match, pattern/substring to search for
*
* string: "~"
*
* doc: Test to see if a string fully matches a pattern.
* Case is not significant, but any character equivalences
* must be expressed in your regular expression.
=*/
static tSuccess
Select_Match_Full( tCC* pzText, tCC* pzMatch )
{
regmatch_t m[2];
/*
* On the first call for this macro, compile the expression
*/
if (pCurMacro->funcPrivate == NULL) {
regex_t* pRe = AGALOC( sizeof( *pRe ), "select match full re" );
if (OPT_VALUE_TRACE > TRACE_EXPRESSIONS) {
fprintf( pfTrace, "Compiling ``%s'' with bits 0x%lX\n",
pzMatch, pCurMacro->res );
}
compile_re(pRe, (char*)pzMatch, (int)pCurMacro->res);
pCurMacro->funcPrivate = pRe;
}
if (regexec((regex_t*)pCurMacro->funcPrivate, pzText, (size_t)2, m, 0) != 0)
return FAILURE;
if ( (m[0].rm_eo != strlen( pzText ))
|| (m[0].rm_so != 0) )
return FAILURE;
return SUCCESS;
}
SCM
ag_scm_string_match_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
SCM res;
regex_t re;
regmatch_t m[2];
compile_re( &re, pzSubstr, REG_EXTENDED );
if (regexec(&re, pzText, (size_t)2, m, 0) != 0)
res = SCM_BOOL_F;
else if ( (m[0].rm_eo != strlen( pzText ))
|| (m[0].rm_so != 0) )
res = SCM_BOOL_F;
else res = SCM_BOOL_T;
regfree( &re );
return res;
}
SCM
ag_scm_string_eqv_match_p( SCM text, SCM substr )
{
char* pzText = ag_scm2zchars( text, "text to match" );
char* pzSubstr = ag_scm2zchars( substr, "match expr" );
SCM res;
regex_t re;
regmatch_t m[2];
compile_re( &re, pzSubstr, REG_EXTENDED | REG_ICASE );
if (regexec(&re, pzText, (size_t)2, m, 0) != 0)
res = SCM_BOOL_F;
else if ( (m[0].rm_eo != strlen( pzText ))
|| (m[0].rm_so != 0) )
res = SCM_BOOL_F;
else res = SCM_BOOL_T;
regfree( &re );
return res;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* We don't bother making a Guile function for any of these :)
*/
static tSuccess
Select_Match_Always( tCC* pzText, tCC* pzMatch )
{
return SUCCESS;
}
/*
* If the "pzText" addresses "zNil", then we couldn't find a value
* and defaulted to an empty string. So, the result exists if
* the pzText address is anything except "zNil".
*/
static tSuccess
Select_Match_Existence( tCC* pzText, tCC* pzMatch )
{
return (pzText != zNil) ? SUCCESS : FAILURE;
}
static tSuccess
Select_Match_NonExistence( tCC* pzText, tCC* pzMatch )
{
return (pzText == zNil) ? SUCCESS : FAILURE;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=macfunc CASE
*
* what: Select one of several template blocks
* handler_proc:
* load_proc:
*
* desc:
*
* The arguments are evaluated and converted to a string, if necessary. A
* simple name will be interpreted as an AutoGen value name and its value will
* be used by the @code{SELECT} macros (see the example below and the
* expression evaluation function, @pxref{EXPR}). The scope of the macro is
* up to the matching @code{ESAC} macro. Within the scope of a @code{CASE},
* this string is matched against case selection macros. There are sixteen
* match macros that are derived from four different ways matches may be
* performed, plus an "always true", "true if the AutoGen value was found",
* and "true if no AutoGen value was found" matches. The codes for the
* nineteen match macros are formed as follows:
*
* @enumerate
* @item
* Must the match start matching from the beginning of the string?
* If not, then the match macro code starts with an asterisk (@code{*}).
* @item
* Must the match finish matching at the end of the string?
* If not, then the match macro code ends with an asterisk (@code{*}).
* @item
* Is the match a pattern match or a string comparison?
* If a comparison, use an equal sign (@code{=}).
* If a pattern match, use a tilde (@code{~}).
* @item
* Is the match case sensitive?
* If alphabetic case is important, double the tilde or equal sign.
* @item
* Do you need a default match when none of the others match?
* Use a single asterisk (@code{*}).
* @item
* Do you need to distinguish between an empty string value and a value
* that was not found? Use the non-existence test (@code{!E}) before
* testing a full match against an empty string (@code{== ''}).
* There is also an existence test (@code{+E}), more for symmetry than
* for practical use.
* @end enumerate
*
* @noindent
* For example:
*
* @example
* [+ CASE +]
* [+ ~~* "[Tt]est" +]reg exp must match at start, not at end
* [+ == "TeSt" +]a full-string, case sensitive compare
* [+ = "TEST" +]a full-string, case insensitive compare
* [+ !E +]not exists - matches if no AutoGen value found
* [+ == "" +]expression yielded a zero-length string
* [+ +E +]exists - matches if there is any value result
* [+ * +]always match - no testing
* [+ ESAC +]
* @end example
*
* @code{} (@pxref{expression syntax}) may be any expression,
* including the use of apply-codes and value-names. If the expression yields
* a number, it is converted to a decimal string.
*
* These case selection codes have also been implemented as
* Scheme expression functions using the same codes. They are documented
* in this texi doc as ``string-*?'' predicates (@pxref{Common Functions}).
=*/
/*=macfunc ESAC
*
* what: Terminate the @code{CASE} Template Block
* in-context:
*
* desc:
* This macro ends the @code{CASE} function template block.
* For a complete description, @xref{CASE}.
=*/
tMacro*
mFunc_Case( tTemplate* pT, tMacro* pMac )
{
typedef tSuccess (t_match_proc)( tCC*, tCC* );
/*
* There are only 15 procedures because the case insenstive matching
* get mapped into the previous four. The last three are "match always",
* "match if a value was found" "match if no value found".
*/
static t_match_proc* match_procs[] = {
&Select_Compare_Full,
&Select_Compare_End,
&Select_Compare_Start,
&Select_Compare,
&Select_Equivalent_Full,
&Select_Equivalent_End,
&Select_Equivalent_Start,
&Select_Equivalent,
&Select_Match_Full,
&Select_Match_End,
&Select_Match_Start,
&Select_Match,
&Select_Match_Always,
&Select_Match_Existence,
&Select_Match_NonExistence
};
tSCC* apzMatchName[] = {
"COMPARE_FULL",
"COMPARE_END",
"COMPARE_START",
"CONTAINS",
"EQUIVALENT_FULL",
"EQUIVALENT_END",
"EQUIVALENT_START",
"EQUIV_CONTAINS",
"MATCH_FULL",
"MATCH_END",
"MATCH_START",
"MATCH_WITHIN",
"MATCH_ALWAYS",
"MATCH_EXISTENCE",
"MATCH_NONEXISTENCE"
};
tMacro* pEnd = pT->aMacros + pMac->endIndex;
ag_bool needFree;
tCC* pzSampleText = evalExpression( &needFree );
/*
* Search through the selection clauses until we either
* reach the end of the list for this CASE macro, or we match.
*/
for (;;) {
tSuccess mRes;
pMac = pT->aMacros + pMac->sibIndex;
if (pMac >= pEnd) {
if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) {
fprintf( pfTrace, "CASE string `%s' did not match\n",
pzSampleText );
if (OPT_VALUE_TRACE < TRACE_EVERYTHING)
fprintf( pfTrace, zFileLine, pCurTemplate->pzTplFile,
pMac->lineNo );
}
break;
}
/*
* The current macro becomes the selected selection macro
*/
pCurMacro = pMac;
mRes = (*(match_procs[ pMac->funcCode & 0x0F ])
)( pzSampleText, pT->pzTemplText + pMac->ozText );
/*
* IF match, THEN generate and stop looking for a match.
*/
if (SUCCEEDED( mRes )) {
if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) {
fprintf( pfTrace, "CASE string `%s' %s matched `%s'\n",
pzSampleText,
apzMatchName[ pMac->funcCode & 0x0F ],
pT->pzTemplText + pMac->ozText );
if (OPT_VALUE_TRACE < TRACE_EVERYTHING)
fprintf( pfTrace, zFileLine, pCurTemplate->pzTplFile,
pMac->lineNo );
}
generateBlock( pT, pMac + 1, pT->aMacros + pMac->sibIndex );
break;
}
else if (OPT_VALUE_TRACE == TRACE_EVERYTHING) {
fprintf( pfTrace, "CASE no match: `%s' %s vs. `%s'\n",
pzSampleText,
apzMatchName[ pMac->funcCode & 0x0F ],
pT->pzTemplText + pMac->ozText );
}
}
if (needFree)
AGFREE( (void*)pzSampleText );
return pEnd;
}
/*
* mLoad_CASE
*
* This function is called to set up (load) the macro
* when the template is first read in (before processing).
*/
tMacro*
mLoad_Case( tTemplate* pT, tMacro* pMac, tCC** ppzScan )
{
size_t srcLen = (size_t)pMac->res; /* macro len */
tCaseStack save_stack = current_case;
tMacro* pEsacMac;
/*
* Save the global macro loading mode
*/
tpLoadProc* papLP = papLoadProc;
/*
* IF there is no associated text expression
* THEN woops! what are we to case on?
*/
if (srcLen == 0)
AG_ABEND_IN( pT, pMac, "expressionless CASE" );
/*
* Load the expression
*/
(void)mLoad_Expr( pT, pMac, ppzScan );
/*
* IF this is the first time here,
* THEN set up the "CASE" mode callout table.
* It is the standard table, except entries are inserted
* for SELECT and ESAC.
*/
if (apCaseLoad[0] == NULL) {
int i;
/*
* Until there is a selection clause, _any_ other
* macro will be illegal (except comments :)!
*/
for (i=0; i < FUNC_CT; i++)
apSelectOnly[i] = &mLoad_Bogus;
memcpy( (void*)apCaseLoad, apLoadProc, sizeof( apLoadProc ));
apSelectOnly[ FTYP_COMMENT] = mLoad_Comment;
apSelectOnly[ FTYP_SELECT ] = \
apCaseLoad[ FTYP_SELECT ] = &mLoad_Select;
apCaseLoad[ FTYP_ESAC ] = &mLoad_Ending;
}
/*
* Set the "select macro only" loading mode
*/
papLoadProc = apSelectOnly;
/*
* Save global pointers to the current macro entry.
* We will need this to link the CASE, SELECT and ESAC
* functions together.
*/
current_case.pCase = current_case.pSelect = pMac;
/*
* Continue parsing the template from this nested level
*/
pEsacMac = parseTemplate( pMac+1, ppzScan );
if (*ppzScan == NULL)
AG_ABEND_IN( pT, pMac, "ESAC not found" );
/*
* Tell the last select macro where its end is.
* (It ends with the "next" sibling. Since there
* is no "next" at the end, it is a tiny lie.)
*
* Also, make sure the CASE macro knows where the end is.
*/
pMac->endIndex = \
current_case.pSelect->sibIndex = (pEsacMac - pT->aMacros);
/*
* Restore any enclosing CASE function's context.
*/
current_case = save_stack;
/*
* Restore the global macro loading mode
*/
papLoadProc = papLP;
/*
* Return the next available macro descriptor
*/
return pEsacMac;
}
/*=macfunc SELECT
*
* what: Selection block for CASE function
* in-context:
* alias: | ~ | = | * | ! | + |
* unload-proc:
*
* desc:
* This macro selects a block of text by matching an expression
* against the sample text expression evaluated in the @code{CASE}
* macro. @xref{CASE}.
*
* You do not specify a @code{SELECT} macro with the word ``select''.
* Instead, you must use one of the 19 match operators described in
* the @code{CASE} macro description.
=*/
static tMacro*
mLoad_Select( tTemplate* pT, tMacro* pMac, tCC** ppzScan )
{
char const* pzScan = *ppzScan; /* text after macro */
char* pzCopy = pT->pNext; /* next text dest */
char const* pzSrc = (char*)pMac->ozText; /* macro text */
long srcLen = pMac->res; /* macro len */
tSCC zInvSel[] = "Invalid selection clause";
int typ = (int)FTYP_SELECT_COMPARE_FULL;
/*
* Set the global macro loading mode
*/
papLoadProc = apCaseLoad;
pMac->res = 0;
if (srcLen == 0)
AG_ABEND_IN( pT, pMac, "Empty macro text" );
/*
* IF the first character is an asterisk,
* THEN the match can start anywhere in the string
*/
if (*pzSrc == '*') {
pzSrc++;
if (isspace( *pzSrc ) || (*pzSrc == NUL)) {
typ = (int)FTYP_SELECT_MATCH_ANYTHING;
srcLen = 0;
pMac->ozText = 0;
goto selection_done;
}
typ |= (int)FTYP_SELECT_COMPARE_SKP_START;
}
/*
* The next character must indicate whether we are
* pattern matching ('~') or doing string compares ('=')
*/
switch (*pzSrc++) {
case '~':
/*
* Or in the pattern matching bit
*/
typ |= (int)FTYP_SELECT_MATCH_FULL;
pMac->res = REG_EXTENDED;
/* FALLTHROUGH */
case '=':
/*
* IF the '~' or '=' is doubled,
* THEN it is a case sensitive match. Skip over the char.
* ELSE or in the case insensitive bit
*/
if (pzSrc[0] == pzSrc[-1]) {
pzSrc++;
} else {
typ |= (int)FTYP_SELECT_EQUIVALENT_FULL;
}
break;
case '!':
case '+':
switch (*pzSrc) {
case 'e':
case 'E':
break;
default:
goto bad_sel;
}
if ((pzSrc[1] != NUL) && (! isspace( pzSrc[1] )))
goto bad_sel;
typ = (int)((pzSrc[-1] == '!')
? FTYP_SELECT_MATCH_NONEXISTENCE : FTYP_SELECT_MATCH_EXISTENCE);
srcLen = 0;
pMac->ozText = 0;
goto selection_done;
default:
bad_sel:
AG_ABEND_IN( pT, pMac, zInvSel );
}
/*
* IF the last character is an asterisk,
* THEN the match may end before the test string ends.
* OR in the "may end early" bit.
*/
if (*pzSrc == '*') {
pzSrc++;
typ |= (int)FTYP_SELECT_COMPARE_SKP_END;
}
if (! isspace( *pzSrc ))
AG_ABEND_IN( pT, pMac, zInvSel );
while (isspace(*pzSrc)) pzSrc++;
srcLen -= pzSrc - (char const*)pMac->ozText;
if (srcLen <= 0)
AG_ABEND_IN( pT, pMac, zInvSel );
/*
* See if we are doing case insensitive regular expressions
*/
if ( (typ & (int)FTYP_SELECT_EQV_MATCH_FULL)
== (int)FTYP_SELECT_EQV_MATCH_FULL) {
int bitSet;
pMac->res = REG_EXTENDED | REG_ICASE;
/*
* Turn off the case comparison mode for regular expressions.
* We don't have to worry about it. It is done for us.
*/
bitSet = ~(int)FTYP_SELECT_EQUIVALENT_FULL;
bitSet |= (int)FTYP_SELECT_COMPARE_FULL; /* dont turn this bit off! */
typ &= bitSet;
}
/*
* Copy the expression
*/
pzScan = pzCopy;
pMac->ozText = (pzCopy - pT->pzTemplText);
if (typ == (int)FTYP_SELECT_EQUIVALENT) {
do {
*(pzCopy++) = toupper( *(pzSrc++) );
} while (--srcLen > 0);
} else {
do {
*(pzCopy++) = *(pzSrc++);
} while (--srcLen > 0);
}
*(pzCopy++) = NUL;
*(pzCopy++) = NUL;
pT->pNext = pzCopy;
if ((*pzScan == '"') || (*pzScan == '\''))
spanQuote( (char*)pzScan );
selection_done:
pMac->funcCode = (teFuncType)typ;
current_case.pSelect->sibIndex = (pMac - pT->aMacros);
current_case.pSelect = (tMacro*)pMac;
return pMac + 1;
}
void
mUnload_Select( tMacro* pMac )
{
if (pMac->funcPrivate != NULL) {
regex_t* pRe = (regex_t*)pMac->funcPrivate;
regfree( pRe );
AGFREE(pRe);
}
}
/*
* Local Variables:
* mode: C
* c-file-style: "stroustrup"
* indent-tabs-mode: nil
* End:
* end of agen5/funcCase.c */