summaryrefslogtreecommitdiff
path: root/agen5/functions.c
diff options
context:
space:
mode:
Diffstat (limited to 'agen5/functions.c')
-rw-r--r--agen5/functions.c539
1 files changed, 539 insertions, 0 deletions
diff --git a/agen5/functions.c b/agen5/functions.c
new file mode 100644
index 0000000..963bd06
--- /dev/null
+++ b/agen5/functions.c
@@ -0,0 +1,539 @@
+
+/**
+ * @file functions.c
+ *
+ * Time-stamp: "2012-06-10 11:24:45 bkorb"
+ *
+ * This module implements text functions.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*=macfunc CONTINUE
+ *
+ * handler-proc: Break
+ * load-proc: Leave
+ * what: Skip to end of a FOR or WHILE macro.
+ *
+ * desc:
+ * This will skip the remainder of the loop and start the next.
+=*/
+
+/*=macfunc BREAK
+ *
+ * handler-proc: Break
+ * load-proc: Leave
+ * what: Leave a FOR or WHILE macro
+ *
+ * desc:
+ * This will unwind the loop context and resume after ENDFOR/ENDWHILE.
+ * Note that unless this happens to be the last iteration anyway,
+ * the (last-for?) function will never yield "#t".
+=*/
+macro_t *
+mFunc_Break(templ_t * tpl, macro_t * mac)
+{
+ for_state_t * fst = curr_ivk_info->ii_for_data;
+ int code = (mac->md_code == FTYP_BREAK) ? LOOP_JMP_BREAK : LOOP_JMP_NEXT;
+ if (fst == NULL) {
+ char const * which =
+ (mac->md_code == FTYP_BREAK) ? BREAK_STR : CONTINUE_STR;
+ AG_ABEND(aprf(BAD_BREAK_FMT, which));
+ }
+ fst += curr_ivk_info->ii_for_depth - 1;
+
+ (void)tpl;
+ (void)mac;
+ longjmp(fst->for_env, code);
+}
+
+/**
+ * wrapper function for calling gen_block in a loop.
+ * It sets up and handles the jump buffer, returning the jump result.
+ *
+ * @param[in,out] jbuf the jump buffer
+ * @param[in] tpl the new active template
+ * @param[in] mac the looping macro
+ * @param[in] end_mac pointer to the first macro after the block
+ *
+ * @returns either LOOP_JMP_OKAY (0) or LOOP_JMP_BREAK (the caller should
+ * exit the loop).
+ */
+LOCAL loop_jmp_type_t
+call_gen_block(jmp_buf jbuf, templ_t * tpl, macro_t * mac, macro_t * end_mac)
+{
+ switch (setjmp(jbuf)) {
+ case LOOP_JMP_OKAY: // 0
+ gen_block(tpl, mac, end_mac);
+ /* FALLTHROUGH */
+
+ case LOOP_JMP_NEXT:
+ return LOOP_JMP_OKAY;
+
+ case LOOP_JMP_BREAK:
+ default:
+ return LOOP_JMP_BREAK;
+ }
+}
+
+/*=macfunc RETURN
+ *
+ * handler-proc:
+ * load-proc: Leave
+ *
+ * what: Leave an INVOKE-d (DEFINE) macro
+ *
+ * desc:
+ * This will unwind looping constructs inside of a DEFINE-d macro and
+ * return to the invocation point. The output files and diversions
+ * @i{are left alone}. This means it is unwise to start diversions
+ * in a DEFINEd macro and RETURN from it before you have handled the
+ * diversion. Unless you are careful. Here is some rope for you.
+ * Please be careful using it.
+=*/
+macro_t *
+mFunc_Return(templ_t * tpl, macro_t * mac)
+{
+ (void)tpl;
+ (void)mac;
+ free_for_context(true);
+ if (curr_ivk_info->ii_prev == NULL)
+ AG_ABEND_IN(tpl, mac, RETURN_FROM_NOWHERE);
+ longjmp(curr_ivk_info->ii_env, 1);
+}
+
+/**
+ * Generate a block with a new template context. It may be either
+ * an @code{INCLUDE}-d template or a user @code{DEFINE}-d macro.
+ * If @code{gen_block} returns with a long jump, the long jump value
+ * is ignored. It was terminated early with a @code{RETURN}.
+ *
+ * @param[in] tpl new template block (included or invoked).
+ */
+LOCAL void
+gen_new_block(templ_t * tpl)
+{
+ templ_t * oldt = current_tpl;
+ ivk_info_t ii = IVK_INFO_INITIALIZER(curr_ivk_info);
+
+ curr_ivk_info = &ii;
+
+ if (setjmp(ii.ii_env) == 0) {
+ macro_t * m = tpl->td_macros;
+ gen_block(tpl, m, m + tpl->td_mac_ct);
+ }
+
+ current_tpl = oldt;
+ curr_ivk_info = ii.ii_prev;
+}
+
+/**
+ * Validate the context for leaving early. @code{FOR} and @code{WHILE} loops
+ * may leave an interation early with @code{CONTINUE} or @code{BREAK}.
+ * @code{DEFINE}-d macros and @code{INCLUDE}-d files may leave early with
+ * @code{RETURN}. Loops may not be left early from an @code{INVOKE}-d macro
+ * or an @code{INCLUDE}-d template.
+ *
+ * This load function handles @code{BREAK}, @code{CONTINUE} and @code{RETURN}.
+ * It is always defined, so it must check for itself whether the
+ * context is correct or not.
+ *
+ * @param tpl template being loaded
+ * @param mac the macro descriptor
+ * @param p_scan the input text scanning pointer
+ *
+ * @returns the macro table entry after mac
+ */
+LOCAL macro_t *
+mLoad_Leave(templ_t * tpl, macro_t * mac, char const ** p_scan)
+{
+ (void) tpl;
+
+ if (mac->md_code == FTYP_RETURN) {
+ /*
+ * Check returns at load time. "break" and "continue" at
+ * instantiation time.
+ */
+ if (! defining_macro && (include_depth == 0))
+ (void)mLoad_Bogus(tpl, mac, p_scan);
+ }
+ return mac + 1;
+}
+
+/*=macfunc INCLUDE
+ *
+ * what: Read in and emit a template block
+ * handler_proc:
+ * load_proc: Expr
+ *
+ * desc:
+ *
+ * The entire contents of the named file is inserted at this point.
+ * The contents of the file are processed for macro expansion. The
+ * arguments are eval-ed, so you may compute the name of the file to
+ * be included. The included file must not contain any incomplete
+ * function blocks. Function blocks are template text beginning with
+ * any of the macro functions @samp{CASE}, @samp{DEFINE}, @samp{FOR},
+ * @samp{IF} and @samp{WHILE}; extending through their respective
+ * terminating macro functions.
+=*/
+macro_t *
+mFunc_Include(templ_t * tpl, macro_t * mac)
+{
+ bool allocated_name;
+ char const * fname = eval_mac_expr(&allocated_name);
+
+ include_depth++;
+ if (*fname != NUL) {
+ templ_t * new_tpl = tpl_load(fname, tpl->td_file);
+ macro_t * last_mac = new_tpl->td_macros + (new_tpl->td_mac_ct - 1);
+
+ if (last_mac->md_code == FTYP_TEXT) {
+ /*
+ * Strip off trailing white space from included templates
+ */
+ char * pz = new_tpl->td_text + last_mac->md_txt_off;
+ char * pe = SPN_WHITESPACE_BACK(pz, pz);
+
+ /*
+ * IF there is no text left, remove the macro entirely
+ */
+ if (pe > pz) {
+ *pe = NUL;
+ } else {
+ new_tpl->td_mac_ct--;
+ }
+ }
+
+ if (OPT_VALUE_TRACE > TRACE_DEBUG_MESSAGE) {
+ fprintf(trace_fp, TRACE_FN_INC_TPL, new_tpl->td_file);
+ if (OPT_VALUE_TRACE == TRACE_EVERYTHING)
+ fprintf(trace_fp, TRACE_FN_INC_LINE, current_tpl->td_file,
+ mac->md_line);
+ }
+
+ gen_new_block(new_tpl);
+ tpl_unload(new_tpl);
+ current_tpl = tpl;
+ }
+ include_depth--;
+
+ if (allocated_name)
+ AGFREE((void*)fname);
+
+ return mac + 1;
+}
+
+/*=macfunc UNKNOWN
+ *
+ * what: Either a user macro or a value name.
+ * handler_proc:
+ * load_proc:
+ * unnamed:
+ *
+ * desc:
+ *
+ * The macro text has started with a name not known to AutoGen. If, at run
+ * time, it turns out to be the name of a defined macro, then that macro is
+ * invoked. If it is not, then it is a conditional expression that is
+ * evaluated only if the name is defined at the time the macro is invoked.
+ *
+ * You may not specify @code{UNKNOWN} explicitly.
+=*/
+macro_t *
+mFunc_Unknown(templ_t * pT, macro_t * pMac)
+{
+ templ_t * pInv = find_tpl(pT->td_text + pMac->md_name_off);
+ if (pInv != NULL) {
+ if (OPT_VALUE_TRACE >= TRACE_EVERYTHING)
+ fprintf(trace_fp, TRACE_FN_REMAPPED, TRACE_FN_REMAP_INVOKE,
+ pMac->md_code, pT->td_file, pMac->md_line);
+ pMac->md_code = FTYP_DEFINE;
+ pMac->md_pvt = (void*)pInv;
+ parse_mac_args(pT, pMac);
+ return mFunc_Define(pT, pMac);
+ }
+
+ if (OPT_VALUE_TRACE >= TRACE_EVERYTHING) {
+ fprintf(trace_fp, TRACE_FN_REMAPPED, TRACE_FN_REMAP_EXPR,
+ pMac->md_code, pT->td_file, pMac->md_line);
+ fprintf(trace_fp, TRACE_FN_REMAP_BASE,
+ pT->td_text + pMac->md_name_off);
+ }
+
+ pMac->md_code = FTYP_EXPR;
+ if (pMac->md_txt_off == 0) {
+ pMac->md_res = EMIT_VALUE;
+
+ } else {
+ char* pzExpr = pT->td_text + pMac->md_txt_off;
+ switch (*pzExpr) {
+ case ';':
+ case '(':
+ pMac->md_res = EMIT_EXPRESSION;
+ break;
+
+ case '`':
+ pMac->md_res = EMIT_SHELL;
+ span_quote(pzExpr);
+ break;
+
+ case '"':
+ case '\'':
+ span_quote(pzExpr);
+ /* FALLTHROUGH */
+
+ default:
+ pMac->md_res = EMIT_STRING;
+ }
+
+ if (OPT_VALUE_TRACE >= TRACE_EVERYTHING)
+ fprintf(trace_fp, TRACE_UNKNOWN_FMT, pMac->md_res, pzExpr);
+ }
+
+ return mFunc_Expr(pT, pMac);
+}
+
+
+/*=macfunc BOGUS
+ *
+ * what: Out-of-context or unknown functions are bogus.
+ * handler_proc:
+ * load_proc:
+ * unnamed:
+=*/
+macro_t*
+mFunc_Bogus(templ_t* pT, macro_t* pMac)
+{
+ char * pz = aprf(FN_BOGUS_FMT, pMac->md_code,
+ (pMac->md_code < FUNC_CT)
+ ? ag_fun_names[ pMac->md_code ]
+ : FN_BOGUS_HUH);
+
+ AG_ABEND_IN(pT, pMac, pz);
+ /* NOTREACHED */
+ return pMac;
+}
+
+
+/*=macfunc TEXT
+ *
+ * what: A block of text to be emitted.
+ * handler_proc:
+ * unnamed:
+=*/
+macro_t*
+mFunc_Text(templ_t* pT, macro_t* pMac)
+{
+ fputs(pT->td_text + pMac->md_txt_off, cur_fpstack->stk_fp);
+ fflush(cur_fpstack->stk_fp);
+ return pMac + 1;
+}
+
+
+/*=macfunc COMMENT
+ *
+ * what: A block of comment to be ignored
+ * load_proc:
+ * alias: "#"
+ *
+ * desc:
+ * This function can be specified by the user, but there will
+ * never be a situation where it will be invoked at emit time.
+ * The macro is actually removed from the internal representation.
+ *
+ * If the native macro name code is @code{#}, then the
+ * entire macro function is treated as a comment and ignored.
+ *
+ * @example
+ * [+ # say what you want, but no '+' before any ']' chars +]
+ * @end example
+=*/
+macro_t *
+mLoad_Comment(templ_t * tpl, macro_t * mac, char const ** p_scan)
+{
+ (void)tpl;
+ (void)p_scan;
+ memset((void*)mac, 0, sizeof(*mac));
+ return mac;
+}
+
+/**
+ * The default (unknown) load function.
+ *
+ * Move any text into the text offset field. This macro will change to
+ * either INVOKE or an expression function, depending on whether or not a
+ * DEFINE macro corresponds to the name. This is determined at instantiation
+ * time. This is used as the default load mechanism.
+ *
+ * @param tpl template being loaded
+ * @param mac the macro descriptor
+ * @param p_scan the input text scanning pointer
+ *
+ * @returns the macro table entry after mac
+ */
+macro_t *
+mLoad_Unknown(templ_t * tpl, macro_t * mac, char const ** unused)
+{
+ char const * scan;
+ ssize_t src_len = (size_t)mac->md_res; /* macro len */
+ (void)unused;
+
+ if (src_len <= 0)
+ goto return_emtpy_expr;
+
+ scan = (char const*)mac->md_txt_off; /* macro text */
+
+ switch (*scan) {
+ case ';':
+ {
+ char const * start = scan;
+
+ /*
+ * Strip off scheme comments
+ */
+ do {
+ scan = strchr(scan, NL);
+ if (scan == NULL)
+ goto return_emtpy_expr;
+ scan = SPN_WHITESPACE_CHARS(scan);
+ if (*scan == NUL)
+ goto return_emtpy_expr;
+ } while (*scan == ';');
+ src_len -= scan - start;
+ break;
+ }
+
+ case '[':
+ case '.':
+ {
+ /*
+ * We are going to recopy the definition name, this time as a
+ * canonical name (i.e. including '[', ']' and '.' characters,
+ * but with all blanks squeezed out)
+ */
+ char * cname = tpl->td_text + mac->md_name_off;
+ size_t cname_len = strlen(cname);
+
+ /*
+ * Move back the source pointer. We may have skipped blanks,
+ * so skip over however many first, then back up over the name.
+ * We have found a name, so we won't back up past the start.
+ */
+ while (IS_WHITESPACE_CHAR(scan[-1])) scan--, src_len++;
+ scan -= cname_len;
+ src_len += cname_len;
+
+ /*
+ * Now copy over the full canonical name. Check for errors.
+ * Advance the scan pointer to just past the name we've copied.
+ */
+ {
+ size_t rem_len = canonical_name(cname, scan, (int)src_len);
+ if (rem_len > src_len)
+ AG_ABEND_IN(tpl, mac, LD_UNKNOWN_INVAL_DEF);
+
+ scan += src_len - rem_len;
+ src_len = rem_len;
+ }
+
+ /*
+ * Where we are stashing text ("td_scan") gets set to just past the
+ * NUL byte terminating the name. "cname" is now longer than before.
+ */
+ tpl->td_scan = cname + strlen(cname) + 1;
+ if (src_len <= 0)
+ goto return_emtpy_expr;
+ break;
+ }
+ }
+
+ /*
+ * Copy the expression (the remaining text)
+ */
+ {
+ char * dest = tpl->td_scan; /* next text dest */
+ mac->md_txt_off = (dest - tpl->td_text);
+ mac->md_res = 0;
+ memcpy(dest, scan, src_len);
+ dest += src_len;
+ *(dest++) = NUL;
+ *dest = NUL; /* double terminate */
+ tpl->td_scan = dest;
+ }
+
+ return mac + 1;
+
+ return_emtpy_expr:
+ mac->md_txt_off = 0;
+ mac->md_res = 0;
+ return mac + 1;
+}
+
+
+/**
+ * Some functions are known to AutoGen, but invalid out of context.
+ * For example, ELIF, ELSE and ENDIF are all known to AutoGen.
+ * However, the load function pointer for those functions points
+ * here, until an "IF" function is encountered.
+ *
+ * @param tpl template being loaded
+ * @param mac the macro descriptor
+ * @param p_scan the input text scanning pointer
+ *
+ * @returns the macro table entry after mac
+ */
+macro_t *
+mLoad_Bogus(templ_t * tpl, macro_t * mac, char const ** p_scan)
+{
+ char const * pzSrc = (char const*)mac->md_txt_off; /* macro text */
+ char const * pzMac;
+
+ char z[ 64 ];
+ (void)p_scan;
+
+ if (pzSrc != NULL) {
+ z[0] = ':';
+ z[1] = z[2] = ' ';
+ strncpy(z+3, pzSrc, (size_t)60);
+ z[63] = NUL;
+ pzSrc = z;
+ }
+ else
+ pzSrc = zNil;
+
+ {
+ int ix = mac->md_code;
+ if ((unsigned)ix >= FUNC_CT)
+ ix = 0;
+
+ pzMac = ag_fun_names[ ix ];
+ }
+
+ pzSrc = aprf(LD_BOGUS_UNKNOWN, tpl->td_file, mac->md_line, pzMac, pzSrc);
+
+ AG_ABEND_IN(tpl, mac, pzSrc);
+ /* NOTREACHED */
+ return NULL;
+}
+/*
+ * Local Variables:
+ * mode: C
+ * c-file-style: "stroustrup"
+ * indent-tabs-mode: nil
+ * End:
+ * end of agen5/functions.c */