/** * @file expState.c * * This module implements expression functions that * query and get state information from AutoGen data. * * Time-stamp: "2012-04-07 09:50:32 bkorb" * * This file is part of AutoGen. * AutoGen Copyright (c) 1992-2012 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 . */ #ifdef SCM_HAVE_T_UINT64 typedef uint64_t ver_type_t; # define VER_UNIT_SHIFT 16ULL # if ((SCM_MAJOR_VERSION * 100) + SCM_MINOR_VERSION) >= 108 # define SCM_FROM(v) scm_from_uint64(v) # else # define SCM_FROM(v) gh_ulong2scm((unsigned long)v) # endif #else typedef uint32_t ver_type_t; # define VER_UNIT_SHIFT 8 # ifdef HAVE_SCM_FROM_UINT32 # define SCM_FROM(v) scm_from_uint32(v) # else # define SCM_FROM(v) gh_ulong2scm((unsigned long)v) # endif #endif /* = = = START-STATIC-FORWARD = = = */ static int entry_length(char* name); static int count_entries(char* name); static SCM find_entry_value(SCM op, SCM obj, SCM test); static ver_type_t str2int_ver(char* pz); static SCM do_tpl_file_line(int line_delta, char const * fmt); /* = = = END-STATIC-FORWARD = = = */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * EXPRESSION EVALUATION SUPPORT ROUTINES * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static int entry_length(char* name) { def_ent_t** papDefs = find_def_ent_list(name); int res = 0; if (papDefs == NULL) return 0; for (;;) { def_ent_t* pDE = *(papDefs++); if (pDE == NULL) break; if (pDE->de_type == VALTYP_TEXT) res += strlen(pDE->de_val.dvu_text); else res++; } return res; } static int count_entries(char* name) { def_ent_t** papDefs = find_def_ent_list(name); int res = 0; if (papDefs == NULL) return 0; for (;;) { def_ent_t* pDE = *(papDefs++); if (pDE == NULL) break; res++; } return res; } /** * Find a definition with a specific value */ static SCM find_entry_value(SCM op, SCM obj, SCM test) { bool isIndexed; def_ent_t* pE; char* pzField; { char * name = ag_scm2zchars(obj, "find name"); if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fprintf(trace_fp, TRACE_FIND_ENT, name); pzField = strchr(name, name_sep_ch); if (pzField != NULL) *(pzField++) = NUL; pE = find_def_ent(name, &isIndexed); } /* * No such entry? return FALSE */ if (pE == NULL) { if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fputs(FIND_ENT_FAIL, trace_fp); return SCM_BOOL_F; } /* * No subfield? Check the values */ if (pzField == NULL) { SCM result; SCM field; if (pE->de_type != VALTYP_TEXT) { if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fputs(FIND_ENT_FAIL, trace_fp); return SCM_BOOL_F; /* Cannot match string -- not a text value */ } field = AG_SCM_STR02SCM(pE->de_val.dvu_text); result = AG_SCM_APPLY2(op, field, test); if (! isIndexed) while (result == SCM_BOOL_F) { pE = pE->de_twin; if (pE == NULL) break; field = AG_SCM_STR02SCM(pE->de_val.dvu_text); result = AG_SCM_APPLY2(op, field, test); } if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fputs((result == SCM_BOOL_T) ? FIND_ENT_SUCC : FIND_ENT_FAIL, trace_fp); return result; } /* * a subfield for a text macro? return FALSE */ if (pE->de_type == VALTYP_TEXT) { if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fputs(FIND_ENT_FAIL, trace_fp); return SCM_BOOL_F; } /* * Search the members for what we want. */ pzField[-1] = name_sep_ch; { SCM field = AG_SCM_STR02SCM(pzField); SCM result; def_ctx_t ctx = curr_def_ctx; curr_def_ctx.dcx_prev = &ctx; curr_def_ctx.dcx_defent = pE->de_val.dvu_entry; result = find_entry_value(op, field, test); if (! isIndexed) while (result == SCM_BOOL_F) { pE = pE->de_twin; if (pE == NULL) break; curr_def_ctx.dcx_defent = pE->de_val.dvu_entry; result = find_entry_value(op, field, test); } curr_def_ctx = ctx; return result; } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * EXPRESSION ROUTINES * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*=gfunc base_name * * what: base output name * * doc: Returns a string containing the base name of the output file(s). * Generally, this is also the base name of the definitions file. =*/ SCM ag_scm_base_name(void) { return AG_SCM_STR02SCM((char*)(void*)OPT_ARG(BASE_NAME)); } /*=gfunc version_compare * * what: compare two version numbers * general_use: * * exparg: op, comparison operator * exparg: v1, first version * exparg: v2, compared-to version * * doc: Converts v1 and v2 strings into 64 bit values and returns the * result of running 'op' on those values. It assumes that the version * is a 1 to 4 part dot-separated series of numbers. Suffixes like, * "5pre4" or "5-pre4" will be interpreted as two numbers. The first * number ("5" in this case) will be decremented and the number after * the "pre" will be added to 0xC000. (Unless your platform is unable * to support 64 bit integer arithmetic. Then it will be added to 0xC0.) * Consequently, these yield true: * @example * (version-compare > "5.8.5" "5.8.5-pre4") * (version-compare > "5.8.5-pre10" "5.8.5-pre4") * @end example =*/ static ver_type_t str2int_ver(char* pz) { char* pzStr = pz; ver_type_t val = 0; int ix = 4; while (--ix >= 0) { unsigned int v; val <<= VER_UNIT_SHIFT; pz = SPN_WHITESPACE_CHARS(pz); next_number: if (! IS_DEC_DIGIT_CHAR(*pz)) break; v = (unsigned int)strtoul(pz, &pz, 0) & ((1 << VER_UNIT_SHIFT) - 1); if (pz == NULL) break; val += v; if (*pz == '-') pz++; switch (*pz) { case 'p': if ((pz[1] == 'r') && (pz[2] == 'e')) { pz += 3; val = (val << 2) - 1; val <<= (VER_UNIT_SHIFT - 2); if (--ix < 0) goto leave_str2int_ver; goto next_number; } /* FALLTHROUGH */ default: goto leave_str2int_ver; case '.': if (! IS_DEC_DIGIT_CHAR(*(++pz))) goto leave_str2int_ver; break; } } leave_str2int_ver: ; while (--ix >= 0) val <<= VER_UNIT_SHIFT; if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fprintf(trace_fp, TRACE_VER_CONVERT, (long long)val, pzStr); return val; } /** * Convert version number strings into a binary representation and compare. */ SCM ag_scm_version_compare(SCM op, SCM v1, SCM v2) { ver_type_t val1 = str2int_ver(ag_scm2zchars(v1, "ver")); ver_type_t val2 = str2int_ver(ag_scm2zchars(v2, "ver")); v1 = SCM_FROM(val1); v2 = SCM_FROM(val2); return scm_apply(op, v1, scm_cons(v2, AG_SCM_LISTOFNULL())); } /*=gfunc count * * what: definition count * * exparg: ag-name, name of AutoGen value * * doc: Count the number of entries for a definition. * The input argument must be a string containing the name * of the AutoGen values to be counted. If there is no * value associated with the name, the result is an SCM * immediate integer value of zero. =*/ SCM ag_scm_count(SCM obj) { int ent_len = count_entries(ag_scm2zchars(obj, "ag object")); return AG_SCM_INT2SCM(ent_len); } /*=gfunc def_file * * what: definitions file name * * doc: Get the name of the definitions file. * Returns the name of the source file containing the AutoGen * definitions. =*/ SCM ag_scm_def_file(void) { return AG_SCM_STR02SCM((char*)(void*)base_ctx->scx_fname); } /*=gfunc exist_p * * what: test for value name * * exparg: ag-name, name of AutoGen value * * doc: return SCM_BOOL_T iff a specified name has an AutoGen value. * The name may include indexes and/or member names. * All but the last member name must be an aggregate definition. * For example: * @example * (exist? "foo[3].bar.baz") * @end example * will yield true if all of the following is true: * @* * There is a member value of either group or string type * named @code{baz} for some group value @code{bar} that * is a member of the @code{foo} group with index @code{3}. * There may be multiple entries of @code{bar} within * @code{foo}, only one needs to contain a value for @code{baz}. =*/ SCM ag_scm_exist_p(SCM obj) { bool x; SCM res; if (find_def_ent(ag_scm2zchars(obj, "ag object"), &x) == NULL) res = SCM_BOOL_F; else res = SCM_BOOL_T; return res; } /*=gfunc ag_function_p * * what: test for function * * exparg: ag-name, name of AutoGen macro * * doc: return SCM_BOOL_T if a specified name is a user-defined AutoGen * macro, otherwise return SCM_BOOL_F. =*/ SCM ag_scm_ag_function_p(SCM obj) { SCM res; if (find_tpl(ag_scm2zchars(obj, "ag user macro")) == NULL) res = SCM_BOOL_F; else res = SCM_BOOL_T; return res; } /*=gfunc match_value_p * * what: test for matching value * * exparg: op, boolean result operator * exparg: ag-name, name of AutoGen value * exparg: test-str, string to test against * * doc: This function answers the question, "Is there an AutoGen value named * @code{ag-name} with a value that matches the pattern @code{test-str} * using the match function @code{op}?" Return SCM_BOOL_T iff at least * one occurrence of the specified name has such a value. The operator * can be any function that takes two string arguments and yields a * boolean. It is expected that you will use one of the string matching * functions provided by AutoGen. * @* * The value name must follow the same rules as the * @code{ag-name} argument for @code{exist?} (@pxref{SCM exist?}). =*/ SCM ag_scm_match_value_p(SCM op, SCM obj, SCM test) { if ( (! AG_SCM_IS_PROC(op)) || (! AG_SCM_STRING_P(obj)) ) return SCM_UNDEFINED; if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fprintf(trace_fp, TRACE_MATCH_VAL, ag_scm2zchars(test, "test val")); return find_entry_value(op, obj, test); } /*=gfunc get * * what: get named value * * exparg: ag-name, name of AutoGen value * exparg: alt-val, value if not present, optional * * doc: * Get the first string value associated with the name. * It will either return the associated string value (if * the name resolves), the alternate value (if one is provided), * or else the empty string. =*/ SCM ag_scm_get(SCM agName, SCM altVal) { def_ent_t* pE; bool x; pE = (! AG_SCM_STRING_P(agName)) ? NULL : find_def_ent(ag_scm2zchars(agName, "ag value"), &x); if ((pE == NULL) || (pE->de_type != VALTYP_TEXT)) { if (AG_SCM_STRING_P(altVal)) return altVal; return AG_SCM_STR02SCM(zNil); } return AG_SCM_STR02SCM(pE->de_val.dvu_text); } /*=gfunc get_c_name * * what: get named value, mapped to C name syntax * * exparg: ag-name, name of AutoGen value * * doc: * * Get the first string value associated with the name. It will either * return the associated string value (if the name resolves), the alternate * value (if one is provided), or else the empty string. The result is * passed through "string->c-name!". =*/ SCM ag_scm_get_c_name(SCM agName) { return ag_scm_string_to_c_name_x( ag_scm_get(agName, SCM_UNDEFINED)); } /*=gfunc get_up_name * * what: get upper cased named value, mapped to C name syntax * * exparg: ag-name, name of AutoGen value * * doc: * * Get the first string value associated with the name. It will either * return the associated string value (if the name resolves), the alternate * value (if one is provided), or else the empty string. The result is * passed through "string->c-name!" and "string->up-case!". =*/ SCM ag_scm_get_up_name(SCM agName) { return ag_scm_string_upcase_x(ag_scm_get_c_name(agName)); } /*=gfunc get_down_name * * what: get lower cased named value, mapped to C name syntax * * exparg: ag-name, name of AutoGen value * * doc: * * Get the first string value associated with the name. It will either * return the associated string value (if the name resolves), the alternate * value (if one is provided), or else the empty string. The result is * passed through "string->c-name!" and "string->down-case!". =*/ SCM ag_scm_get_down_name(SCM agName) { return ag_scm_string_downcase_x(ag_scm_get_c_name(agName)); } /*=gfunc high_lim * * what: get highest value index * * exparg: ag-name, name of AutoGen value * * doc: * * Returns the highest index associated with an array of definitions. * This is generally, but not necessarily, one less than the * @code{count} value. (The indexes may be specified, rendering a * non-zero based or sparse array of values.) * * This is very useful for specifying the size of a zero-based array * of values where not all values are present. For example: * * @example * tMyStruct myVals[ [+ (+ 1 (high-lim "my-val-list")) +] ]; * @end example =*/ SCM ag_scm_high_lim(SCM obj) { def_ent_t* pE; bool isIndexed; pE = find_def_ent(ag_scm2zchars(obj, "ag value"), &isIndexed); /* * IF we did not find the entry we are looking for * THEN return zero * ELSE search the twin list for the high entry */ if (pE == NULL) return AG_SCM_INT2SCM(0); if (isIndexed) return AG_SCM_INT2SCM((int)pE->de_index); if (pE->de_etwin != NULL) pE = pE->de_etwin; return AG_SCM_INT2SCM((int)pE->de_index); } /*=gfunc len * * what: get count of values * * exparg: ag-name, name of AutoGen value * * doc: If the named object is a group definition, then "len" is * the same as "count". Otherwise, if it is one or more text * definitions, then it is the sum of their string lengths. * If it is a single text definition, then it is equivalent to * @code{(string-length (get "ag-name"))}. =*/ SCM ag_scm_len(SCM obj) { int len = entry_length(ag_scm2zchars(obj, "ag value")); return AG_SCM_INT2SCM(len); } /*=gfunc low_lim * * what: get lowest value index * * exparg: ag-name, name of AutoGen value * * doc: Returns the lowest index associated with an array of definitions. =*/ SCM ag_scm_low_lim(SCM obj) { def_ent_t* pE; bool x; pE = find_def_ent(ag_scm2zchars(obj, "ag value"), &x); /* * IF we did not find the entry we are looking for * THEN return zero * ELSE we have the low index. */ if (pE == NULL) return AG_SCM_INT2SCM(0); return AG_SCM_INT2SCM((int)pE->de_index); } /*=gfunc set_option * * what: Set a command line option * * exparg: opt, AutoGen option name + its argument * * doc: The text argument must be an option name followed by any needed * option argument. Returns SCM_UNDEFINED. =*/ SCM ag_scm_set_option(SCM opt) { optionLoadLine(&autogenOptions, ag_scm2zchars(opt, "opt + arg")); return SCM_UNDEFINED; } /*=gfunc suffix * * what: get the current suffix * * doc: * Returns the current active suffix (@pxref{pseudo macro}). =*/ SCM ag_scm_suffix(void) { return AG_SCM_STR02SCM((char*)curr_sfx); } /*=gfunc tpl_file * * what: get the template file name * * exparg: full_path, include full path to file, optonal * * doc: Returns the name of the current template file. * If @code{#t} is passed in as an argument, then the template * file is hunted for in the template search path. Otherwise, * just the unadorned name. =*/ SCM ag_scm_tpl_file(SCM full) { if (AG_SCM_BOOL_P(full) && AG_SCM_NFALSEP(full)) { static char const * const sfx[] = { TPL_FILE_TPL, NULL }; char z[AG_PATH_MAX]; if (SUCCESSFUL(find_file(tpl_fname, z, sfx, NULL))) return AG_SCM_STR02SCM(z); } return AG_SCM_STR02SCM((char*)(void*)tpl_fname); } /** * guts of the template file/line functions */ static SCM do_tpl_file_line(int line_delta, char const * fmt) { void * args[2] = { [0] = (void*)current_tpl->td_file, [1] = (void*)((long)cur_macro->md_line + line_delta) }; char * buf = strrchr(args[0], DIRCH); if (buf != NULL) args[0] = buf + 1; { size_t sz = strlen(fmt) + strlen(args[0]) + 24; buf = ag_scribble(sz); } sprintfv(buf, fmt, (snv_constpointer*)args); return AG_SCM_STR02SCM(buf); } /*=gfunc tpl_file_line * * what: get the template file+line number * * exparg: msg-fmt, formatting for line message, optional * * doc: * Returns the file and line number of the current template macro using * either the default format, "from %s line %d", or else the format you * supply. For example, if you want to insert a "C" language file-line * directive, you would supply the format "# %2$d \"%1$s\"", but that * is also already supplied with the scheme variable * @xref{SCM c-file-line-fmt}. You may use it thus: * @example * (tpl-file-line c-file-line-fmt) * @end example * * It is also safe to use the formatting string, "%2$d". AutoGen uses * an argument vector version of printf: @xref{snprintfv}, * and it does not need to know the types of each argument in order to * skip forward to the second argument. =*/ SCM ag_scm_tpl_file_line(SCM fmt) { char const * pzFmt = TPL_FILE_LINE_FMT; if (AG_SCM_STRING_P(fmt)) pzFmt = ag_scm2zchars(fmt, "f/l fmt"); return do_tpl_file_line(0, pzFmt); } /*=gfunc tpl_file_next_line * * what: get the template file plus next line number * * exparg: msg-fmt, formatting for line message, optional * * doc: * This is almost the same as @xref{SCM tpl-file-line}, except that * the line referenced is the next line, per C compiler conventions, and * consequently defaults to the format: # "" =*/ SCM ag_scm_tpl_file_next_line(SCM fmt) { char const * pzFmt = TPL_FILE_NEXT_LINE_FMT; if (AG_SCM_STRING_P(fmt)) pzFmt = ag_scm2zchars(fmt, "f/l fmt"); return do_tpl_file_line(1, pzFmt); } /*=gfunc def_file_line * * what: get a definition file+line number * * exparg: ag-name, name of AutoGen value * exparg: msg-fmt, formatting for line message, optional * * doc: * Returns the file and line number of a AutoGen defined value, using * either the default format, "from %s line %d", or else the format you * supply. For example, if you want to insert a "C" language file-line * directive, you would supply the format "# %2$d \"%1$s\"", but that * is also already supplied with the scheme variable * @xref{SCM c-file-line-fmt}. You may use it thus: * * @example * (def-file-line "ag-def-name" c-file-line-fmt) * @end example * * It is also safe to use the formatting string, "%2$d". AutoGen uses * an argument vector version of printf: @xref{snprintfv}. =*/ SCM ag_scm_def_file_line(SCM obj, SCM fmt) { char const * pzFmt = DEF_FILE_LINE_FMT; char * buf; bool x; def_ent_t * pE = find_def_ent(ag_scm2zchars(obj, "ag value"), &x); /* * IF we did not find the entry we are looking for * THEN return UNDEFINED */ if (pE == NULL) return SCM_UNDEFINED; if (AG_SCM_STRING_P(fmt)) pzFmt = ag_scm2zchars(fmt, "f/l fmt"); { void * args[2] = { (void*)pE->de_file, (void*)(long)pE->de_line }; size_t maxlen; buf = strrchr(args[0], DIRCH); if (buf != NULL) args[0] = buf + 1; maxlen = strlen(args[0]) + strlen(pzFmt) + LOG10_2to32 + 1; buf = ag_scribble(maxlen); sprintfv(buf, pzFmt, (snv_constpointer*)args); } return AG_SCM_STR02SCM(buf); } /* * Local Variables: * mode: C * c-file-style: "stroustrup" * indent-tabs-mode: nil * End: * end of agen5/expState.c */