#include #include #include "vars.h" #include "pline.h" #include "structs.h" #include "format.h" typedef struct ifstack IFSTACK; /* * An entry in the stack of conditional state. A conditional can be in * one of three states: not-yet-true, true, and was-true. (The * difference between not-yet-true and was-true is that "else" or * "elseif" can turn the former, but not the latter, into "true".) * * Here, true says whether it's currently true; evertrue says whether * it ever has been true. */ struct ifstack { IFSTACK *link; unsigned int true; unsigned int evertrue; } ; /* * Format an object description. f is the FILE * to send the resulting * string to; s is the format string; io is the INVOBJ being * described. * * The format is processed in a manner vaguely akin to printf(3)'s * format strings. Escapes are introduced by % signs; other * characters are copied verbatim (except that `output' occurring * inside a false conditional is dropped - printf formats don't have * conditionals, but ours do). * * The full truth of % escapes can, of course, be found below. Here is * a summary as of this writing. Unqualified references to INVOBJ or * OBJTYPE fields refer to those fields in the INVOBJ or its OBJTYPE, * as appropriate. If the % escape is not listed here, it produces an * immediate panic, even if it's inside a false conditional. * * %n * Insert the dispn, in decimal. * %1 * Insert the name string. * %2 * Insert the name2 string. * %3 * Insert the name3 string. * %4 * Insert the name4 string. * %5 * Insert the name5 string. * %c * Insert the called string. * %@ * Panic, but not if it's in a false conditional. (This * exists to help catch bugs. For example, rings should * never be collapsed and thus should never have a dispn * other than 1; their format string includes %?P%@%. to * panic if such a thing is encountered.) * %+x * Calls the fmt_spec routine with x as the second arg. * %? * Introduces a conditional. Simple conditionals take the * form of %?COND ... %. where COND is the condition: * * COND True iff... * C the called string is non-nil. * 2 the name2 string is non-nil. * 3 the name3 string is non-nil. * 4 the name4 string is non-nil. * 5 the name5 string is non-nil. * K flags & OTF_KNOWN. * P dispn > 1. * +x fmt_cond(x,the INVOBJ) returns true. * !COND COND is false. * * If the first char of COND is (, then the conditional is * an expression. Inside ( ), the above conditional forms * can be combined with |, &, and ^, which all have equal * precedence and are left-associative, so that, for * example, %?(C&K|P) is equivalent to %?((C&K)|P) and * %?(C|K&P) is equivalent to %?((C|K)&P). * * If a conditional has a | between the ? and the COND, it * is "else if" rather than "if". * %| * This is "else" to %?'s "if". * %. * This is "endif" to %?'s "if". * * Thus, for example, at this writing the scroll OBJTYPE has format * * %?P%n %|a %.scroll%?Ps%. %?Kof %1%?|Ccalled %c%|titled %2%. * * which generates text like * * 3 scrolls of magic mapping * a scroll called weird feeling * 2 scrolls titled ZELGO MER */ static void format(FILE *f, const char *s, INVOBJ *io) { IFSTACK *ifstack; OBJTYPE *ot; const char *s0; void fmtstring(const char *str) { if (! str) panic("invalid %%%c in format %.*s | %s",*s,(int)(s-s0),s0,s); fprintf(f,"%s",str); } int parse_cond(void) { switch (*s++) { case 'C': return(!!ot->called); break; case '2': return(!!ot->name2); break; case '3': return(!!ot->name3); break; case '4': return(!!ot->name4); break; case '5': return(!!ot->name5); break; case 'K': return(!!(ot->flags&OTF_KNOWN)); break; case 'P': return(io->dispn>1); break; case '!': return(!parse_cond()); break; case '+': return((*ot->ops->fmt_cond)(*s++,io)); break; case '(': { int v; v = parse_cond(); while (1) { switch (*s++) { case ')': return(v); break; case '|': v = parse_cond() || v; break; case '&': v = parse_cond() && v; break; case '^': if (parse_cond()) v = ! v; break; case '\0': panic("end of format within conditional: %s",s0); break; default: panic("bad conditional connective %c in format %.*s | %s",s[-1],(int)(s-s0),s0,s); break; } } } break; case '\0': panic("end of format within conditional: %s",s0); break; default: panic("bad conditional %c in format %.*s | %s",s[-1],(int)(s-s0),s0,s); break; } } s0 = s; ot = &objtypes[io->v[0]->type]; #define IFTRUE() (!ifstack||ifstack->true) ifstack = 0; while (*s) { if (*s != '%') { if (IFTRUE()) putc(*s,f); s ++; continue; } s ++; switch (*s++) { case '\0': panic("end of format after %%: %s",s0); break; default: panic("invalid spec `%%%c' in format %.*s | %s",s[-1],(int)(s-s0),s0,s); break; case 'n': if (IFTRUE()) fprintf(f,"%d",io->dispn); break; case '1': if (IFTRUE()) fmtstring(ot->name); break; case '2': if (IFTRUE()) fmtstring(ot->name2); break; case '3': if (IFTRUE()) fmtstring(ot->name3); break; case '4': if (IFTRUE()) fmtstring(ot->name4); break; case '5': if (IFTRUE()) fmtstring(ot->name5); break; case 'c': if (IFTRUE()) fmtstring(ot->called); break; case '@': if (IFTRUE()) panic("%%@ in format: %s",s0); break; case '+': if (IFTRUE()) (*ot->ops->fmt_spec)(f,*s++,io); break; case '?': { int eif; int cond; eif = 0; if (*s == '|') { eif = 1; s ++; } cond = parse_cond(); if (eif) { if (! ifstack) panic("%%?| not within %%?...%%. in format %.*s | %s",(int)(s-s0),s0,s); ifstack->true = cond && !ifstack->evertrue; ifstack->evertrue |= ifstack->true; } else { IFSTACK *i; i = malloc(sizeof(IFSTACK)); if (IFTRUE()) { i->true = cond; i->evertrue = cond; } else { i->true = 0; i->evertrue = 1; } i->link = ifstack; ifstack = i; } } break; case '|': if (! ifstack) panic("%%| not within %%?...%%. in format %.*s | %s",(int)(s-s0),s0,s); if (ifstack->evertrue) { ifstack->true = 0; } else { ifstack->evertrue = 1; ifstack->true = 1; } break; case '.': { IFSTACK *i; if (! ifstack) panic("%%. not closing a %%? in format %.*s | %s",(int)(s-s0),s0,s); i = ifstack; ifstack = i->link; free(i); } break; } } if (ifstack) panic("unclosed conditional in format %s",s0); #undef IFTRUE } /* * A function suitable for use as an object type's format function. * Processes the object type's format string with format(), above. */ void std_format(FILE *f, INVOBJ *io) { format(f,objtypes[io->v[0]->type].format,io); }