/*############################################################################## FUNNNELWEB COPYRIGHT ==================== FunnelWeb is a literate-programming macro preprocessor. The FunnelWeb web is at http://www.ross.net/funnelweb/ Copyright (c) Ross N. Williams 1992. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of Version 2 of the GNU General Public License as published by the Free Software Foundation (http://www.gnu.org/). This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See Version 2 of the GNU General Public License for more details. You should have received a copy of Version 2 of the GNU General Public License along with this program. If not, you can obtain a copy as follows: ftp://prep.ai.mit.edu/pub/gnu/COPYING-2.0 or write to: Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA Section 2a of the license requires that all changes to this file be recorded prominently in this file. Please record all changes here. Programmers: RNW Ross N. Williams (ross@ross.net) Changes: 07-May-1992 RNW Program prepared for release under GNU GPL V2. 09-May-1999 RNW Modified to cater for multiple library levels. ##############################################################################*/ /******************************************************************************/ /* ANALYSE.C */ /******************************************************************************/ #include "style.h" #include "analyse.h" #include "as.h" #include "data.h" #include "lister.h" #include "misc.h" #include "table.h" /******************************************************************************/ LOCAL void chk_mac P_((p_ma_t)); LOCAL void chk_mac (p_ma) p_ma_t p_ma; { mc_t *call; p_md_t p_md; /* Deal with undefined macros by flagging the first invocation. */ if (!maisdef(p_ma)) { ls_loo(p_ma->ma_calls,(ulong) 1,PPV &call); as_cold(call!=NULL,"check_macro: No instances of \"undefined\" macro!"); lr_err(&call->mc_ps,"Call to undefined macro."); return; } p_md = &p_ma->ma_defn[p_ma->ma_minlev]; /* Assert: The macro has a definition. */ /* Check that all the calls to the macro have the correct number of params. */ ls_fir(p_ma->ma_calls); ls_nxt(p_ma->ma_calls,PPV &call); while (call!=NULL) { if (call->mc_npar != p_md->md_npar) { sprintf(linet1,"Call has %lu parameter%s, but the macro", (ulong) call->mc_npar, call->mc_npar==1 ? "" : "s"); lr_err(&(call->mc_ps),linet1); sprintf(linet1,"definition at line %lu specifies %lu parameter%s.", (ulong) p_md->md_ps.ps_line, (ulong) p_md->md_npar, p_md->md_npar==1 ? "" : "s"); lr_mes(&(call->mc_ps),linet1); if (p_md->md_npar==0 && call->mc_npar==1) { lr_mes(&(call->mc_ps),"Note: If your call looks like this: @@(@) then you"); lr_mes(&(call->mc_ps),"should be aware that FunnelWeb treats this as a call with"); lr_mes(&(call->mc_ps),"a single parameter consisting of the empty string. This"); lr_mes(&(call->mc_ps),"is a consequence of parameter delimiters @\"@\" being optional."); } } ls_nxt(p_ma->ma_calls,PPV &call); } /* Check that the number of calls of the macro is legal. */ if (p_md->md_isfil) { /* Flag all calls to a file macro as errors. */ if (ls_len(p_ma->ma_calls)>0) { lr_mes(&p_md->md_ps, "This file macro is called one or more times."); lr_mes(&p_md->md_ps, "Each call has been flagged with an error message."); } ls_fir(p_ma->ma_calls); ls_nxt(p_ma->ma_calls,PPV &call); while (call!=NULL) { lr_err(&(call->mc_ps),"Calls to file macros are not allowed."); lr_mes(&(call->mc_ps),"Reason: It should be possible to comment out a"); lr_mes(&(call->mc_ps),"file macro without a big fuss. Calling a file macro"); lr_mes(&(call->mc_ps),"just tangles it up in the macro structure,"); lr_mes(&(call->mc_ps),"making it more difficult to comment out later."); ls_nxt(p_ma->ma_calls,PPV &call); } } else { /* Non-file macros. */ if (ls_len(p_ma->ma_calls)==0 && !p_md->md_iszer) lr_err(&p_md->md_ps, "This macro is never used (and has no @Z)."); if (ls_len(p_ma->ma_calls)>1 && !p_md->md_isman) lr_err(&p_md->md_ps, "This macro is used more than once (and has no @M)."); } } /******************************************************************************/ LOCAL void chk_rec P_((void)); LOCAL void chk_rec() /* This macro analyses the calls between the macros in the macro */ /* table and flags all recursive macros with an error message. */ /* */ /* Def: A macro is a "level 0 macro" if it does not call any macros. */ /* Def: A macro is a "level n macro" if it calls at least one macro of level */ /* n-1, but does not call any macros of level n or higher. */ /* Def: A macro is a "level infinity macro" if it directly or indirectly */ /* calls itself. */ /* */ /* This function totally ignores undefined macros. This means that it can be */ /* invoked even if problems are detected by the other checks. */ /* */ /* Although the actions here may seem rather time expensive, they may not */ /* be. The function repeatedly runs through the macro table and each macro's */ /* bodypart list and expression lists. However, there are not likely to be */ /* many macros and each definition expression list is likely to be quite */ /* short, as each macro will be mainly text. */ /* Even if this stuff does take a while, it is better to detect recursion now */ /* than at tangle time! */ { p_ma_t p_ma; /* General purpose pointer to macro. */ name_t dummy_name; /* Dummy name variable acts as a placeholder in calls. */ uword level; /* Level of macro currently being tagged. */ bool gotone; /* TRUE iff current loop iteration tagged >0 macros. */ /* We need a concrete number to stand for level infinity. */ /* 30000 is a nice, large 15 bit number. */ #define LEVINF 30000 /* Make sure that our idea of infinity is big enough. */ as_cold(tb_len(macro_table)ma_level=LEVINF; gotone=TRUE; /* Work from level 0 upwards identifying macros of successive levels. */ for (level=0; levelma_level == LEVINF)) { p_md_t p_md = &p_ma->ma_defn[p_ma->ma_minlev]; p_bpls_t p_bpls = p_md->md_body; /* Run through each body part of the macro. */ ls_fir(p_bpls); while (TRUE) { p_bp_t p_bp; ls_nxt(p_bpls,PPV &p_bp); if (p_bp==NULL) break; /* Run through the list of expression elements. */ ls_fir(p_bp->bp_ex); while (TRUE) { p_el_t p_el; ls_nxt(p_bp->bp_ex,PPV &p_el); if (p_el==NULL) break; /* Don't tag anything that calls anything that isn't tagged. */ if ((p_el->el_kind==EL_INVC ) && (maisdef(p_el->el_p_mac) ) && (p_el->el_p_mac->ma_level==LEVINF)) goto nextmacro; } } /* If we got this far, then our macro must have called nothing but */ /* macros of levels [0,level-1]. Thus, we can tag it level 'level'. */ p_ma->ma_level=level; gotone=TRUE; } /* End if */ nextmacro:; } /* End while */ } /* End for loop. */ /* At this point, we have tagged all macros with their level number. Note */ /* that level infinity macros are discovered simply because they do not get */ /* tagged. The following loop flags all such recursive macros with errors. */ tb_fir(macro_table); while (tb_rea(macro_table,PV dummy_name,PV &p_ma)) if (maisdef(p_ma) && p_ma->ma_level==LEVINF) { p_md_t p_md = &p_ma->ma_defn[p_ma->ma_minlev]; lr_err(&p_md->md_ps,"This macro has an infinite expansion."); } /* Note: The above message is not "This macro is defined recursively", */ /* because this function catches not only recursive macros, but also all the */ /* non-recursive macros that call the recursive macros!! To detect only */ /* the recursive macros, we really ought to use Tarjan's algorithm for the */ /* detection of strongly connected components. However, I don't have the */ /* reference handy right now and I want to finish this version of FunnelWeb */ /* soon, so I'll leave the installation of Tarjan's algorithm as a possible */ /* future enhancement. Then the error message can be changed. */ } /******************************************************************************/ EXPORT void analyse() { name_t dummy_name; p_ma_t p_ma; /* Issue an error if there are no macros. */ if (tb_len(macro_table)==0) { char *m="E: No macros defined."; wl_l(m); if (option.op_s_b) wl_sj(m); num_err++; } /* Issue an error if there are no product (output) files. */ if (tb_len(file_table)==0) { char *m="E: No output files specified."; wl_l(m); if (option.op_s_b) wl_sj(m); num_err++; } /* Take a look at each macro and issue appropriate diagnostics. */ tb_fir(macro_table); while (tb_rea(macro_table,PV dummy_name,PV &p_ma)) chk_mac(p_ma); /* Check for recursive macros. */ chk_rec(); /* Run through the document list and flag any sections that still have not */ /* been given names. */ ls_fir(document_list); while (TRUE) { p_dc_t p_dc; ls_nxt(document_list,PPV &p_dc); if (p_dc==NULL) break; if (p_dc->dc_kind==DC_TYPE && p_dc->dc_pty->ty_kind==TY_NSEC && !p_dc->dc_pty->ty_isnam) { lr_err(&p_dc->dc_ps,"This section has no name."); lr_mes(&p_dc->dc_ps,"You can give it one explicitly (As in @A@) or implicitly by"); lr_mes(&p_dc->dc_ps,"defining a macro (whose name will be inherited) within the section."); } } } /******************************************************************************/ /* End of ANALYSE.C */ /******************************************************************************/