/* * R S E N T * * Generates random text messages. From James Gimpel, * "Algorithms in SNOBOL4". */ /*)BUILD */ /* * Usage: * * generate(text, outfun) * char *text; -- the rule to start * int (*outfun)(); -- output function * * The text will be output until a rule is seen. Rules are text * enclosed in angle brackets. For example, * * generate("That was a dumb idea, you .", outfun); * * Outfun will be called as follows: * * outfun(text) * char *text; -- what to output * * { * if (text == NULL) { * -- terminate output and reinitialize * } * else { * -- output desired text * } * } * * For testing, the program reads a grammar from an indicated file, * then allows random sentence generations. * * The grammar is entered in "Bacus Naur" format: * * = rule body | another body | ... * * Where rule names are enclosed in '<' and '>' and rule bodies * are separated by '|'. To continue a rule on another line, * the last byte of the line should be '|'. The frequency of * a rule can be set using the '#number#' construction. * Note: blanks are significant in rules. * */ #include #define EOS '\0' #define EOF -1 #define EOL '\n' #define TRUE 1 #define FALSE 0 #define NRULE 50 /* Number of rules */ #define NTERM 400 /* Rule bodies */ #define TEMPMAX 256 /* Temp buffer size */ typedef struct rule { char *r_name; /* Rule name */ int r_weightsum; /* Sum of all weights */ char **r_term; /* Rule terms */ } RULE; RULE rule[NRULE]; char *term[NTERM]; int rindex = 0; /* Rule index */ int tindex = 0; /* Term index */ int debug = 0; /* Magic printout hack flag */ extern long seed; /* Magic for debugging only */ long oldseed; /* More magic for debugging */ char line[133]; /* Input text line */ char *linep = line; /* -> current input text */ char temp[TEMPMAX]; /* Working text */ FILE *infd = NULL; int ccpos; /* For newline while generating */ /* * Text to output to define praise subroutine */ char *header[] = { "#include ", "typedef struct rule {", " char *r_name;", " int r_weightsum;", " char **r_term;", "} RULE;", NULL, }; main(argc, argv) int argc; /* Argument counter */ char *argv[]; /* Argument vector */ /* * Get grammar, then generate random sentences. * * ::= * || */ { if (argc > 1 && argv[1][0] == '-' && tolower(argv[1][1]) == 'd') { debug++; argc--; argv++; } if (argc >= 2) { if ((infd = fopen(argv[1], "r")) == NULL) { error("Can't open grammar file \"%s\"\n", argv[1]); } } else { if ((infd = fopen("praise.grm", "r")) == NULL) { while (infd == NULL) { printf("Grammar input file: "); fflush(stdout); gets(line); if ((infd = fopen(line, "r")) == NULL) { fprintf("Can't open \"%s\"\n", line); } } } } getgrammar(); printf("%d rules, %d terms\n", rindex, tindex); printf(" for rule names,\n'?' for grammar dump,\n"); printf("'.' to write grammar in C\n"); printf("or ()rulename\n"); process(); } process() /* * Get command and do it */ { register char *lp; register int howmany; extern int out(); for (;;) { printf("* "); fflush(stdout); if (gets((lp = line)) == NULL) break; if (*lp == EOS) dumpnames(); else if (streq(lp, "??")) debug++; else if (streq(lp, "?")) dumpgrammar(); else if (*lp == '.') { for (lp++; *lp == ' '; lp++); outgrammar(lp); } else { if (isdigit(*lp)) { howmany = atoi(lp); while (isdigit(*lp)) lp++; if (*lp == EOS) { generate( "Gotta have a rule, you .", out); continue; } } else { howmany = 1; } while (--howmany >= 0) { oldseed = seed; generate(lp, out); out(NULL); if (debug) { seed = oldseed; debug = 0; generate(lp, out); out(NULL); debug = 1; } } } } } outgrammar(filename) char *filename; { register char **hp; register int i; register FILE *outfd; outfd = NULL; if (*filename != EOS) { if ((outfd = fopen(filename, "w")) == NULL) { printf("Can't write to \"%s\"\n", filename); } } while (outfd == NULL) { printf("Output C grammar to? "); fflush(stdout); if (gets(line) == NULL) exit(); if ((outfd = fopen(line, "w")) == NULL) { perror(line); printf("Can't create \"%s\".\n", line); } } do { printf("Rule vector name: "); fflush(stdout); if (gets(line) == NULL) exit(0); } while (line[0] == EOS); for (hp = header; *hp != NULL; hp++) { fprintf(outfd, "%s\n", *hp); } fprintf(outfd, "static char *term[%d] = {\n", tindex); for (i = 0; i < tindex; i++) { if (term[i] == NULL) { fprintf(outfd, " NULL,\n"); } else { fprintf(outfd, " \"\\%03o%s\",\n", term[i][0], &term[i][1]); } } fprintf(outfd, "};\n"); fprintf(outfd, "RULE %s[%d] = {\n", line, rindex); for (i = 0; i <= rindex; i++) { if (rule[i].r_name == NULL) fprintf(outfd, "{ NULL,"); else fprintf(outfd, "{ \"%s\",", rule[i].r_name); fprintf(outfd, "\t%d, ", rule[i].r_weightsum); if (rule[i].r_term == NULL) fprintf(outfd, "NULL },\n"); else fprintf(outfd, " &term[%d] },\n", rule[i].r_term - &term[0]); } fprintf(outfd, "};\n"); fclose(outfd); } dumpnames() /* * Dump all rule names */ { register int r; register RULE *rp; register int len; rp = &rule[0]; ccpos = 0; for (r = 0; r < rindex; r++) { len = strlen(rp->r_name); if (len + ccpos >= (72 - 3)) { printf("\n"); ccpos = 0; } else if (ccpos > 0) { printf(" "); ccpos++; } printf("<%s>", rp->r_name); ccpos += len + 3; rp++; } if (ccpos > 0) printf("\n"); } dumpgrammar() /* * Dump the entire grammar */ { int r; register RULE *rp; register char **termp; register char *tp; rp = &rule[0]; for (r = 0; r < rindex; r++) { printf("%3d <%s> [%d] ::=\n", r, rp->r_name, rp->r_weightsum); for (termp = rp->r_term; (tp = *termp++) != NULL;) { printf(" #%d# ", *tp++); printf(" %s", tp); if (*termp != NULL) printf(" |\n"); } printf("\n"); rp++; } } generate(text, outfun) char *text; /* Where to start */ int (*outfun)(); /* Output function */ /* * Generate text from the indicated pattern - recursive. */ { register char *start; /* Start of current stuff */ register char *end; /* End of current stuff */ register char *tp; /* Random text pointer */ RULE *rp; /* Rule pointer */ int weight; /* Weight for randomness */ char **termp; /* Rule term pointer */ char *work; /* Temp buffer pointer */ if (debug) printf("\ngenerate(\"%s\")\n", text); for (start = text; *start != EOS;) { /* * Look for */ end = (start == text) ? start : start + 1; for (; *end != EOS && *end != '<'; end++); /* * Output start .. end-1 (even if null string) */ if (end > start && (tp = malloc((end - start) + 1)) != NULL) { work = tp; while (start < end) { *tp++ = *start++; } *tp = EOS; (*outfun)(work); free(work); } if (*end == EOS) { break; /* All done */ } /* * (else) text ... */ end++; for ((rp = &rule[0]); (tp = rp->r_name) != NULL; rp++) { /* * Look for the in the rule name table */ for (start = end; *tp != EOS && *start == *tp;) { start++; tp++; } if (*tp == EOS && *start == '>') { /* * Gotcha */ weight = irand(rp->r_weightsum); if (debug) { printf(" <%s> [%d/%d]:", rp->r_name, weight, rp->r_weightsum); } for (termp = rp->r_term; *termp != NULL; *termp++) { if ((weight -= (termp[0][0] & 0377)) < 0) break; } if (termp == NULL) { generate(" (bug) ", outfun); } else { generate(&termp[0][1], outfun); } break; /* Exit rule search */ } } if (tp == NULL) { /* * No match for this rule */ (*outfun)("<"); start = end; /* start at "foo>" */ } else { start++; /* start after '>' */ } } } /* * Output routine for generate() */ static int out_ccpos = 0; /* Initialize ccpos */ static int out_space = 0; /* Pending space? */ out(text) char *text; /* * Do the output, called from within generate(). * Note: * out(NULL) Completion - reinitialization */ { register char *tend; register char *tptr; register int tsize; if ((tend = text) == NULL) { if (out_ccpos > 0) { /* Terminate any */ putchar('.'); /* partially-output */ putchar('\n'); /* text line */ } out_ccpos = 0; /* Initialize carriage */ out_space = 0; /* and space flag */ return; } if (debug) printf("\nout(\"%s\"), cc = %d, sp = %d [", text, out_ccpos, out_space); if (*tend == EOS) { /* * Null input line */ out_space = 0; return; } if (out_ccpos <= 0 && islower(*tend)) { putchar(toupper(*tend++)); /* First char is */ out_ccpos = 1; /* Capitalized */ out_space = 0; /* No space before */ } /* * At the while test, tend -> start of the word or EOS */ while (*tend != EOS) { /* * tptr -> start of word (can't -> EOS) * tend := EOS or blank following the word * First, skip over multiple spaces. */ if (out_space) { while (*tend == ' ') tend++; } for (tptr = tend; *tend != EOS && *tend != ' '; tend++) ; /* Find following blank */ tsize = tend - tptr; /* Actual word size */ if (out_space) { /* Space needed? */ out_space = 0; /* Not next time */ if ((out_ccpos + tsize) >= 72) { putchar('\n'); /* Make a new line */ out_ccpos = 0; /* Reset the carriage */ } else { putchar(' '); /* It fits on this line */ out_ccpos++; /* Count the blank too */ } } out_ccpos += tsize; if (debug) putchar('{'); while (tptr < tend) { /* Output the word */ putchar(*tptr++); } if (debug) putchar('}'); if (*tend == ' ') { /* Space needed soon? */ out_space = 1; /* Yep, set the flag */ while (*tend == ' ') /* and get all the */ tend++; /* spaces we can */ } } if (debug) printf("], cc=%d, sp=%d\n", out_ccpos, out_space); } getgrammar() /* * Read the grammar */ { while (!feof(infd)) { getrule(); } rule[rindex].r_name = NULL; /* terminate rules */ rule[rindex].r_weightsum = 0; rule[rindex].r_term = NULL; } getrule() /* * Read one rule: * * ::= '=' * ::= '<' '>' * ::= * || '|' * * '|' at the end of the line continues rule_bodies on the next line. * else, the rule ends at the end of line. */ { RULE *rp; char *getname(); int getbyte(); for (;;) { /* Loop until a rule is read in */ while (peek() == ' ' || peek() == EOL) getbyte(); if (peek() == EOF) { return (FALSE); } if (rindex >= (NRULE - 1)) error("More than %d rules\n", NRULE); rp = &rule[rindex++]; /* rp -> new rule */ if ((rp->r_name = getname()) == NULL) { bug("E", "no name for rule"); skipeol(); rindex--; continue; } skipwhite(); if (getbyte() != '=') { bug("W", "expecting '=' after rule name"); } rp->r_term = &term[tindex]; rp->r_weightsum = getbody(); return (TRUE); } } int getbody() /* * Read all bodies for this rule: * * ::= () * ::= '#' '#' * ::= text string with s */ { int wsum; /* Weight sum */ register char *tp; /* Text pointer */ register int c; /* Current character */ int value; /* Working value */ int getbyte(); char *stash(); wsum = 0; while ((c = skipwhite()) != EOL && c != EOF) { if (tindex >= (NTERM-1)) { error("More than %d rule terms\n", NTERM); } if (c == '#') { /* * Specific weight: = #number# body */ getbyte(); value = 0; while (isdigit((c = getbyte()))) { value *= 10; value += (c - '0'); } } else value = 1; wsum += value; tp = &temp[0]; *tp++ = value; while (tp < &temp[TEMPMAX-2]) { switch (peek()) { case EOF: case EOL: case '|': goto breakout; case ' ': /* Trash leading blanks */ if (tp == &temp[1]) { getbyte(); break; } default: *tp++ = getbyte(); } } breakout: while (tp > &temp[1] && tp[-1] == ' ') tp--; /* Trailing blanks too */ *tp = EOS; term[tindex++] = stash(temp); if (skipwhite() == '|') { getbyte(); if (skipwhite() == EOL) getbyte(); } } term[tindex++] = NULL; /* Terminate rule terms */ return (wsum); } char * getname() /* * Read a rule */ { register char *tp; register int c; char *stash(); int getbyte(); if (getbyte() != '<') { bug("E", "Name must start with an '<'"); return (NULL); } for (tp = &temp[0]; tp < &temp[TEMPMAX - 2];) { if ((c = getbyte()) == '>' || c == EOL || c == EOF) break; *tp++ = c; } *tp = EOS; if (c != '>') { bug("E", "Bad or long rule name -- must end with '>'"); fprintf(stderr, "name as read = \"%s\"\n", temp); return (NULL); } return (stash(temp)); } /* * Stuff */ char * myalloc(size) int size; /* * Allocate or die */ { register char *p; if ((p = calloc(size, 1)) == NULL) error("?-Praise-Can't allocate %d bytes\n", size); return (p); } char * stash(string) char *string; /* * Store a string away */ { register char *s; if ((s = malloc(strlen(string) + 1)) == NULL) error("?-Praise-Can't store \"%s\"", string); return(strcpy(s, string)); } /* * I/O */ int getbyte() /* * Read one byte */ { if (*linep == EOS) { if (fgets(line, sizeof line, infd) == NULL) return (EOF); linep = line; } return (*linep++); } int peek() /* * Peek at the next byte */ { register int c; if ((c = getbyte()) != EOF) linep--; return (c); } int skipwhite() /* * Skip over whitespace, return "peek()" after skip */ { register int c; while ((c = peek()) == ' ' || c == '\t') getbyte(); return (c); } skipeol() /* * Skip to end of line */ { register int c; while ((c = getbyte()) != EOL && c != EOF) ; } bug(severity, message) char *severity; char *message; { fprintf(stderr, "?%s-Praise-%s", severity, message); if (!feof(infd)) { fprintf(stderr, " at line:\n%.*s\n", strlen(line) - 1, line); fprintf(stderr, "the error is near byte %d", linep - line); if (linep > line && linep[-1] >= ' ') { fprintf(stderr, ": '%c'\n", linep[-1]); } else fprintf(stderr, "\n"); } else fprintf(stderr, " at end of input\n"); if (tolower(severity[0]) == 'f') error("can't continue"); }