/* generate.c * * Written by D'Arcy J.M. Cain * D'Arcy Cain Consulting * 207 Gamble Avenue * Toronto, Ontario M4J 2P4 * +1 416 424 2871 * * email: darcy@druid.net * * File generation utility * * This program may be freely distributed as long as credit is given to D'Arcy * J.M. Cain, the source is included and this notice remains intact. There * is specifically no restrictions on use of the program including personal * or commercial. You may even charge others for this program as long as the * above conditions are met. * * This is not shareware and no registration fee is expected. If you like the * program and want to support this method of distribution, write a program * and distribute it the same way and I will feel I have been paid. * * Of course gifts of money, drinks and extravagant jewels are always welcome. * * See man page for documentation * * Note: Best viewed with tabstops set to 4 * */ #include #include #include #include #include #include #include #include #include #include #ifdef __MSDOS__ # include # include # ifndef INCLUDE_DIR # define INCLUDE_DIR "c:/package" # endif /* sock_open not available on DOS yet so just alias it to fopen */ #define sock_open fopen #else # include # ifndef INCLUDE_DIR # define INCLUDE_DIR "/usr/package" # endif extern FILE *sock_open(const char *str, const char *mode); #endif #ifndef MAXPARAMNO #define MAXPARAMNO 100 #endif void fatal(const char *s,...); extern int expr(const char *str); extern char *xgetline(FILE *fp, char *buf, size_t *linenum); extern void xgetline_cchar(char c); extern int initarg(int argc, char **argv); extern int initarge(int argc, char **argv); extern int getarg(const char *opts); extern char *xoptarg; static size_t xline; static int quiet = 0; static int strip_leading_spaces = 0; static char cur_line_str[8] = "0"; static char user_str[32] = ""; static char pid_str[21] = "0"; static char date_str[24]; static char time_str[24]; static const char *noparams[] = {"", "", "", "", "", "", "", "", "", ""}; static int open_quote = 0, close_quote = 0; typedef struct _macro { char *name; /* name of the macro */ char *defn; /* unexpanded definition of macro */ int mod_flag; /* to protect the predefined macros */ struct _macro *next; /* it's a linked list */ } MACRO; /* The predefined macros are defined here. This list is actually * * copied into the working list. This may seem wasteful but is * * required because of the high level of error checking I do. * * Others are allocated and linked to this list. */ const char *mac_base_init[][2] = { {"__PID__", NULL}, {"__DATE__", NULL}, {"__TIME__", NULL}, {"__LINE__", NULL}, {"__USER__", NULL}, {"__FILE__", "stdin"}, {"__OFILE__", "stdout"}, {"__INCLUDE__", INCLUDE_DIR}, #define Str(x) #x #define Xstr(x) Str(x) {"__V_MAJOR__", Xstr(VERSION_MAJOR)}, {"__V_MINOR__", Xstr(VERSION_MINOR)}, #ifdef __MSDOS__ {"__MSDOS__", "MS DOS version of generate"}, {"__OS__", "MSDOS"}, #else {"__UNIX__", "UNIX version of generate"}, {"__OS__", "UNIX"}, #endif }; enum {PID_MACRO, DATE_MACRO, TIME_MACRO, LINE_MACRO, USER_MACRO, FILE_MACRO, OFILE_MACRO, INCLUDE_MACRO, V_MAJOR_MACRO, V_MINOR_MACRO, THIS_OS_MACRO, OS_MACRO }; #define BASE_SIZE (sizeof(mac_base_init)/sizeof(mac_base_init[0])) /* this is the actual list we work with */ static MACRO *mac_base = NULL; /* catastrophic failure */ #ifndef PYTHON void fatal(const char *s,...) { va_list argptr; fprintf(stderr, "generate: file %s, line %s: ", mac_base[FILE_MACRO].defn, cur_line_str); va_start(argptr, s); vfprintf(stderr, s, argptr); va_end(argptr); fprintf(stderr, "\n"); exit(1); } #endif /* just walk through the list till we find it */ /* returns NULL if not found */ static MACRO * find_macro(const char *name) { MACRO *mac = mac_base; while (mac && strcmp(name, mac->name)) mac = mac->next; return (mac); } /* remove a macro from the chain */ static void del_macro(const char *name) { MACRO *mac = mac_base, *pmac = NULL; /* we could use find_macro but then we have to find the parent */ /* I suppose I could make a parent pointer but it hardly seems worth it */ while (mac && strcmp(name, mac->name)) { pmac = mac; mac = mac->next; } /* no error if macro doesn't exist */ if (!mac) return; /* it is however an error to delete one of the base macros */ if (mac->mod_flag == 0) fatal("Can't delete predefined macro %s", name); pmac->next = mac->next; free(mac->name); free(mac->defn); free(mac); } static void init_macros(void) { int i; /* get memory for all the base macros */ if ((mac_base = malloc(sizeof(MACRO) * BASE_SIZE)) == NULL) fatal("Can't allocate memory for base macros (%s)", strerror(errno)); for (i = 0; i < BASE_SIZE; i++) { if (mac_base_init[i][1]) mac_base[i].defn = strdup(mac_base_init[i][1]); else { switch (i) { case PID_MACRO: mac_base[i].defn = pid_str; break; case DATE_MACRO: mac_base[i].defn = date_str; break; case TIME_MACRO: mac_base[i].defn = time_str; break; case LINE_MACRO: mac_base[i].defn = cur_line_str; break; case USER_MACRO: mac_base[i].defn = user_str; break; default: fatal("*** Internal error in init_macros ***\n"); break; } } mac_base[i].name = strdup(mac_base_init[i][0]); mac_base[i].mod_flag = 0; mac_base[i].next = &mac_base[i + 1]; } mac_base[BASE_SIZE - 1].next = NULL; } /* add a macro to the list */ static MACRO * add_macro(const char *name, const char *defn) { MACRO *mac; if (find_macro(name)) fatal("Macro %s already defined", name); /* put at end of list */ for (mac = mac_base; mac->next; mac = mac->next) ; if ((mac->next = malloc(sizeof(MACRO))) == NULL) fatal("Can't allocate memory for macro (%s)", strerror(errno)); mac = mac->next; mac->name = strdup(name); mac->defn = strdup(defn); mac->next = NULL; mac->mod_flag = 1; return (mac); } /* see the use of this function for an explanation */ /* basically the caller assigns the parent and we */ /* allow duplicates in this case */ static MACRO * for_macro(MACRO * mac, const char *defn) { if ((mac->next = malloc(sizeof(MACRO))) == NULL) fatal("Can't allocate memory for @FOR macro (%s)", strerror(errno)); mac->next->name = strdup(mac->name); mac = mac->next; mac->defn = strdup(defn); mac->next = NULL; mac->mod_flag = 1; return (mac); } /* here is where we expand the macro */ static void replace_macro(const char *src, char *dst, const char *pre_param[]) { MACRO *mac; char buf[16384], *p; const char *param[MAXPARAMNO]; int k, q; *dst = 0; while (*src) { while (*dst) dst++; /* get simple cases out of the way first */ if (*src == '\\') { *dst++ = *src++; *dst++ = *src++; *dst = 0; continue; } if (*src != '$') { *dst++ = *src++; *dst = 0; continue; } /* we get here so we are looking at a macro */ src++; if (*src != '(') { buf[0] = *src++; buf[1] = 0; } else { char tmp_buf[16384]; src++; for (p = tmp_buf, k = 1; k; src++) { if ((q = (*p++ = *src)) == '"' || q == '\'') { for (++src; *src != q; src++) if ((*p++ = *src) == '\\') *p++ = *src++; else if (!*src) fatal("Unterminated quote"); *p++ = *src; } else if (*src == '\\') *p++ = *(++src); else if (*src == '(') k++; else if (*src == ')') k--; else if (!*src) fatal("Unterminated macro parens"); } *(p - 1) = 0; /* write over the trailing ')' */ replace_macro(tmp_buf, buf, pre_param); } /* buf now holds the macro string */ for (k = 0; k < MAXPARAMNO; k++) param[k] = ""; /* see if we need to replace positional parameter */ if (isdigit((int) *buf)) { char *ptr = NULL; int posparam = strtol(buf, &ptr, 10); if (*ptr && *ptr != ':') fatal("Invalid macro call"); if (*ptr == ':' && !*pre_param[posparam]) replace_macro(ptr + 1, dst, param); else replace_macro(pre_param[posparam], dst, param); continue; } /* $(@:string) Gives length of string */ if (buf[0] == '@' && buf[1] == ':') { char lenbuf[2048]; replace_macro(buf + 2, lenbuf, pre_param); sprintf(dst, "%lu", (unsigned long) strlen(lenbuf)); continue; } /* $(=:5 + 6) does calculation */ if (buf[0] == '=' && buf[1] == ':') { char calcbuf[2048]; replace_macro(buf + 2, calcbuf, pre_param); sprintf(dst, "%d", expr(calcbuf)); continue; } /* $(%:5 - 6) does a series */ if (buf[0] == '%' && buf[1] == ':') { char calcbuf[2048], *ptr; long num = 0, to = 0; replace_macro(buf + 2, calcbuf, pre_param); for (ptr = calcbuf; *ptr;) { while (isspace((int) *ptr)) ptr++; if (!isdigit((int) *ptr)) fatal("Invalid digit"); num = strtol(ptr, &ptr, 0); while (isspace((int) *ptr)) ptr++; if (*ptr == '-') { ptr++; while (isspace((int) *ptr)) ptr++; if (!isdigit((int) *ptr)) fatal("Invalid expression"); to = strtol(ptr, &ptr, 0); while (num <= to) { sprintf(dst, " %ld", num++); while (*dst) dst++; } } else sprintf(dst, " %ld", num); while (*dst) dst++; } continue; } /* $(?:, , ) is an empty string tester */ if (buf[0] == '?' && buf[1] == ':') { char calcbuf[2048]; char *e1, *e2, *e3; replace_macro(buf + 2, calcbuf, pre_param); e1 = calcbuf; while (isspace((int) *e1)) e1++; e2 = e1; while (*e2 && *e2 != ',') { if (*e2 == '\\' && *(e2 + 1) == ',') e2++; e2++; } if (*e2) (*e2++ = 0); while (isspace((int) *e2)) e2++; e3 = e2; while (*e3 && *e3 != ',') { if (*e3 == '\\' && *(e3 + 1) == ',') e3++; e3++; } if (*e3) (*e3++ = 0); if (*e1) strcpy(dst, e2); else strcpy(dst, e3); continue; } /* see if there are parameters */ k = 0; if ((p = strchr(buf, ':')) != NULL) { while (*p) { *p++ = 0; while (isspace((int) *p)) p++; param[k++] = p; while (*p && *p != ',') if (*p++ == '\\') p++; } } /* get the macro definition */ if ((mac = find_macro(buf)) == NULL) fatal("Macro %s not defined", buf); replace_macro(mac->defn, dst, param); } } static int cmp_macro(MACRO * mac, const char *s) { char buf[2048]; replace_macro(mac->defn, buf, noparams); return (strcmp(buf, s)); } #if 0 static void mk_dir_env(char *path) { char *p, *q, c; for (p = path; (q = strchr(p, '/')) != NULL; p = q + 1) { /* case of root based path skip trivial case */ if (p == path) continue; c = *p; *p = 0; mkdir(path, 0); /* let umask determine permissions */ *p = c; /* set things back to what they were */ } } #endif static FILE *outfp; static void put_line(const char *s, FILE * fp) { int no_nl = 0; if (open_quote) fputc(open_quote, fp); while (*s) { no_nl = 0; if (*s == '\\') { switch (*++s) { case 'c': no_nl = 1; break; case 'a': fputc('\a', fp); break; case 'b': fputc('\b', fp); break; case 'f': fputc('\f', fp); break; case 'n': fputc('\n', fp); break; case 'r': fputc('\r', fp); break; case 't': fputc('\t', fp); break; default: fputc(*s, fp); break; } } else fputc(*s, fp); s++; } if (close_quote) fputc(close_quote, fp); if (!no_nl) fputc('\n', fp); } typedef enum { AT_DEFINE, AT_DEFAULT, AT_GETENV, AT_READLINE, AT_REDEFINE, AT_UNDEF, AT_INCLUDE, #ifndef RESTRICTED_VERSION AT_FILE, AT_APPEND, #endif AT_PRINT, AT_SHOW, AT_CLEAR, AT_IFDEF, AT_IFNDEF, AT_ELIFDEF, AT_ELIFNDEF, AT_ELSE, AT_ENDIF, AT_QUOTE, AT_FOR, AT_END, AT_ERROR, AT_RETURN } ATYPE; static ATYPE find_key(char *s) { char *p; int ret; if (!strncasecmp(s, "@DEFINE", 7)) { p = s + 7; ret = AT_DEFINE; } else if (!strncasecmp(s, "@GETENV", 7)) { p = s + 7; ret = AT_GETENV; } else if (!strncasecmp(s, "@READLINE", 9)) { p = s + 9; ret = AT_READLINE; } else if (!strncasecmp(s, "@REDEFINE", 9)) { p = s + 9; ret = AT_REDEFINE; } else if (!strncasecmp(s, "@UNDEF", 6)) { p = s + 6; ret = AT_UNDEF; } else if (!strncasecmp(s, "@INCLUDE", 8)) { p = s + 8; ret = AT_INCLUDE; } #ifndef RESTRICTED_VERSION else if (!strncasecmp(s, "@FILE", 5)) { p = s + 5; ret = AT_FILE; } else if (!strncasecmp(s, "@APPEND", 7)) { p = s + 7; ret = AT_APPEND; } #endif else if (!strncasecmp(s, "@PRINT", 6)) { p = s + 6; ret = AT_PRINT; } else if (!strncasecmp(s, "@SHOW", 5)) { p = s + 5; ret = AT_SHOW; } else if (!strncasecmp(s, "@CLEAR", 6)) { p = s + 6; ret = AT_CLEAR; } else if (!strncasecmp(s, "@IFDEF", 6)) { p = s + 6; ret = AT_IFDEF; } else if (!strncasecmp(s, "@IFNDEF", 7)) { p = s + 7; ret = AT_IFNDEF; } else if (!strncasecmp(s, "@ELIFDEF", 8)) { p = s + 8; ret = AT_ELIFDEF; } else if (!strncasecmp(s, "@ELIFNDEF", 9)) { p = s + 9; ret = AT_ELIFNDEF; } else if (!strncasecmp(s, "@ELSE", 5)) { p = s + 5; ret = AT_ELSE; } else if (!strncasecmp(s, "@ENDIF", 6)) { p = s + 6; ret = AT_ENDIF; } else if (!strncasecmp(s, "@QUOTE", 6)) { p = s + 6; ret = AT_QUOTE; } else if (!strncasecmp(s, "@DEFAULT", 8)) { p = s + 8; ret = AT_DEFAULT; } else if (!strncasecmp(s, "@FOR", 4)) { p = s + 4; ret = AT_FOR; } else if (!strncasecmp(s, "@END", 4)) { p = s + 4; ret = AT_END; } else if (!strncasecmp(s, "@ERROR", 6)) { p = s + 6; ret = AT_ERROR; } else if (!strncasecmp(s, "@RETURN", 6)) { p = s + 7; ret = AT_RETURN; } else return (-1); if (*p && !isspace((int) *p)) return (-1); while (isspace((int) *p)) p++; strcpy(s, p); return (ret); } static void make_path(char *p) { char *q; if ((q = strrchr(p, '/')) != NULL && p != q) { *q = 0; make_path(p); *q = '/'; } #ifdef __MSDOS__ mkdir(p); #else mkdir(p, 0777); #endif } static char * split_line(char *s) { while (*s && !isspace((int) *s)) s++; if (*s) *s++ = 0; while (isspace((int) *s)) s++; return (s); } static int for_level = 0; static const char *for_name[64]; static long for_pos[64]; static int for_line[64]; /* if_flag tells us whether we enter the function in an IF state */ /* 0 means no IF state (start of file state) */ /* 1 means entering in true state */ /* -1 means entering in false state */ static void get_input(FILE * in_fp, int if_flag) { MACRO *mac; char ibuf[2048], mac_exp[2048]; FILE *next_fp; int curline; int cmd; char *curname = mac_base[FILE_MACRO].defn; char *p, *pp = NULL, *q; int if_level = 0; int if_state = 0; int for_entry = for_level; int return_flag = 0; /* if_state tells us the state within this invocation */ /* 0 means true or outside of IF block */ /* 1 means FALSE */ /* -1 means currently FALSE and have already done TRUE block */ /* this flag is modified by @ELSE and @ELIF commands */ /* to start with it can only be 0 or 1 */ if (if_flag < 0) if_state = 1; while ((pp = xgetline(in_fp, pp, &xline)) != NULL) { if (return_flag) continue; p = pp; sprintf(cur_line_str, "%lu", (unsigned long) xline); while (isspace((int) *p)) p++; if (!*p) continue; if (*p != '@') { if (if_state) continue; if (*p == '!') p++; replace_macro(strip_leading_spaces ? p : pp, mac_exp, noparams); put_line(mac_exp, outfp); continue; } /* undocumented - probably unneeded */ if (!p[1] || isspace((int) p[1])) /* comment */ continue; if ((cmd = find_key(p)) == AT_ENDIF) { if (if_level--) continue; if (!if_flag) fatal("Unmatched @ENDIF"); if (for_entry != for_level) fatal("Missing @END"); fflush(outfp); return; } if (cmd > AT_IFNDEF && cmd < AT_ENDIF) { if (if_level) continue; if (!if_flag) fatal("Unbalanced @ELSE, @ELIFDEF or @ELIFNDEF"); replace_macro(p, mac_exp, noparams); q = split_line(mac_exp); if ((mac = find_macro(mac_exp)) != NULL && *q && cmp_macro(mac, q)) mac = NULL; if (if_state < 1) /* possible 0 or already -1 */ if_state = -1; /* means that TRUE already done */ else if (cmd == AT_ELSE) /* if TRUE not done do it now */ if_state = 0; else if (cmd == AT_ELIFDEF) /* as above but conditionally */ { if (mac) if_state = 0; } else if (!mac) if_state = 0; continue; } if (if_state) { if (cmd == AT_IFDEF || cmd == AT_IFNDEF) if_level++; continue; } if (cmd >= AT_REDEFINE) replace_macro(p, mac_exp, noparams); switch (cmd) { case AT_DEFAULT: { char *r = split_line(p); if (!find_macro(p)) add_macro(p, r); } break; case AT_GETENV: { char *r = split_line(p); char *e = getenv(p); if (e) add_macro(p, e); else if (*r) add_macro(p, r); } break; case AT_READLINE: { char *r = split_line(p); char *e = xgetline(stdin, NULL, NULL); if (e && *e) add_macro(p, e); else if (*r) add_macro(p, r); else add_macro(p, ""); free(e); } break; case AT_DEFINE: add_macro(p, split_line(p)); break; case AT_REDEFINE: q = split_line(mac_exp); if ((mac = find_macro(mac_exp)) == NULL) fatal("Macro %s not found", mac_exp); if (mac->mod_flag == 0) fatal("Can't modify macro %s", mac_exp); free(mac->defn); mac->defn = strdup(q); break; case AT_UNDEF: split_line(p); del_macro(p); break; case AT_INCLUDE: split_line(mac_exp); /* colon in name means remote system */ if ((q = strchr(mac_exp, ':')) != NULL) { strcpy(ibuf, mac_exp); if ((next_fp = sock_open(mac_exp, "r")) == NULL) fatal("Can't open socket on %s (%s)", ibuf, strerror(errno)); } else { if (*mac_exp == '/' || *mac_exp == '\\' || *mac_exp == '.') strcpy(ibuf, mac_exp); else sprintf(ibuf, "%s/%s", mac_base[INCLUDE_MACRO].defn, mac_exp); if ((next_fp = fopen(ibuf, "r")) == NULL) fatal("Can't open %s (%s)", ibuf, strerror(errno)); } curname = mac_base[FILE_MACRO].defn; mac_base[FILE_MACRO].defn = ibuf; curline = xline; xline = 0; get_input(next_fp, 0); fclose(next_fp); xline = curline; mac_base[FILE_MACRO].defn = curname; break; #ifndef RESTRICTED_VERSION case AT_FILE: case AT_APPEND: if (outfp && outfp != stdout) fclose(outfp); free(mac_base[OFILE_MACRO].defn); split_line(mac_exp); if (*mac_exp == 0) { outfp = stdout; mac_base[OFILE_MACRO].defn = strdup("stdout"); } /* colon in name means remote system */ else if ((q = strchr(mac_exp, ':')) != NULL) { if ((next_fp = sock_open(mac_exp, "w")) == NULL) fatal("Can't open socket on %s (%s)", mac_exp, strerror(errno)); mac_base[OFILE_MACRO].defn = strdup(mac_exp); } else { if ((q = strrchr(mac_exp, '/')) != NULL) { *q = 0; make_path(mac_exp); *q = '/'; } if ((outfp = fopen(mac_exp, cmd == AT_FILE ? "wt" : "at")) == NULL) fatal("Can't open %s (%s)", mac_exp, strerror(errno)); mac_base[OFILE_MACRO].defn = strdup(mac_exp); } break; #endif /* RESTRICTED_VERSION */ case AT_ERROR: if (cmd == AT_ERROR) fatal(mac_exp); break; case AT_RETURN: return_flag = 1; break; case AT_PRINT: if (!quiet) put_line(mac_exp, stdout); break; case AT_SHOW: for (mac = mac_base; mac; mac = mac->next) printf("[%s] = [%s]\n", mac->name, mac->defn); break; case AT_CLEAR: if (*mac_exp) { MACRO *mclr; split_line(mac_exp); if ((mclr = find_macro(mac_exp)) == NULL) fatal("Macro %s not found", mac_exp); /* skip over multiples (@FOR variables) */ while (mclr->next && !strcmp(mclr->name, mclr->next->name)) mclr = mclr->next; mac = mclr->next; mclr->next = NULL; /* it is an error to clear from one of the base macros */ if (mclr->mod_flag == 0) fatal("Can't clear from predefined macro %s", mac_exp); } else { mac = (mac_base + BASE_SIZE - 1)->next; mac_base[BASE_SIZE - 1].next = NULL; } while (mac) { MACRO *m = mac->next; free(mac->name); free(mac->defn); free(mac); mac = m; } break; case AT_IFDEF: case AT_IFNDEF: q = split_line(mac_exp); if ((mac = find_macro(mac_exp)) != NULL && *q && cmp_macro(mac, q)) mac = NULL; get_input(in_fp, (mac ? 1 : -1) * (cmd == AT_IFDEF ? 1 : -1)); break; case AT_QUOTE: p = mac_exp; if ((open_quote = p[0]) != 0 && p[1]) p++; close_quote = *p; break; case AT_FOR: if (!*(p = split_line(mac_exp))) fatal("No arguments to @FOR command"); mac = add_macro(mac_exp, ""); for (; *p; p = q) { while (*p && isspace((int) *p)) p++; if (*p == '\'' || *p == '"') { int c = *p++; for (q = p; *q && *q != c; q++) ; if (*q) *q++ = 0; } else q = split_line(p); mac = for_macro(mac, p); } for_name[for_level] = mac->name; for_pos[for_level] = ftell(in_fp); for_line[for_level] = xline; del_macro(for_name[for_level++]); break; case AT_END: if (for_level <= for_entry) fatal("Too many @END statements"); del_macro(for_name[for_level - 1]); if (!find_macro(for_name[for_level - 1])) for_level--; else { fseek(in_fp, for_pos[for_level - 1], SEEK_SET); xline = for_line[for_level - 1]; } break; default: fatal("Unknown directive"); break; } } if (for_level > for_entry) fatal("Missing @END"); if (if_state) fatal("Missing @ENDIF"); fflush(outfp); } #ifndef PYTHON int main(int argc, char **argv) { int c, tickled = 0; char *q, buf[BUFSIZ]; FILE *fp; struct stat st; outfp = stdout; init_macros(); if ((q = getenv("USER")) == NULL) #ifndef __MSDOS__ if ((q = getenv("LOGNAME")) == NULL) q = getlogin(); #else q = getenv("LOGNAME"); #endif if (q) strcpy(user_str, q); #ifdef __MSDOS__ if ((q = getenv("PID")) != NULL) strncpy(pid_str, q, sizeof(pid_str - 1)); #else sprintf(pid_str, "%ld", (long) (getpid())); #endif { time_t t = time(NULL); struct tm *tm = localtime(&t); strftime(date_str, sizeof(date_str), "%a %b %e %Y", tm); strftime(time_str, sizeof(time_str), "%T", tm); } if (initarge(argc, argv) < 0) fatal("Error initializing arguments"); #ifdef __XMSDOS__ _fmode = O_BINARY; #endif while ((c = getarg("D:i:qvs")) != 0) { switch (c) { case 'D': if ((q = strchr(xoptarg, '=')) != NULL) *q++ = 0; else q = xoptarg + strlen(xoptarg); add_macro(xoptarg, q); break; case 'q': quiet = 1; break; case 'v': quiet = 0; break; case 's': strip_leading_spaces = 1; break; case 'i': mac_base[INCLUDE_MACRO].defn = strdup(xoptarg); break; case -1: tickled++; xline = 0; /* colon in name means remote system */ if ((q = strchr(xoptarg, ':')) != NULL) { strcpy(buf, xoptarg); if ((fp = sock_open(xoptarg, "r")) == NULL) fatal("Can't open socket on %s (%s)", buf, strerror(errno)); } else { if (*xoptarg == '/' || *xoptarg == '\\' || *xoptarg == '.') strcpy(buf, xoptarg); else sprintf(buf, "%s/%s", mac_base[INCLUDE_MACRO].defn, xoptarg); if (stat(buf, &st)) fatal("Can't stat %s (%s)", buf, strerror(errno)); if (S_ISDIR(st.st_mode)) strcat(buf, "/script"); if ((fp = fopen(buf, "r")) == NULL) fatal("Can't open %s (%s)", buf, strerror(errno)); } mac_base[FILE_MACRO].defn = buf; get_input(fp, 0); fclose(fp); break; default: fatal("Unknown option"); break; } } if (!tickled) get_input(stdin, 0); return (0); } #else /* PYTHON defined */ #include #include static char Generate_doc[] = "Initiate the generate macro processor. Note that @FILE and @APPEND do\n" "not work in the Python module.\n" "\n" "The first argument is the name of the input file. The second argument is\n" "the name of the output file. The optional third argument is a dictionary,\n" "each key of which should be turned into a predefined macro. The final\n" "argument is the character to use as a comment (the default is #).\n" "\n" "DESCRIPTION\n" "\n" "Generate reads the file named as the first argument and provisionally\n" "outputs each line with macro substitution. If the first character is\n" "'@' then it is taken to be a directive and various actions are taken\n" "as described below. An unescaped comment character in the input stream\n" "causes all input from that character to the end of the current line to\n" "be ignored. Blank lines are also ignored. The sequence \"\\\"\n" "is converted to a space.\n" "\n" "A dollar sign signifies the start of a macro unless escaped by a backslash.\n" "If it is followed by an open parentheses then everything up to the closing\n" "parentheses is the macro. If not followed by an open parentheses then the\n" "single character following the dollar sign is the macro. A macro is\n" "replaced by its definition as in the following example:\n" "\n" " @DEFINE hello goodbye\n" "\n" "This defines hello such that instances in the text of \"$(hello)\" are\n" "replaced by \"goodbye\". See below for more information on defining\n" "macros.\n" "\n" "If there is a colon embedded in the macro then it separates the name of\n" "the macro from arguments to it. Arguments are separated by commas.\n" "The arguments are numbered from 0 to 9. In the definition of the macro\n" "positional parameters which consist of a dollar sign followed by a\n" "digit are replaced by the argument from the macro call. For example:\n" "\n" " @DEFINE foo bar $0 zap $1\n" " ...\n" " $(foo:none,gun) --> bar none zap gun\n" "\n" "Defaults are allowed:\n" " @DEFINE foo bar $0 zap $(1:gun)\n" " ...\n" " $(foo:none) --> same result as above\n" "Note that \"\\$\" is treated as a single \"$\". Macro processing is not\n" "performed within quotes unless the quotes are escaped.\n" "\n" "It is an error to define a macro that already exists.\n" "\n" "Four special macros are defined, '@', '=', '%' and '?'. The first is a\n" "strlen operator, the second is a calc operator. The third generates a\n" "series of numbers. The fourth evaluates to its second argument if the\n" "first is not blank and to its third argument otherwise. Naturally this\n" "will normally be used with a variable in the first argument.\n" "\n" " $(@:Hello) ----> 5\n" " $(=:$(@:Hello) + $(@:world) ----> 10\n" " $(%: 1 2 3 6 - 9) ----> 1 2 3 6 7 8 9\n" " $(?: something, expr1, expr2) ----> expr2\n" " $(?: , expr1, expr2) ----> expr2\n" "\n" "Predefined macros:\n" "\n" " __FILE__ Current file being used for input\n" "\n" " __LINE__ Current line in current file\n" "\n" " __OFILE__ Current file being used for output\n" "\n" " __INCLUDE__ Active include directory\n" "\n" " __USER__ Current user taken from USER environment variable, LOG-\n" " NAME environment variable or, if Unix, from the actual\n" " login name.\n" "\n" " __DATE__ The date when the script was interpreted.\n" "\n" " __TIME__ The time when the script was interpreted.\n" "\n" " __UNIX__ Defined if compiled under Unix\n" "\n" " __MSDOS__ Defined if compiled under MS DOS\n" "\n" " __PID__ Process ID under Unix or 0 under MSDOS\n" "\n" "Predefined macros cannot be cleared.\n" "\n" "DIRECTIVES\n" "\n" "@DEFINE\n" "This defines a new macro, for example:\n" "\n" " @DEFINE hello goodbye\n" "\n" "This defines hello such that instances in the text of \"$(hello)\" are\n" "replaced by \"goodbye\". See above for more details on macro substitution\n" "\n" "@UNDEF\n" "This removes a previously defined macro. It is not an error to unde fine\n" "a macro that was not defined to begin with.\n" "\n" "@DEFAULT\n" "This operates like define except that the directive is ignored if the\n" "macro is already defined. This is equivalent to:\n" "\n" " @IFNDEF foo\n" " @DEFINE foo bar\n" " @ENDIF\n" "\n" "@REDEFINE\n" "This allows a macro to be redefined. The macro must already exist in\n" "order to redefine it. The importance of this command is that it modifies\n" "the definition of the original macro and so can be used in a block\n" "while affecting macros outside of the block. Here is a sample use of\n" "this command:\n" "\n" " @DEFAULT foo x y z\n" " @FOR bar a b c\n" " @REDEFINE foo $(foo) $(bar)\n" " @CLEAR bar\n" " @END\n" " @PRINT $(foo) # Should print \"x y z a b c\"\n" "\n" "Note: Due to the nature of this command, the redefinition is evaluated\n" "for macro processing before assignment to avoid recursive definitions.\n" "This means that parameter passing cannot be use in these definitions.\n" "\n" "@GETENV\n" "This looks for an environment variable matching the first argument. If\n" "it is found a macro is defined as its value. If it is not found and a\n" "string is included then the macro is defined as that string. If neither\n" "case is true the macro is not defined.\n" "\n" "@READLINE\n" "This reads a line from the standard input regardless of the current\n" "input file and assigns it to the name given as its argument. If the\n" "input line is empty and a second argument is supplied then that becomes\n" "the definition. Note that unlike GETENV this command always creates a\n" "macro definition.\n" "\n" "@IFDEF\n" "@IFNDEF\n" "This tests if the macro is currently defined and performs the statements\n" "up till the balancing @ELIFDEF, @ELIFNDEF, @ELSE or @ENDIF if so in the\n" "case if @IFDEF and if not in the case of @IFNDEF.\n" "\n" "@ELIFDEF\n" "@ELIFNDEF\n" "After an @IFDEF or @IFNDEF a series of @ELIFDEFs and @ELIFNDEFs may\n" "appear. If The macro is defined (or not for @ELIFNDEF) and no blocks\n" "have been performed since the balancing @IFDEF or @IFNDEF then the\n" "statements up till the balancing @ELIFDEF, @ELIFNDEF, @ELSE or @ENDIF\n" "are performed.\n" "\n" "Note: The IFDEF family evaluates the macro before testing as well as\n" "evaluating the macro definition. This makes testing of macros with\n" "positional parameters a little tricky. Example:\n" "\n" " @DEFINE X Y\n" " @DEFINE Z $(X) $(1:foo) $2 bar\n" " @DEFINE A $Z\n" " ...\n" " @IFDEF A Y foo bar\n" " PASSED\n" " @ENDIF\n" "\n" "This passes but note the double space between foo and bar.\n" "\n" "@ELSE\n" "IF previous balancing block has been performed the statements up till\n" "the balancing @ENDIF are performed.\n" "\n" "@ENDIF\n" "Closing line for above statements.\n" "\n" "@FOR\n" "Does for loop processing. The arguments are a macro name followed by\n" "the strings to assign each time through the loop.\n" "\n" "@END\n" "This marks the end of a @FOR loop.\n" "\n" "@INCLUDE\n" "The named file is included at the current point in the processing just\n" "as if it had been part of the current file with the exception that @IF\n" "type statements must balance within a particular file. If the file\n" "name starts with a '/' ('\\' allowed as well for MSDOS compatibility) or\n" "a '.' then that file is used, otherwise the package directory is\n" "prepended before the open is performed. The default package directory\n" "is compiled into the program and may be changed with a -i option on the\n" "command line. Under Unix, if the file name has a colon it is taken to\n" "be in the form host:port and a socket is opened to the specified host\n" "and port with the output used as the input to generate.\n" "\n" "@PRINT\n" "Prints the text following the directive to be printed on the standard\n" "output no matter what the current output file is.\n" "\n" "@ERROR\n" "Similar to @PRINT except that processing stops at that point.\n" "\n" "@RETURN\n" "Causes the the balance of the current file to be ignored. This is still\n" "experimental and needs some work in cleaning up @FOR loops.\n" "\n" "@SHOW\n" "Useful for debugging. Shows all the currently defined macros and their\n" "definitions.\n" "\n" "@CLEAR\n" "Clears all user defined macros. If a macro is given then all macros\n" "that were defined since the named macro was defined are cleared. The\n" "named macro itself is not cleared.\n" "\n" "@QUOTE\n" "Causes all subsequent output to be surrounded by quote characters. The\n" "characters to use are determined by the argument to @QUOTE. The first\n" "character is the open quote and the second is the close quote. If\n" "there is only one character it is used for both open and close. If\n" "there is no argument then quoting is turned off.\n" "\n" "AUTHOR\n" "\n" "D'Arcy J.M. Cain\n" "Toronto, Ontario\n" "Email: darcy@druid.net"; static jmp_buf exit_buf; void pyexit(int status) { /* status must be non-zero */ if (status == 0) status = -1; longjmp(exit_buf, status); } static char errbuf[4096]; /* catastrophic failure */ void fatal(const char *s,...) { va_list argptr; int len; len = sprintf(errbuf, "generate: file %s, line %s: ", mac_base[FILE_MACRO].defn, cur_line_str); va_start(argptr, s); vsprintf(errbuf+len, s, argptr); va_end(argptr); pyexit(1); } static PyObject * Generate(PyObject * self, PyObject * args) { char * infile, *outfile; PyObject * dict = NULL; PyObject * key, * value; PyObject * skey, *svalue; FILE * fpi; int pos; char buf[16384]; char * env; char * cchar = NULL; int rc; if (!PyArg_ParseTuple(args, "ss|Os", &infile, &outfile, &dict, &cchar)) { return NULL; } if (dict && !PyDict_Check(dict)) { PyErr_SetObject(PyExc_TypeError, PyString_FromString("dict argument must be a dictionary")); return NULL; } /* default macro values */ sprintf(pid_str, "%ld", (long)getpid()); env = getenv("USER"); if (!env) env = getenv("LOGNAME"); if (env) { strncpy(user_str, env, sizeof(user_str)); user_str[sizeof(user_str)-1] = 0; } { time_t t = time(NULL); struct tm *tm = localtime(&t); strftime(date_str, sizeof(date_str), "%a %b %e %Y", tm); strftime(time_str, sizeof(time_str), "%T", tm); } init_macros(); if (dict) { pos = 0; while (PyDict_Next(dict, &pos, &key, &value)) { skey = PyObject_Str(key); svalue = PyObject_Str(value); add_macro(PyString_AsString(skey), PyString_AsString(svalue)); Py_DECREF(skey); Py_DECREF(svalue); } } fpi = fopen(infile, "r"); if (!fpi) { sprintf(buf, "Error %d opening %s: %s\n", errno, infile, strerror(errno)); PyErr_SetObject(PyExc_IOError, PyString_FromString(buf)); return NULL; } outfp = fopen(outfile, "w"); if (!outfp) { fclose(fpi); sprintf(buf, "Error %d opening %s: %s\n", errno, outfile, strerror(errno)); PyErr_SetObject(PyExc_IOError, PyString_FromString(buf)); return NULL; } mac_base[FILE_MACRO].defn = infile; mac_base[OFILE_MACRO].defn = strdup(outfile); if ((rc = setjmp(exit_buf))) { sprintf(buf, "Generate failed with error code %d. %s", rc, errbuf); PyErr_SetObject(PyExc_SyntaxError, PyString_FromString(buf)); fclose(fpi); fclose(outfp); return NULL; } if (cchar) xgetline_cchar(*cchar); get_input(fpi, 0); fclose(fpi); fclose(outfp); Py_INCREF(Py_None); return Py_None; } PyMethodDef generate[] = { {"generate", (PyCFunction)Generate, METH_VARARGS, Generate_doc}, {NULL, NULL, 0, NULL}, }; void initgenerate(void) { static char mod_docstr[] = "Generate macro processor"; Py_InitModule4("generate", generate, mod_docstr, NULL, PYTHON_API_VERSION); } #endif