/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ /* * Modified: Sep-88 by Tom Hageman. * - "Symbolic" macro definitions, allow keyboard macro defs in joverc, * editable macro defs; for backward compatibility the old 'binary' * macro files can still be read (and converted to the new symbolic * format) iff this module is compiled with MACROCONVERT #defined. * * - name completion in NameMac for existing macros. * [Feb-89] * compare existing macro with new one before adding it for more * accurate save behaviour. * [Nov-89] * split macro in "static" (Macro) and "dynamic" (Frame) part. * [Jan-90] * trace macro calls on error. * [Jul-90] * complete revision (for 4.14 compatibility). Macro files are now just * like .joverc files and can be loaded using "source". Still keep * special "edit-macros" facility. Remove MACROCONVERT code. * [Jan-91] * "make-macro-interactive" more versatile; argument on recursive * "start-remembering" starts new keyboard macro right away. * fixed bug in special ^Q handling code in pr_mac. * [Jul-91] * cancel bell on caught macro errors; add informative message in * "make-macro-interactive"; add direct key binding on argument in * "define-macro"; add "trace-macros", TRACEMAC code. * [Apr-92] * add IMMEDIATE flag for immediate recursive macro execution in ExecNow. * (see RCS log at end for more...) */ #include "jove.h" RCS("$Id: macros.c,v 14.31.0.12 1994/06/24 17:55:03 tom Exp tom $") #include "ctype.h" #include "io.h" #include "maps.h" #define EXECUTE 0x100 /* executing this macro */ #define SAVE 0x200 /* this macro needs saving to a file */ #define ERRCATCH 0x400 /* catch errors in this macro */ public Frame *MacStkPtr; private Frame AFrame; /* single static frame for faster access */ #define NewFrame() (MacStkPtr ? (Frame *) emalloc(sizeof(Frame)) : &AFrame) #define PopFrame(f) {if (MacStkPtr = (f)->mf_return) free((void_*)(f));} /* * The keyboard macro is the only one that is used for defining. * If we are defining, the variable `Defining' is set. * (I [TRH] think this is cleaner than setting a flag in the keyboard macro) * (SAVE flag is set so keyboard macro always shows up in "Edit Macros") */ Macro KeyMacro = { MACRO|SAVE, "keyboard-macro" }; private unsigned KeyMacBufLen ZERO; int Defining ZERO; Macro *macros = &KeyMacro; /* kbd macro always at head of list */ #define macros (&KeyMacro) /* ...so we can use this instead */ private int Nmacros = 1 + 1; /* i.e., kbd macro + dummy. */ const data_obj *LastCmd; DEF_INT( "trace-macros", TraceMac, V_BOOL ) ZERO; _IF(def PRIVATE) /* * Find a macro by name. * Normally only existing macro names are permitted, but as a special case, * if the `prompt' parameter equals NULL, you can enter a new name. */ data_obj * findmac(prompt) const char *prompt; { static const char **strings ZERO; register Macro *m; { register const char **strs; if (strs = strings) /* release storage when aborted previously. */ free((void_*) strs); strings = strs = (const char **) emalloc(Nmacros * sizeof(char *)); /* build name vector */ { m = macros; } do { *strs++ = m->Name; } while (m = m->m_nextm); *strs = NULL; } { register const char *_prompt; register int com; # define ret_mode com /* alias! */ ret_mode = NOTHING; if ((_prompt = prompt) == NULL) { /* [TRH] this is a kludge */ _prompt = ProcFmt; ret_mode = RET_STATE; } com = complete(strings, ret_mode, _prompt); free((void_*) strings); strings = NULL; if (com < 0) { if (com == ORIGINAL || com == AMBIGUOUS) return NULL; complain(NullStr); } m = macros; while (--com >= 0) m = m->m_nextm; # undef ret_mode /* unalias */ } return (data_obj *) m; } /* * Save a macro. * If the macro already exists, ignore `name' and replace its body. * Otherwise create a brand new macro with the given `name'. * The body's length should be non-zero. */ private Macro *save_mac __(( Macro *_(mac), const char *_(name), const char *_(body), int _(length), int _(save) )); private Macro * save_mac(mac, name, body, length, save) Macro *mac; const char *name, *body; { register Macro *m, *prev; register size_t len = length; if ((m = mac) == NULL) { /* new macro; append it to the end of the list */ for (prev = macros; m = prev->m_nextm; prev = m) { #ifndef TINY /* alphabetic is nicer I think */ if (m->Name[0] < name[0]) continue; if (strcmp(m->Name, name) >= 0) break; #endif } m = (Macro *) emalloc(sizeof(Macro)); m->m_nextm = prev->m_nextm; prev->m_nextm = m; Nmacros++; m->Name = NoName; /* in case allocation fails */ m->m_body = NULL; m->Name = copystr(name); } else { /* existing macro; if no change we are ready now */ if (len == m->m_len && bcmp(body, m->m_body, len) == 0) return m; if (m->m_body) { free(m->m_body); m->m_body = NULL; /* in case allocation fails */ if (m == &KeyMacro) /* needs special care */ KeyMacBufLen = 0; } } byte_copy(body, m->m_body = emalloc(len), len); m->m_len = len; m->Type = MACRO | save; return m; } /* * Delete a macro. */ private void del_mac __(( Macro *_(mac) )); private void del_mac(mac) register Macro *mac; { register Macro *m = macros; register const char *name; if (mac == m) { /* Keyboard macro; just pretend body is empty */ m->m_len = 0; return; } /* skip Keyboard macro which is at the head of the list */ do { if ((m = m->m_nextm) == NULL) complain("?macro?"); /* shouldn't happen! */ } while (m->m_nextm != mac); m->m_nextm = mac->m_nextm; DelBind((data_obj *) mac); if ((name = mac->Name) && name != NoName) free((void_*) name); if (mac->m_body) free(mac->m_body); free((void_*) mac); Nmacros--; } /* * Unwind the macro stack. */ void fix_macros() { register Frame *sp; register Macro *m; register int oldflags; #ifndef TINY register int unwind = 0; #endif if (Interactive) /* allow errors. */ return; #if (IMMEDIATE) oldflags = 0; #endif while (sp = MacStkPtr) { /* unwind the stack */ m = sp->mf_macro; Interactive = sp->mf_interactive; PopFrame(sp); oldflags = m->Type; m->Type &= ~(EXECUTE|ERRCATCH|IMMEDIATE); if (oldflags & (ERRCATCH|IMMEDIATE)) { /* until caught */ unbell(); break; } #ifndef TINY add_mess(!unwind ? " (in macro %s" : ".%s", m->Name); unwind++; #endif if (Interactive) /* or interactive. */ break; } #ifndef TINY if (unwind) add_mess(")"); #endif if (Defining) { updmodline(); Defining = NO; } #if (IMMEDIATE) if (oldflags & IMMEDIATE) Leave(); #endif } /* * Setup a macro for executing |exp| times. (0 means "infinite") * If exp <= 0 errors in this macro are caught, i.e., execution resumes * at its calling macro. */ void do_macro(m) register Macro *m; { register Frame *f; register int num; if (m->Type & EXECUTE) complain("[Attempt to execute macro (%s) recursively!]", m->Name); #ifdef TRACEMAC if (True(TraceMac) && in_macro()) f_mess("{%s -> %s * %d}", MacStkPtr->mf_macro->Name, m->Name, exp); #endif if ((num = exp) <= 0) { if ((num = -num) == 0 && !(exp_p & YES)) return; /* This is so that "make-macro-interactive" can propagate `execute zero times' when prefixed with "ESC +" or "ESC -". If you want to propagate `infinite execution', prefix "C-U". A direct prefix to the macro, i.e., "ESC 0" always executes the macro infinitely. */ m->Type |= ERRCATCH; } f = NewFrame(); f->mf_return = MacStkPtr; MacStkPtr = f; m->Type |= EXECUTE; f->mf_macro = m; f->mf_offset = 0; f->mf_ntimes = num; f->mf_interactive = Interactive; Interactive = NO; } /* * Add a character to the keyboard macro. */ void mac_putc(c) int c; { register int len = KeyMacro.m_len++; register char *body = KeyMacro.m_body; if (len >= KeyMacBufLen) { register size_t buflen = len + 16; if (body) body = realloc(body, buflen); else body = malloc(buflen); if ((KeyMacro.m_body = body) == NULL) { KeyMacBufLen = 0; KeyMacro.m_len = 0; Defining = NO; updmodline(); complain("[Can't allocate storage for keyboard macro]"); } KeyMacBufLen = buflen; } body[len] = c; } /* * get next character from the macro execution stack. */ int mac_getc() { register Frame *sp; register Macro *m; while (sp = MacStkPtr) { m = sp->mf_macro; if (sp->mf_offset != m->m_len) { if (True(TraceMac)) redisplay(); return _UC_(m->m_body[sp->mf_offset++]); } sp->mf_offset = 0; if (--sp->mf_ntimes == 0) { m->Type &= ~(EXECUTE|ERRCATCH); Interactive = sp->mf_interactive; PopFrame(sp); #ifdef TRACEMAC if (True(TraceMac) && in_macro()) f_mess("{%s <- %s}", MacStkPtr->mf_macro->Name, m->Name); #endif #if (IMMEDIATE) if (m->Type & IMMEDIATE) { m->Type &= ~IMMEDIATE; Leave(); } #endif if (Interactive) break; } #ifdef TRACEMAC else if (True(TraceMac)) f_mess("{%s * %d}", m->Name, sp->mf_ntimes); #endif } return EOF; } /* * Do not allow changes to macros during macro execution/definition. * (As a macro lingers on in a kind of "zombie state" after it has been * executed to completion, we use `mac_getc' to cleanup any zombies * and then test, instead of in_macro().) */ private void permission __(( void )); private void permission() { if (Defining) complain("[can't %f when defining]"); if (mac_getc() >= 0) complain("[can't %f when executing]"); } /* * print the macro's body in symbolic form. */ private void pr_mac __(( Macro *_(m), File *_(fp) )); private void pr_mac(m, fp) Macro *m; register File *fp; { register int c; register const char *cp = m->m_body, *last = &cp[m->m_len]; while (cp < last) { c = _UC_(*cp++); if (c == '^' || c == '\\') putc('\\', fp); else if (c < 0200) { if (isctrl(c)) { putc('^', fp); c ^= '@'; #ifdef FUNCKEYS /* * take chars after C-Q literally (this is * for literal chars >= 0200, as opposed to * function keys.) */ if (c == 'Q') { putc(c, fp); c = *cp++; } #endif } } #ifdef FUNCKEYS else { /* print the function key name */ fprintf(fp, "%p", c); continue; } #endif putc(c, fp); } } #ifndef TINY private int Editing ZERO; /* this is set to SAVE by EditMacros */ #else # define Editing 0 #endif /* * Define a macro from the command line (or from a .joverc). */ DEF_CMD( "define-macro", DefMac, NO ) { char namebuf[LBSIZE]; char *body = Minibuf; register Macro *m; int save_flag = Editing; /* default when in .joverc */ data_obj *dummy, **bind_to = &dummy; { register const char *name; register char *deflt = NULL; if (m = (Macro *) findmac((char *) 0)) { /* macro exists */ name = m->Name; if (!InJoverc) { /* write macro in symbolic form to string, to provide default for editing */ File sf; sf.f_ptr = deflt = Minibuf; /* it is free... */ sf.f_cnt = LBSIZE - 1; sf.f_flags = F_STRING; pr_mac(m, &sf); *sf.f_ptr = '\0'; } } else { name = strcpy(namebuf, Minibuf); } /* if we aren't already in a .joverc, insert the result string back into the input stream, to be interpreted through addgetc. */ if (!InJoverc) { body = genbuf; /* Cannot use Minibuf here. */ Inputp = ask(deflt, ProcArgFmt, name); if (!in_macro()) save_flag = SAVE; } else if (exp_p && !Editing) { /* Allow direct keybinding if an argument is given, and we are in a true .joverc. */ bind_to = MapKey(active_map(), MAP_BIND); getch(); /* eat separator */ } } { Savejmp savejmp; register char *cp; register int c, err; InJoverc++; /* pretend we're in .joverc (for addgetc()) */ if ((err = push_env(savejmp)) == NO) { for (cp = body; (c = addgetc()) >= 0; *cp++ = c) ; /* Allow redefinition of macro by itself, but only at its very end. */ if (m && (c = mac_getc()) >= 0) { if (m->Type & EXECUTE) complain("[Can't %f \"%s\" when executing]", m->Name); Ungetc(c); } if (c = (cp - body)) { *bind_to = (data_obj *) save_mac(m, namebuf, body, c, save_flag); } else { /* delete the macro if its new body is empty, and it exists, and we were editing them. */ if (!(m && Editing)) complain("[Macro body is empty!]"); #ifndef Editing del_mac(m); #endif } } pop_env(savejmp); InJoverc--; Inputp = NULL; /* just to be sure... */ if (err) complain((char *) 0); /* propagate error */ } } DEF_CMD( "name-keyboard-macro", NameMac, NO ) { register Macro *m; if (InJoverc) { DefMac(); /* [TRH] this is for backward compatibility */ return; } permission(); if (KeyMacro.m_len == 0) complain("[No keyboard macro to name!]"); m = (Macro *) findmac((char *) 0); if (m == &KeyMacro) complain("[Can't name it that!]"); save_mac(m, Minibuf, KeyMacro.m_body, KeyMacro.m_len, SAVE); } DEF_CMD( "start-remembering", Remember, NO ) { if (in_macro()) /* We're already executing the macro; ignore any attempts to define the keyboard macro while we are executing. */ return; message("Remembering..."); /* Start new macro if a non-positive numeric argument is given, or if we are not currently defining and no argument is given. Otherwise start or keep appending to the current definiton. */ if (KeyMacro.m_len && (exp > 0) && (Defining || exp_p)) add_mess(" continue with definition"); else KeyMacro.m_len = 0; updmodline(); Defining++; } DEF_CMD( "stop-remembering", Forget, NO ) { register Macro *m = &KeyMacro; register data_obj *cmd; register int n; if (!Defining) complain("[Not currently defining!]"); Defining = NO; updmodline(); message("Keyboard macro defined."); n = m->m_len; /* * Backup to (hopefully) the start of Forget. We don't want it to * be part of the macro (though it does not hurt if it is). This * works in all common cases, i.e., if the command is bound to a * key, OR if it is invoked through ESC X. */ do { if (--n < 0) return; cmd = get_bind(active_map(), _UC_(m->m_body[n])); } while (cmd != LastCmd && !IsPrefix(cmd)); m->m_len = n; } DEF_CMD( "execute-keyboard-macro", ExecMacro, NO ) { if (KeyMacro.m_len == 0) complain("[No keyboard macro to execute.]"); do_macro(&KeyMacro); } /* This now toggles so that it is useful even when not Asking. When given an argument it propagates the macro repeat count. (and resets it to 1) */ DEF_CMD( "make-macro-interactive", MacInter, NO ) { if (Interactive ^= YES) { /* Toggle; off -> on */ register Frame *sp; if (!exp_p) { #ifndef TINY register const data_obj *dp = LastCmd; #endif if (sp = MacStkPtr) LastCmd = (data_obj *) sp->mf_macro; /* Cheat */ add_mess("(%f)"); #ifndef TINY if (!Asking && (sp || Defining)) add_mess("... %s to resume macro %s.", (find_binds(dp, genbuf)) ? genbuf : dp->Name, (sp) ? "execution" : "definition"); #endif } else { if (sp = MacStkPtr) { register Macro *m = sp->mf_macro; exp = sp->mf_ntimes; sp->mf_ntimes = 1; if (m->Type & ERRCATCH) { m->Type &= ~ERRCATCH; exp = -exp; } } Interactive = NO; } } this_cmd = ARG_CMD; /* propagate numeric argument */ } int ModMacs() { register Macro *m = macros; while (m = m->m_nextm) /* skip keyboard macro */ if (m->Type & SAVE) return YES; return NO; } /* * Write macros. If we're not editing, each macro definition is prepended * by "define-macro", so it can be read as a .joverc file. * if exp_p is set, only save changed macros. */ private void write_macros __(( const char *_(file) )); private void write_macros(file) const char *file; { register File *fp; register Macro *m; register int nmacs = 0; fp = open_file(file, iobuff, F_WRITE|F_TEXT|F_COMPLAIN); m = macros; #ifndef Editing if (Editing || /* don't output keyboard macro if not editing */ (m = m->m_nextm)) /* ...the next do { ... } while (...) */ #endif do { if (exp_p && !(m->Type & SAVE)) continue; nmacs++; fprintf(fp, Editing ? "%s " : "define-macro %s ", m->Name); pr_mac(m, fp); putc('\n', fp); m->Type &= Editing|TYPEMASK; } while (m = m->m_nextm); f_close(fp); add_mess(" %d macro%n saved.", nmacs); } #ifndef TINY /* * Read macros. This is only used in "edit-macros". * Macro files saved with "write-macros-to-file" are in .joverc format * and can be loaded with the "source" command. */ private void read_macros __(( const char *_(file) )); private void read_macros(file) const char *file; { static const char MacErrBuf[] = "*Macro errors*"; register File *fp; register int err, nmacs = 0; Savejmp sav_jmp; fp = open_file(file, iobuff, F_READ|F_TEXT|F_COMPLAIN); InJoverc++; /* pretend that we're in a .joverc */ if (err = push_env(sav_jmp)) PutErrInBuf(MacErrBuf, file, io_lines, genbuf); while (f_gets(fp, Inputp = genbuf, LBSIZE) == 0 /*!EOF*/) { DefMac(); nmacs++; } f_close(fp); pop_env(sav_jmp); InJoverc--; add_mess(" %d macro%n read.", nmacs); if (err) { rbell(); s_mess("[see buffer \"%s\"]", MacErrBuf); } } #endif /* TINY */ DEF_CMD( "write-macros-to-file", WriteMac, NO ) { char filebuf[FILESIZE]; write_macros(ask_file((char *) 0, (char *) 0, filebuf)); } #ifndef TINY DEF_CMD( "edit-macros", EditMacs, NO ) _IF(ndef TINY) { permission(); Editing = SAVE; EditParam(write_macros, read_macros, "Edit Macros"); Editing = NO; } #endif /* TINY */ DEF_CMD( "delete-macro", DelMacro, NO ) { del_mac((Macro *)findmac(ProcFmt)); } /*====================================================================== * $Log: macros.c,v $ * Revision 14.31.0.12 1994/06/24 17:55:03 tom * (read_macros): fix name of scratch buffer. * * Revision 14.31.0.10 1994/04/08 01:25:39 tom * (DefMac): use Minibuf to expand macro if Injoverc, genbuf otherwise. * * Revision 14.31.0.9 1993/11/11 02:47:02 tom * (DefMac): use genbuf instead of Minibuf for macro body expansion. * * Revision 14.31.0.1 1993/07/07 12:12:57 tom * (F_TEXT): new option for f_open et al. * * Revision 14.31 1993/02/16 03:07:13 tom * "make-macro-interactive": allow propagation of 0 as `execute 0 times' * to "execute-macro"; remove (void) casts; lotsa random optimizations. * * Revision 14.30 1993/02/06 00:48:33 tom * cleanup whitespace; some random optimizations. * * Revision 14.28 1992/10/24 01:24:20 tom * convert to "port{ansi,defs}.h" conventions; some changes due to keymaps. * * Revision 14.27 1992/09/21 16:50:02 tom * fix_macros(): don't unwind macro stack beyond Interactive. * * Revision 14.26 1992/08/26 23:56:55 tom * make macro execution work when Interactive; PRIVATE-ized some Variable defs; * add RCS directives. * */