diff options
Diffstat (limited to 'agen5/expMake.c')
-rw-r--r-- | agen5/expMake.c | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/agen5/expMake.c b/agen5/expMake.c new file mode 100644 index 0000000..926b930 --- /dev/null +++ b/agen5/expMake.c @@ -0,0 +1,429 @@ + +/** + * @file expMake.c + * + * Time-stamp: "2012-03-04 13:54:13 bkorb" + * + * This module implements Makefile construction 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/>. + */ + +/** + * Figure out how to handle the line continuation. + * If the line we just finished ends with a backslash, we're done. + * Just add the newline character. If it ends with a semi-colon or a + * doubled amphersand or doubled or-bar, then escape the newline with a + * backslash. If the line ends with one of the keywords "then", "in" or + * "else", also only add the escaped newline. Otherwise, add a + * semi-colon, backslash and newline. + * + * @param ppzi pointer to pointer to input text + * @param ppzo pointer to pointer to output text + * @param tabch line prefix (tab) character + * @param bol pointer to start of currently-being-output line + * + * @returns false to say the newline is dropped becase we're done + * true to say the line was appended with the newline. + */ +static bool +handle_eol(char ** ppzi, char ** ppzo, char tabch, char * bol) +{ + char * pzScn = *ppzi; + char * pzOut = *ppzo; + int l_len = pzOut - bol; + + /* + * Backup past trailing white space (other than newline). + */ + while (IS_NON_NL_WHITE_CHAR(pzOut[-1])) + pzOut--; + + /* + * Skip over empty lines, but leave leading white space + * on the next non-empty line. + */ + { + char* pz = pzScn; + while (IS_WHITESPACE_CHAR(*pz)) { + if (*(pz++) == NL) + pzScn = pz; + } + } + + /* + * The final newline is dropped. + */ + if (*pzScn == NUL) + return false; + + switch (pzOut[-1]) { + case '\\': + /* + * The newline is already escaped, so don't + * insert our extra command termination. + */ + *(pzOut++) = NL; + break; + + case '&': + /* + * A single ampersand is a backgrounded command. We must terminate + * those statements, but not statements conjoined with '&&'. + */ + if ('&' != pzOut[-2]) + goto append_statement_end; + /* FALLTHROUGH */ + + case '|': + case ';': + skip_semi_colon: + /* + * Whatever the reason for a final '|', '&' or ';', + * we will not add a semi-colon after it. + */ + memcpy(pzOut, MAKE_SCRIPT_NL + 2, MAKE_SCRIPT_NL_LEN - 2); + pzOut += MAKE_SCRIPT_NL_LEN - 2; + break; + + case 'n': // "then" or "in" + if (l_len < 3) + goto append_statement_end; + + if (pzOut[-2] == 'i') { + if ((l_len == 3) || IS_WHITESPACE_CHAR(pzOut[-3])) + goto skip_semi_colon; + goto append_statement_end; + } + + if ( (l_len < 5) + || ( (l_len > 5) + && ! IS_WHITESPACE_CHAR(pzOut[-5]) )) + goto append_statement_end; + if (strncmp(pzOut-4, HANDLE_EOL__THE, HANDLE_EOL__THE_LEN) == 0) + goto skip_semi_colon; + goto append_statement_end; + + case 'e': // "else" + if ( (l_len < 5) + || ( (l_len > 5) + && ! IS_WHITESPACE_CHAR(pzOut[-5]) )) + goto append_statement_end; + if (strncmp(pzOut-4, HANDLE_EOL__ELS, HANDLE_EOL__ELS_LEN) == 0) + goto skip_semi_colon; + goto append_statement_end; + + default: + append_statement_end: + /* + * Terminate the current command and escape the newline. + */ + memcpy(pzOut, MAKE_SCRIPT_NL, MAKE_SCRIPT_NL_LEN); + pzOut += MAKE_SCRIPT_NL_LEN; + } + + /* + * We have now started our next output line and there are still data. + * Indent with a tab, if called for. If we do insert a tab, then skip + * leading tabs on the line. + */ + if (tabch) { + *(pzOut++) = tabch; + while (*pzScn == tabch) pzScn++; + } + + *ppzi = pzScn; + *ppzo = pzOut; + return true; +} + +/** + * Pass through untreated sedable lines. Sometimes it is just very useful + * to post-process Makefile files with sed(1) to clean it up. + * + * @param txt pointer to text. We skip initial white space. + * @param tab pointer to where we stash the tab character to use + * @returns true to say this was a sed line and was emitted, + * false to say it was not and needs to be copied out. + */ +static bool +handle_sed_expr(char ** src_p, char ** out_p) +{ + char * src = *src_p; + char * out = *out_p; + + switch (src[1]) { + case 'i': + if (strncmp(src+2, HANDLE_SED_IFNDEF, HANDLE_SED_IFNDEF_LEN) == 0) + break; + if (strncmp(src+2, HANDLE_SED_IFDEF, HANDLE_SED_IFDEF_LEN) == 0) + break; + return false; + + case 'e': + if (strncmp(src+2, HANDLE_SED_ELSE, HANDLE_SED_ELSE_LEN) == 0) + break; + if (strncmp(src+2, HANDLE_SED_ENDIF, HANDLE_SED_ENDIF_LEN) == 0) + break; + /* FALLTHROUGH */ + default: + return false; + } + + { + char * p = BRK_NEWLINE_CHARS(src); + size_t l; + if (*p == NL) /* do not skip NUL */ + p++; + l = p - src; + memcpy(out, src, l); + *src_p = src + l; + *out_p = out + l; + } + + return true; +} + +/** + * Compute a maximal size for the output script. Leading and trailing white + * space are trimmed. Dollar characters will likely be doubled and newlines + * may get as many as MAKE_SCRIPT_NL_LEN characters inserted. Make sure + * there's space. + * + * @param txt pointer to text. We skip initial white space. + * @param tab pointer to where we stash the tab character to use + * @returns the maximum number of bytes required to store result. + */ +static size_t +script_size(char ** txt_p, char * tab) +{ + char * txt = *txt_p; + char * ptxte; + size_t sz = 0; + + /* + * skip all blank lines and other initial white space + * in the source string. + */ + if (! IS_WHITESPACE_CHAR(*txt)) + *tab = TAB; + else { + txt = SPN_WHITESPACE_CHARS(txt + 1); + *tab = (txt[-1] == TAB) ? NUL : TAB; + } + + /* + * Do nothing with empty input. + */ + if (*txt == NUL) + return 0; + + /* + * "txt" is now our starting point. Do not modify it any more. + */ + *txt_p = txt; + + for (ptxte = txt - 1;;) { + ptxte = BRK_MAKE_SCRIPT_CHARS(ptxte+1); + if (*ptxte == NUL) + break; + sz += (*ptxte == '$') ? 1 : MAKE_SCRIPT_NL_LEN; + } + + ptxte = SPN_WHITESPACE_BACK(txt, ptxte); + *ptxte = NUL; + sz += (ptxte - txt); + return sz; +} + +/*=gfunc makefile_script + * + * what: create makefile script + * general_use: + * + * exparg: text, the text of the script + * + * doc: + * This function will take ordinary shell script text and reformat it + * so that it will work properly inside of a makefile shell script. + * Not every shell construct can be supported; the intent is to have + * most ordinary scripts work without much, if any, alteration. + * + * The following transformations are performed on the source text: + * + * @enumerate + * @item + * Trailing whitespace on each line is stripped. + * + * @item + * Except for the last line, the string, " ; \\" is appended to the end of + * every line that does not end with certain special characters or keywords. + * Note that this will mutilate multi-line quoted strings, but @command{make} + * renders it impossible to use multi-line constructs anyway. + * + * @item + * If the line ends with a backslash, it is left alone. + * + * @item + * If the line ends with a semi-colon, conjunction operator, pipe (vertical + * bar) or one of the keywords "then", "else" or "in", then a space and a + * backslash is added, but no semi-colon. + * + * @item + * The dollar sign character is doubled, unless it immediately precedes an + * opening parenthesis or the single character make macros '*', '<', '@@', + * '?' or '%'. Other single character make macros that do not have enclosing + * parentheses will fail. For shell usage of the "$@@", "$?" and "$*" + * macros, you must enclose them with curly braces, e.g., "$@{?@}". + * The ksh construct @code{$(<command>)} will not work. Though some + * @command{make}s accept @code{$@{var@}} constructs, this function will + * assume it is for shell interpretation and double the dollar character. + * You must use @code{$(var)} for all @command{make} substitutions. + * + * @item + * Double dollar signs are replaced by four before the next character + * is examined. + * + * @item + * Every line is prefixed with a tab, unless the first line + * already starts with a tab. + * + * @item + * The newline character on the last line, if present, is suppressed. + * + * @item + * Blank lines are stripped. + * + * @item + * Lines starting with "@@ifdef", "@@ifndef", "@@else" and "@@endif" are + * presumed to be autoconf "sed" expression tags. These lines will be + * emitted as-is, with no tab prefix and no line splicing backslash. + * These lines can then be processed at configure time with + * @code{AC_CONFIG_FILES} sed expressions, similar to: + * + * @example + * sed "/^@@ifdef foo/d;/^@@endif foo/d;/^@@ifndef foo/,/^@@endif foo/d" + * @end example + * @end enumerate + * + * @noindent + * This function is intended to be used approximately as follows: + * + * @example + * $(TARGET) : $(DEPENDENCIES) + * <+ (out-push-new) +> + * ....mostly arbitrary shell script text.... + * <+ (makefile-script (out-pop #t)) +> + * @end example +=*/ +SCM +ag_scm_makefile_script(SCM text_scm) +{ + char * res_str; /*@< result string */ + char * out; /*@< output scanning ptr */ + char * bol; /*@< start of last output line */ + char tabch; /*@< char to use for start-of-line tab */ + + char * text = ag_scm2zchars(text_scm, "make script"); + size_t sz = script_size(&text, &tabch); + + if (sz == 0) + return AG_SCM_STR02SCM(zNil); + + bol = out = res_str = ag_scribble(sz); + + /* + * Force the initial line to start with a real tab. + */ + *(out++) = TAB; + + for (;;) { + char * p = BRK_MAKE_SCRIPT_CHARS(text); + size_t l = p - text; + if (l > 0) { + memcpy(out, text, l); + text = p; + out += l; + } + + /* + * "text" now points to one of three characters: + * a newline, a dollar or a NUL byte. + */ + if (*text == NUL) + break; + + if (*text == NL) { + if (! handle_eol(&text, &out, tabch, bol)) + break; + + bol = out; + + /* + * As a special "hack", if a line starts with "@ifdef", "@ifndef", + * "@else" or "@endif", then we assume post processing sed will + * fix it up. Those lines get left alone. + */ + if (*text == '@') { + if (handle_sed_expr(&text, &out)) + bol = out; + } + + } else { + /* + * Quadruple a double dollar, leave alone make-interesting + * dollars, and double it otherwise. + */ + switch (text[1]) { + case '(': case '*': case '@': case '<': case '%': case '?': + /* one only */ + break; + + case '$': + /* + * $$ in the shell means process id. Avoid having to do a + * backward scan on the second '$' by handling the next '$' + * now. We get FOUR '$' chars. + */ + text++; + *(out++) = '$'; + *(out++) = '$'; + *(out++) = '$'; + /* quadruple */ + break; + + default: + *(out++) = '$'; /* double */ + } + + *(out++) = *(text++); + } + } + + { + SCM res = AG_SCM_STR2SCM(res_str, out - res_str); + return res; + } +} + +/* + * Local Variables: + * mode: C + * c-file-style: "stroustrup" + * indent-tabs-mode: nil + * End: + * end of agen5/expMake.c */ |