/**
* @file funcCase.c
*
* This module implements the CASE text function.
*
* @addtogroup autogen
* @{
*/
/*
* This file is part of AutoGen.
* AutoGen Copyright (C) 1992-2016 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _toupper
# ifdef __toupper
# define _toupper(c) __toupper(c)
# else
# define _toupper(c) toupper(c)
# endif
#endif
typedef tSuccess (tSelectProc)(char const * sample, char const * pattern);
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 {
macro_t * pCase;
macro_t * pSelect;
};
static tCaseStack current_case;
static load_proc_t mLoad_Select;
static load_proc_p_t apCaseLoad[ FUNC_CT ] = { NULL };
static load_proc_p_t apSelectOnly[ FUNC_CT ] = { NULL };
/* = = = START-STATIC-FORWARD = = = */
static void
compile_re(regex_t * re, char const * pat, int flags);
static inline void
up_case(char * pz);
static tSuccess
Select_Compare(char const * sample, char const * pattern);
static tSuccess
Select_Compare_End(char const * sample, char const * pattern);
static tSuccess
Select_Compare_Start(char const * sample, char const * pattern);
static tSuccess
Select_Compare_Full(char const * sample, char const * pattern);
static tSuccess
Select_Equivalent(char const * sample, char const * pattern);
static tSuccess
Select_Equivalent_End(char const * sample, char const * pattern);
static tSuccess
Select_Equivalent_Start(char const * sample, char const * pattern);
static tSuccess
Select_Equivalent_Full(char const * sample, char const * pattern);
static tSuccess
Select_Match(char const * sample, char const * pattern);
static tSuccess
Select_Match_End(char const * sample, char const * pattern);
static tSuccess
Select_Match_Start(char const * sample, char const * pattern);
static tSuccess
Select_Match_Full(char const * sample, char const * pattern);
static tSuccess
Select_Match_Always(char const * sample, char const * pattern);
static tSuccess
Select_Match_Existence(char const * sample, char const * pattern);
static tSuccess
Select_Match_NonExistence(char const * sample, char const * pattern);
static bool
selection_type_complete(templ_t * tpl, macro_t * mac, char const ** psrc);
static macro_t *
mLoad_Select(templ_t * tpl, macro_t * mac, char const ** pscan);
/* = = = END-STATIC-FORWARD = = = */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void
compile_re(regex_t * re, char const * pat, int flags)
{
int rerr = regcomp(re, VOIDP(pat), flags);
if (rerr != 0) {
char erbf[ SCRIBBLE_SIZE ];
regerror(rerr, re, erbf, sizeof(erbf));
fprintf(stderr, BAD_RE_FMT, rerr, erbf, pat);
AG_ABEND(COMPILE_RE_BAD);
}
}
static inline void
up_case(char * pz)
{
while (*pz != NUL) {
if (IS_LOWER_CASE_CHAR(*pz))
*pz = (char)_toupper((int)*pz);
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(char const * sample, char const * pattern)
{
return (strstr(sample, pattern)) ? 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(char const * sample, char const * pattern)
{
size_t vlen = strlen(pattern);
size_t tlen = strlen(sample);
tSuccess res;
if (tlen < vlen)
res = FAILURE;
else if (strcmp(sample + (tlen - vlen), pattern) == 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(char const * sample, char const * pattern)
{
size_t vlen = strlen(pattern);
tSuccess res;
if (strncmp(sample, pattern, 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(char const * sample, char const * pattern)
{
return (strcmp(sample, pattern) == 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(char const * sample, char const * pattern)
{
char * pz;
tSuccess res = SUCCESS;
AGDUPSTR(pz, sample, "equiv chars");
up_case(pz);
if (strstr(pz, pattern) == NULL)
res = FAILURE;
AGFREE(pz);
return res;
}
SCM
ag_scm_string_contains_eqv_p(SCM text, SCM substr)
{
char * pzSubstr;
SCM res;
AGDUPSTR(pzSubstr, ag_scm2zchars(substr, "search"), "substr");
up_case(pzSubstr);
if (SUCCESSFUL(Select_Equivalent(ag_scm2zchars(text, "sample"),
pzSubstr)))
res = SCM_BOOL_T;
else res = SCM_BOOL_F;
AGFREE(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(char const * sample, char const * pattern)
{
size_t vlen = strlen(pattern);
size_t tlen = strlen(sample);
if (tlen < vlen)
return FAILURE;
return (streqvcmp(sample + (tlen - vlen), pattern) == 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(char const * sample, char const * pattern)
{
size_t vlen = strlen(pattern);
return (strneqvcmp(sample, pattern, (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 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 both
* numbers, then the query is passed through to @code{scm_num_eq_p()},
* otherwise the result depends on the SCMs being strictly equal.
=*/
static tSuccess
Select_Equivalent_Full(char const * sample, char const * pattern)
{
return (streqvcmp(sample, pattern) == 0) ? SUCCESS : FAILURE;
}
SCM
ag_scm_string_eqv_p(SCM text, SCM substr)
{
/*
* We are overloading the "=" operator. Our arguments may be
* numbers or booleans...
*/
teGuileType tt = ag_scm_type_e(text);
{
teGuileType st = ag_scm_type_e(substr);
if (st != tt)
return SCM_BOOL_F;
}
switch (tt) {
case GH_TYPE_NUMBER:
return scm_num_eq_p(text, substr);
case GH_TYPE_STRING:
{
char * pzText = ag_scm2zchars(text, "text");
char * pzSubstr = ag_scm2zchars(substr, "m expr");
return (streqvcmp(pzText, pzSubstr) == 0) ? SCM_BOOL_T : SCM_BOOL_F;
}
case GH_TYPE_BOOLEAN:
default:
return (text == substr) ? 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(char const * sample, char const * pattern)
{
/*
* On the first call for this macro, compile the expression
*/
if (cur_macro->md_pvt == NULL) {
void * mat = VOIDP(pattern);
regex_t * re = AGALOC(sizeof(*re), "select match re");
compile_re(re, mat, (int)cur_macro->md_res);
cur_macro->md_pvt = VOIDP(re);
}
if (regexec((regex_t *)cur_macro->md_pvt, sample, (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(char const * sample, char const * pattern)
{
regmatch_t m[2];
/*
* On the first call for this macro, compile the expression
*/
if (cur_macro->md_pvt == NULL) {
void * mat = VOIDP(pattern);
regex_t * re = AGALOC(sizeof(*re), "select match end re");
compile_re(re, mat, (int)cur_macro->md_res);
cur_macro->md_pvt = VOIDP(re);
}
if (regexec((regex_t *)cur_macro->md_pvt, sample, (size_t)2, m, 0)
!= 0)
return FAILURE;
if (m[0].rm_eo != (int)strlen(sample))
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 != (int)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 != (int)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(char const * sample, char const * pattern)
{
regmatch_t m[2];
/*
* On the first call for this macro, compile the expression
*/
if (cur_macro->md_pvt == NULL) {
void * mat = VOIDP(pattern);
regex_t * re = AGALOC(sizeof(*re), "select match start re");
compile_re(re, mat, (int)cur_macro->md_res);
cur_macro->md_pvt = VOIDP(re);
}
if (regexec((regex_t *)cur_macro->md_pvt, sample, (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(char const * sample, char const * pattern)
{
regmatch_t m[2];
/*
* On the first call for this macro, compile the expression
*/
if (cur_macro->md_pvt == NULL) {
void * mat = VOIDP(pattern);
regex_t * re = AGALOC(sizeof(*re), "select match full re");
if (OPT_VALUE_TRACE > TRACE_EXPRESSIONS) {
fprintf(trace_fp, TRACE_SEL_MATCH_FULL,
pattern, cur_macro->md_res);
}
compile_re(re, mat, (int)cur_macro->md_res);
cur_macro->md_pvt = re;
}
if (regexec((regex_t *)cur_macro->md_pvt, sample, (size_t)2, m, 0)
!= 0)
return FAILURE;
if ( (m[0].rm_eo != (int)strlen( sample ))
|| (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 != (int)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 != (int)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(char const * sample, char const * pattern)
{
(void)sample;
(void)pattern;
return SUCCESS;
}
/**
* If the "sample" addresses "zNil", then we couldn't find a value and
* defaulted to an empty string. So, the result is true if the sample
* address is anything except "zNil".
*/
static tSuccess
Select_Match_Existence(char const * sample, char const * pattern)
{
(void)pattern;
return (sample != no_def_str) ? SUCCESS : FAILURE;
}
/**
* If the "sample" addresses "zUndefined", then we couldn't find a value and
* defaulted to an empty string. So, the result false if the sample address
* is anything except "zUndefined".
*/
static tSuccess
Select_Match_NonExistence(char const * sample, char const * pattern)
{
(void)pattern;
return (sample == no_def_str) ? 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 <full-expression> +]
* [+ ~~* "[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{<full-expression>} (@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}.
=*/
macro_t *
mFunc_Case(templ_t * pT, macro_t * pMac)
{
typedef tSuccess (t_match_proc)(char const *, char const *);
/*
* 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 * const 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
};
static char const * const match_names[] = {
"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"
};
macro_t * end_mac = pT->td_macros + pMac->md_end_idx;
bool free_txt;
char const * samp_text = eval_mac_expr(&free_txt);
/*
* 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->td_macros + pMac->md_sib_idx;
if (pMac >= end_mac) {
if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) {
fprintf(trace_fp, TRACE_CASE_FAIL, samp_text);
if (OPT_VALUE_TRACE == TRACE_EVERYTHING)
fprintf(trace_fp, TAB_FILE_LINE_FMT,
current_tpl->td_file, pMac->md_line);
}
break;
}
/*
* The current macro becomes the selected selection macro
*/
cur_macro = pMac;
mRes = (*(match_procs[pMac->md_code & 0x0F])
)(samp_text, pT->td_text + pMac->md_txt_off);
/*
* IF match, THEN generate and stop looking for a match.
*/
if (SUCCEEDED(mRes)) {
if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) {
fprintf(trace_fp, TRACE_CASE_MATCHED,
samp_text,
match_names[pMac->md_code & 0x0F],
pT->td_text + pMac->md_txt_off);
if (OPT_VALUE_TRACE == TRACE_EVERYTHING)
fprintf(trace_fp, TAB_FILE_LINE_FMT,
current_tpl->td_file, pMac->md_line);
}
gen_block(pT, pMac + 1, pT->td_macros + pMac->md_sib_idx);
break;
}
else if (OPT_VALUE_TRACE == TRACE_EVERYTHING) {
fprintf(trace_fp, TRACE_CASE_NOMATCH,
samp_text,
match_names[pMac->md_code & 0x0F],
pT->td_text + pMac->md_txt_off);
}
}
if (free_txt)
AGFREE(samp_text);
return end_mac;
}
/*
* mLoad_CASE
*
* This function is called to set up (load) the macro
* when the template is first read in (before processing).
*/
macro_t *
mLoad_Case(templ_t * pT, macro_t * pMac, char const ** ppzScan)
{
size_t srcLen = (size_t)pMac->md_res; /* macro len */
tCaseStack save_stack = current_case;
macro_t * pEsacMac;
/*
* Save the global macro loading mode
*/
load_proc_p_t const * papLP = load_proc_table;
/*
* IF there is no associated text expression
* THEN woops! what are we to case on?
*/
if (srcLen == 0)
AG_ABEND_IN(pT, pMac, LD_CASE_NO_EXPR);
/*
* Load the expression
*/
(void)mLoad_Expr(pT, pMac, ppzScan);
/*
* IF this is the first time here,
* THEN set up the "CASE" mode callout tables.
* 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, only comment and select
* macros are allowed.
*/
for (i=0; i < FUNC_CT; i++)
apSelectOnly[i] = mLoad_Bogus;
memcpy(VOIDP(apCaseLoad), base_load_table, sizeof( base_load_table ));
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
*/
load_proc_table = 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 = parse_tpl(pMac+1, ppzScan);
if (*ppzScan == NULL)
AG_ABEND_IN(pT, pMac, LD_CASE_NO_ESAC);
/*
* 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->md_end_idx =
current_case.pSelect->md_sib_idx = (int)(pEsacMac - pT->td_macros);
/*
* Restore any enclosing CASE function's context.
*/
current_case = save_stack;
/*
* Restore the global macro loading mode
*/
load_proc_table = papLP;
/*
* Return the next available macro descriptor
*/
return pEsacMac;
}
/**
* Figure out the selection type. Return "true" (it is complete) if
* no argument is required. That is, if it is a "match anything" or
* an existence/non-existence test.
*
* @param[in] tpl The active template
* @param[in,out] mac The selection macro structure
* @param[out] psrc The scan pointer for the selection argument
*/
static bool
selection_type_complete(templ_t * tpl, macro_t * mac, char const ** psrc)
{
char const * src = (char *)mac->md_txt_off;
mac_func_t fcode = FTYP_SELECT_COMPARE_FULL;
/*
* IF the first character is an asterisk,
* THEN the match can start anywhere in the string
*/
if (*src == '*') {
src++;
if (IS_END_TOKEN_CHAR(*src)) {
mac->md_code = FTYP_SELECT_MATCH_ANYTHING;
goto leave_done;
}
fcode = (mac_func_t)(
(unsigned int)FTYP_SELECT_COMPARE_FULL |
(unsigned int)FTYP_SELECT_COMPARE_SKP_START);
}
/*
* The next character must indicate whether we are
* pattern matching ('~') or doing string compares ('=')
*/
switch (*src++) {
case '~':
/*
* Or in the pattern matching bit
*/
fcode = (mac_func_t)(
(unsigned int)fcode | (unsigned int)FTYP_SELECT_MATCH_FULL);
mac->md_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 (src[0] == src[-1]) {
src++;
} else {
fcode = (mac_func_t)(
(unsigned int)fcode |
(unsigned int)FTYP_SELECT_EQUIVALENT_FULL);
}
break;
case '!':
case '+':
switch (*src) {
case 'e':
case 'E':
break;
default:
goto bad_sel;
}
if (! IS_END_TOKEN_CHAR(src[1]))
goto bad_sel;
mac->md_code = (src[-1] == '!')
? FTYP_SELECT_MATCH_NONEXISTENCE
: FTYP_SELECT_MATCH_EXISTENCE;
goto leave_done;
default:
bad_sel:
AG_ABEND_IN(tpl, mac, LD_SEL_INVAL);
}
/*
* 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 (*src == '*') {
src++;
fcode = (mac_func_t)(
(unsigned int)fcode |
(unsigned int)FTYP_SELECT_COMPARE_SKP_END);
}
if (! IS_END_TOKEN_CHAR(*src))
AG_ABEND_IN(tpl, mac, LD_SEL_INVAL);
mac->md_code = fcode;
*psrc = SPN_WHITESPACE_CHARS(src);
return false;
leave_done:
/*
* md_code has been set. Zero out md_txt_off to indicate
* no argument text. NULL the selection argument pointer.
*/
mac->md_txt_off = 0;
*psrc = NULL;
return true;
}
/*=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 macro_t *
mLoad_Select(templ_t * tpl, macro_t * mac, char const ** pscan)
{
char const * sel_arg;
long arg_len = (long)mac->md_res; /* macro len */
(void)pscan;
/*
* Set the global macro loading mode
*/
load_proc_table = apCaseLoad;
mac->md_res = 0;
if (arg_len == 0)
AG_ABEND_IN(tpl, mac, LD_SEL_EMPTY);
if (selection_type_complete(tpl, mac, &sel_arg))
goto selection_done;
arg_len -= (intptr_t)(sel_arg - mac->md_txt_off);
if (arg_len <= 0)
AG_ABEND_IN(tpl, mac, LD_SEL_INVAL);
/*
* See if we are doing case insensitive regular expressions
* Turn off the case comparison mode for regular expressions.
* We don't have to worry about it. It is done for us.
*/
if ( ((int)mac->md_code & (int)FTYP_SELECT_EQV_MATCH_FULL)
== (int)FTYP_SELECT_EQV_MATCH_FULL) {
static unsigned int const bits =
~( unsigned int)FTYP_SELECT_EQUIVALENT_FULL
| (unsigned int)FTYP_SELECT_COMPARE_FULL;
mac->md_res = REG_EXTENDED | REG_ICASE;
mac->md_code = (mac_func_t)((unsigned int)mac->md_code & bits);
}
/*
* Copy the selection expression, double NUL terminate.
*/
{
char * dest = tpl->td_scan;
char const * svdest = dest;
mac->md_txt_off = (uintptr_t)(dest - tpl->td_text);
if (mac->md_code == FTYP_SELECT_EQUIVALENT) {
do {
*(dest++) = (char)toupper((uint8_t)*(sel_arg++));
} while (--arg_len > 0);
} else {
memcpy(dest, sel_arg, (size_t)arg_len);
dest += arg_len;
}
*(dest++) = NUL;
*(dest++) = NUL;
tpl->td_scan = dest;
/*
* If the value is a quoted string, strip the quotes and
* process the string (backslash fixup).
*/
if ((*svdest == '"') || (*svdest == '\''))
span_quote(VOIDP(svdest));
}
selection_done:
/*
* Link this selection macro to the list of selectors for CASE.
*/
current_case.pSelect->md_sib_idx = (int)(mac - tpl->td_macros);
current_case.pSelect = (macro_t *)mac;
return mac + 1;
}
/**
* Free data for a selection macro. Regular expression selections
* allocate the compiled re.
*/
void
mUnload_Select(macro_t * mac)
{
if (mac->md_pvt != NULL) {
regex_t * regexp = (regex_t *)mac->md_pvt;
regfree(regexp);
AGFREE(regexp);
}
}
/**
* @}
*
* Local Variables:
* mode: C
* c-file-style: "stroustrup"
* indent-tabs-mode: nil
* End:
* end of agen5/funcCase.c */