/* * flag - manipulate flags * * Reads records from stdin, manipulates flags according to the program * given on the command line, and writes the resulting records to * stdout, or other disposition as specified. * * The program on the command-line is a simple stack-based language. * Primitives: * * n push the number of flags in the record * Nx push the number of x flags in the record * nnn (string of digits) push the number * "x push character code for x * <,>,= compare two top-of-stack numbers; push 1 if they obey * the indicated relation, 0 if not * ! push 1 if top-of-stack is 0, 0 if not * ?#. * conditional: if top-of-stack is 0, execute the second * code block, otherwise the first. (The # and the * second code block are optional.) * (@) * loop: execute the first code block, then pop the * top-of-stack value. If the value popped is zero, * continue execution after the ), otherwise execute the * second code block and return to the first code block. * x swap the top two stack elements * r rotate: ... a b c -> ... b c a * R rotate: ... x1 x2 ... xN N -> x2 ... xN x1 (TOS > 0) * ... x1 ... xN-1 xN -N -> xN x1 ... xN-1 (TOS < 0) * d duplicate the top-of-stack * ^ pop the top-of-stack and throw it away * +,-,*,/,% * perform the indicated arithmetic operation * &,|,~ perform the indicated bitwise logical operation * f+x add flag x (at end) to flags * f-x remove flag x from flags, no error if absent * F+x add flag x, making it the Nth flag, where N is * top-of-stack (1-origin); if N is out-of-range, error. * F-x remove Nth flag x from flags, where N is top-of-stack * (1 origin); if N is out-of-range, error. (If x is :, * as described below, and flag character code is ":, * removes Nth flag regardless of what the flags are.) * F!x replace Nth flag with x, where N is top-of-stack (1 * origin); if N is out-of-range, error. * F@x push index (1 origin) of first x flag, or 0 if none * F= push character code for Nth flag, where N is * top-of-stack (1 origin); if N is out-of-range, error. * W write this record to the file descriptor number taken * from the top-of-stack; see below for more. * X exit: return from the current definition, or if not * inside a definition, behave as if control had run off * the end of the program. * :name; * define "name" as , for use with $ * $name execute the definition of "name" * * Whitespace is ignored, except where a character is needed for a flag * or for a " construct, though it does serve to terminate * non-self-terminating constructs like numbers. Names in the : and $ * constructs may be any sequence of non-whitespace characters, even * including characters like ? and :, though good style advises * against the use of such confusing names. * * For operations which take a flag character (N, f, and some F), if * the flag character is : (which is not otherwise useful), the * top-of-stack value is taken as the flag character code. (For * operations which take something else on the stack, the flag code is * always above the other thing(s).) * * If the W operation does not appear anywhere in the program, an * implicit "1W" is done at the end; however, if any W calls occur * anywhere, regardless of whether they get executed, no implicit W is * performed, and no output is generated except as specified by * (executed) W calls. */ #include #include #include #include #include #include "deconst.h" #define STACKSIZE 1024 extern const char *__progname; #define CODE__FLAGS 0xffffff00 #define CODE__NUMBER 0x000000ff #define CODE_FOLLOW 0x00000100 #define CODE_COUNT 0x00000001 #define CODE_COUNT_x (0x00000002|CODE_FOLLOW) #define CODE_CONST (0x00000003|CODE_FOLLOW) #define CODE_CONST_ch (0x00000004|CODE_FOLLOW) #define CODE_BIN_LT 0x00000005 #define CODE_BIN_GT 0x00000006 #define CODE_BIN_EQ 0x00000007 #define CODE_BIN_ADD 0x00000008 #define CODE_BIN_SUB 0x00000009 #define CODE_BIN_MUL 0x0000000a #define CODE_BIN_DIV 0x0000000b #define CODE_BIN_MOD 0x0000000c #define CODE_BIN_AND 0x0000000d #define CODE_BIN_OR 0x0000000e #define CODE_UN_LNOT 0x0000000f #define CODE_UN_BNOT 0x00000010 #define CODE_IF (0x00000011|CODE_FOLLOW) #define CODE_JMP (0x00000012|CODE_FOLLOW) #define CODE_SWAP 0x00000013 #define CODE_ROT 0x00000014 #define CODE_ROTATE 0x00000015 #define CODE_DUP 0x00000016 #define CODE_POP 0x00000017 #define CODE_FOP_ADD 0x00000018 #define CODE_FOP_REM 0x00000019 #define CODE_FOP_ADD_N 0x0000001a #define CODE_FOP_REM_N 0x0000001b #define CODE_FOP_SET 0x0000001c #define CODE_FOP_INDEX 0x0000001d #define CODE_FOP_PUSH 0x0000001e #define CODE_CALL (0x0000001f|CODE_FOLLOW) #define CODE_RET 0x00000020 #define CODE_WRITE 0x00000021 #define CODE_DBG_ON 0x00000022 #define CODE_DBG_OFF 0x00000023 #define WHITESPACE ' ': case '\t': case '\r': case '\n' typedef struct date DATE; typedef struct def DEF; typedef struct str STR; struct date { unsigned int year : 12; #define MAXYEAR ((1<<12)-1) unsigned int month : 4; unsigned int day : 5; } ; struct def { DEF *link; const char *name; int off; } ; struct str { char *s; int l; } ; static int args_c; static char **args_v; static DEF *defs; static int codeoff; static int ncodevec; static int *codevec; static STR line = { 0, 0 }; static DATE date; static DATE edate; static STR flags; static STR category; static STR from; static STR to; static long int amount; static STR currency; static int stack[STACKSIZE]; static int depth; static int usedW; static int dbg; static char *bindex(const char *str, int len, char c) { char *s; s = deconst(str); for (;len>0;s++,len--) if (*s == c) return(s); return(0); } #define EOI (-1) static inline int peek(void) { if (**args_v) { return(**args_v); } else if (args_c < 2) { return(EOI); } else { return(' '); } } static void eat(void) { if (**args_v) { ++*args_v; } else if (args_c < 2) { abort(); } else { args_c --; args_v ++; } } static inline int get(void) { int c; c = peek(); if (c != EOI) eat(); return(c); } static void skipspace(void) { int c; while (1) { c = peek(); switch (c) { case WHITESPACE: break; default: return; break; } eat(); } } static char *readname(void) { int len; int have; char *buf; int c; skipspace(); buf = malloc(1); have = 0; len = 0; while (1) { c = get(); switch (c) { case WHITESPACE: case EOI: buf[len] = '\0'; return(buf); break; } if (len >= have) { have = len + 10; buf = realloc(buf,have+1); } buf[len++] = c; } } static DEF *find_def(const char *name, int how) #define FD_CREATE 1 #define FD_LOOKUP 2 { DEF *d; char *t; switch (how) { case FD_CREATE: case FD_LOOKUP: break; default: abort(); break; } for (d=defs;d;d=d->link) { if (!strcmp(d->name,name)) { switch (how) { case FD_CREATE: return(0); break; case FD_LOOKUP: return(d); break; } } } switch (how) { case FD_CREATE: t = malloc(strlen(name)+1); strcpy(t,name); d = malloc(sizeof(DEF)); d->name = t; d->off = -1; d->link = defs; defs = d; return(d); break; case FD_LOOKUP: return(0); break; } abort(); } static inline void die(void) __attribute__ ((noreturn)); static inline void die(void) { exit(0); } static void fatal(const char *, ...) __attribute__ ((format(printf,1,2),noreturn)); static void fatal(const char *fmt, ...) { va_list ap; if (*fmt) { fprintf(stderr,"%s: program error: ",__progname); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); } die(); } static inline void codegen(int code) { if (codeoff >= ncodevec) { ncodevec = codeoff + 64; codevec = realloc(codevec,ncodevec*sizeof(*codevec)); } if (dbg) printf("codegen: %d at %d\n",code,codeoff); codevec[codeoff++] = code; } static inline void codegen_at(int off, int code) { if ((off < 0) || (off >= codeoff)) abort(); if (dbg) printf("codegen_at: %d at %d\n",code,off); codevec[off] = code; } static void parse_(int toplevel) { int c; int noeat; while (1) { c = peek(); noeat = 0; switch (c) { case WHITESPACE: break; case 'n': codegen(CODE_COUNT); break; case 'N': eat(); c = peek(); if (c == EOI) fatal("end-of-program after N"); codegen(CODE_COUNT_x); codegen(c); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int n; n = 0; while ((c >= '0') && (c <= '9')) { n = (10 * n) + (c - '0'); eat(); c = peek(); } codegen(CODE_CONST); codegen(n); noeat = 1; } break; case '"': eat(); c = peek(); if (c == EOI) fatal("end-of-program after \""); codegen(CODE_CONST_ch); codegen(c); break; case '<': codegen(CODE_BIN_LT); break; case '>': codegen(CODE_BIN_GT); break; case '=': codegen(CODE_BIN_EQ); break; case '+': codegen(CODE_BIN_ADD); break; case '-': codegen(CODE_BIN_SUB); break; case '*': codegen(CODE_BIN_MUL); break; case '/': codegen(CODE_BIN_DIV); break; case '%': codegen(CODE_BIN_MOD); break; case '&': codegen(CODE_BIN_AND); break; case '|': codegen(CODE_BIN_OR); break; case '!': codegen(CODE_UN_LNOT); break; case '~': codegen(CODE_UN_BNOT); break; case 'x': codegen(CODE_SWAP); break; case 'r': codegen(CODE_ROT); break; case 'R': codegen(CODE_ROTATE); break; case 'd': codegen(CODE_DUP); break; case '^': codegen(CODE_POP); break; case 'X': codegen(CODE_RET); break; case 'W': usedW = 1; codegen(CODE_WRITE); break; case 'f': { int op; eat(); c = get(); if (c == EOI) fatal("end-of-program after f"); switch (c) { case '+': op = CODE_FOP_ADD; break; case '-': op = CODE_FOP_REM; break; default: fatal("bad character %c after f",c); break; } c = peek(); if (c == EOI) fatal("end-of-program after f"); if (c != ':') { codegen(CODE_CONST_ch); codegen(c); } codegen(op); } break; case 'F': { int op; eat(); c = peek(); if (c == EOI) fatal("end-of-program after F"); switch (c) { case '+': op = CODE_FOP_ADD_N; if (0) { case '-': op = CODE_FOP_REM_N; } if (0) { case '!': op = CODE_FOP_SET; } if (0) { case '@': op = CODE_FOP_INDEX; } eat(); c = peek(); if (c == EOI) fatal("end-of-program after F"); if (c != ':') { codegen(CODE_CONST_ch); codegen(c); } codegen(op); break; case '=': codegen(CODE_FOP_PUSH); break; default: fatal("bad character %c after F",c); break; } } break; case '?': { int br_at; codegen(CODE_IF); br_at = codeoff; codegen(0); eat(); parse_(0); c = peek(); if (c == '#') { int br2_at; eat(); codegen(CODE_JMP); br2_at = codeoff; codegen(0); codegen_at(br_at,codeoff); br_at = br2_at; parse_(0); c = peek(); } if (c != '.') fatal("improperly closed conditional"); codegen_at(br_at,codeoff); } break; case '(': { int topoff; int br_at; topoff = codeoff; eat(); parse_(0); c = peek(); if (c != '@') fatal("improperly constructed loop"); eat(); codegen(CODE_IF); br_at = codeoff; codegen(0); parse_(0); c = peek(); if (c != ')') fatal("improperly closed loop"); codegen(CODE_JMP); codegen(topoff); codegen_at(br_at,codeoff); } break; case ':': { char *name; DEF *def; int br_at; if (! toplevel) fatal("definition not at top level"); eat(); name = readname(); def = find_def(name,FD_CREATE); if (! def) fatal("redefinition of name %s",name); codegen(CODE_JMP); br_at = codeoff; codegen(0); def->off = codeoff; parse_(0); c = peek(); if (c != ';') fatal("definition not ended with ;"); codegen(CODE_RET); codegen_at(br_at,codeoff); } break; case '$': { char *name; DEF *def; eat(); name = readname(); def = find_def(name,FD_LOOKUP); if (! def) fatal("use of %s with no prior definition",name); noeat = 1; codegen(CODE_CALL); codegen(def->off); } break; case '\4': eat(); c = peek(); switch (c) { case '+': codegen(CODE_DBG_ON); break; case '-': codegen(CODE_DBG_OFF); break; case '!': dbg = 1; break; default: fatal("invalid debugging subcommand"); break; } break; default: return; break; } if (! noeat) eat(); } } static int disassem(int off) { int op; int next; int rv; if ((off < 0) || (off >= codeoff)) abort(); op = codevec[off]; rv = 1; if (op & CODE_FOLLOW) { if (off+1 >= codeoff) abort(); next = codevec[off+1]; rv ++; } switch (op) { case CODE_COUNT: printf("COUNT"); break; case CODE_COUNT_x: printf("COUNT_x \"%c",next); break; case CODE_CONST: printf("CONST %d",next); break; case CODE_CONST_ch: printf("CONST \"%c",next); break; case CODE_BIN_LT: printf("BIN_LT"); break; case CODE_BIN_GT: printf("BIN_GT"); break; case CODE_BIN_EQ: printf("BIN_EQ"); break; case CODE_BIN_ADD: printf("BIN_ADD"); break; case CODE_BIN_SUB: printf("BIN_SUB"); break; case CODE_BIN_MUL: printf("BIN_MUL"); break; case CODE_BIN_DIV: printf("BIN_DIV"); break; case CODE_BIN_MOD: printf("BIN_MOD"); break; case CODE_BIN_AND: printf("BIN_AND"); break; case CODE_BIN_OR: printf("BIN_OR"); break; case CODE_UN_LNOT: printf("UN_LNOT"); break; case CODE_UN_BNOT: printf("UN_BNOT"); break; case CODE_IF: printf("IF %d",next); break; case CODE_JMP: printf("JMP %d",next); break; case CODE_SWAP: printf("SWAP"); break; case CODE_ROT: printf("ROT"); break; case CODE_ROTATE: printf("ROTATE"); break; case CODE_DUP: printf("DUP"); break; case CODE_POP: printf("POP"); break; case CODE_FOP_ADD: printf("FOP_ADD"); break; case CODE_FOP_REM: printf("FOP_REM"); break; case CODE_FOP_ADD_N: printf("FOP_ADD_N"); break; case CODE_FOP_REM_N: printf("FOP_REM_N"); break; case CODE_FOP_SET: printf("FOP_SET"); break; case CODE_FOP_INDEX: printf("FOP_INDEX"); break; case CODE_FOP_PUSH: printf("FOP_PUSH"); break; case CODE_CALL: printf("CALL %d",next); break; case CODE_RET: printf("RET"); break; case CODE_WRITE: printf("WRITE"); break; case CODE_DBG_ON: printf("DBG_ON"); break; case CODE_DBG_OFF: printf("DBG_OFF"); break; default: if (op & CODE_FOLLOW) { printf("?%d/%d",op,next); } else { printf("?%d",op); } break; } return(rv); } static void parse(void) { parse_(1); if (peek() != EOI) { int c; printf("%s: junk after program: ",__progname); while (1) { c = get(); if (c == EOI) break; putchar(c); } printf("\n"); die(); } codegen(CODE_RET); if (dbg) { int i; i = 0; while (i < codeoff) { printf("%4d: ",i); i += disassem(i); printf("\n"); } } } static inline unsigned int monthdays(unsigned int, unsigned int) __attribute__ ((const)); static inline unsigned int monthdays(unsigned int m, unsigned int y) { if (m == 2) { if (y % 4) return(28); if (y % 100) return(29); if (y % 400) return(28); return(29); } /* JanFebMarAprMayJunJulAugSepOctNovDec */ return(" \37\00\37\36\37\36\37\37\36\37\36\37"[m]); } static int trans_date(const char *bp, DATE *dp) { int i; unsigned int y; unsigned int m; unsigned int d; d = 0; for (i=0;i<8;i++) { if ((bp[i] < '0') || (bp[i] > '9')) return(0); d = (10 * d) + (bp[i] - '0'); } y = d / 10000; m = (d / 100) % 100; d %= 100; if (y > MAXYEAR) return(0); if ((m < 1) || (m > 12)) return(0); if ((d < 1) || (d > monthdays(m,y))) return(0); dp->year = y; dp->month = m; dp->day = d; return(1); } static int parse_trans(void) { int i; int l; char *c0; char *c1; char *c2; char *c3; int neg; l = line.l; if (l < 22) return(0); if ((line.s[8] != ':') || (line.s[17] != ':')) return(0); if (!trans_date(line.s,&date) || !trans_date(line.s+9,&edate)) return(0); l -= 18; c0 = bindex(line.s+18,l,':'); if (c0 == 0) return(0); l -= c0 - (line.s+18); c1 = bindex(c0+1,l-1,':'); if (c1 == 0) return(0); l -= c1 - c0; c2 = bindex(c1+1,l-1,':'); if (c2 == 0) return(0); l -= c2 - c1; c3 = bindex(c2+1,l-1,':'); if (c3 == 0) return(0); l -= c3 - c2; if (l < 2) return(0); if (c3+l != line.s+line.l) abort(); flags.l = c0 - (line.s + 18); flags.s = malloc(flags.l); bcopy(line.s+18,flags.s,flags.l); category.s = c0 + 1; category.l = c1 - category.s; from.s = c1 + 1; from.l = c2 - from.s; to.s = c2 + 1; to.l = c3 - to.s; amount = 0; neg = 0; i = 1; if (c3[i] == '-') { neg = 1; i ++; } for (;i '9')) return(0); amount = (amount * 10) + (c3[i] - '0'); } if (neg) amount = - amount; if (i < l) { currency.s = c3 + i + 1; currency.l = l - (i + 1); } else { currency.s = 0; currency.l = 0; } return(1); } static int read_trans(void) { int c; char *buf; int have; int len; buf = 0; have = 0; len = 0; while (1) { c = getchar(); if (c == EOF) { if (len > 0) { fprintf(stderr,"%s: warning: unterminated last line ignored\n",__progname); } return(0); } if (c == '\n') { free(line.s); line.s = buf; line.l = len; if (parse_trans()) return(1); fprintf(stderr,"%s: warning: malformed input line ignored: ",__progname); fwrite(line.s,1,line.l,stderr); fprintf(stderr,"\n"); buf = 0; have = 0; len = 0; continue; } while (len >= have) buf = realloc(buf,have+=8); buf[len++] = c; } } static void write_trans(int fd) { char datesbuf[19]; char amtbuf[64]; struct iovec iov[11]; static char colon = ':'; static char newline = '\n'; sprintf(&amtbuf[0],":%ld:",amount); sprintf(&datesbuf[0],"%04d%02d%02d:%04d%02d%02d:",date.year,date.month,date.day,edate.year,edate.month,edate.day); iov[0].iov_base = &datesbuf[0]; iov[0].iov_len = 18; iov[1].iov_base = flags.s; iov[1].iov_len = flags.l; iov[2].iov_base = : iov[2].iov_len = 1; iov[3].iov_base = category.s; iov[3].iov_len = category.l; iov[4].iov_base = : iov[4].iov_len = 1; iov[5].iov_base = from.s; iov[5].iov_len = from.l; iov[6].iov_base = : iov[6].iov_len = 1; iov[7].iov_base = to.s; iov[7].iov_len = to.l; iov[8].iov_base = &amtbuf[0]; iov[8].iov_len = strlen(&amtbuf[0]); iov[9].iov_base = currency.s; iov[9].iov_len = currency.l; iov[10].iov_base = &newline; iov[10].iov_len = 1; writev(fd,&iov[0],11); } static void free_trans(void) { free(flags.s); } static int pop(void) { if (depth < 1) fatal("stack underflow"); return(stack[--depth]); } static void push(int x) { if (depth >= STACKSIZE) fatal("stack overflow"); stack[depth++] = x; } static void runcode(int pc) { int op; int next; while (1) { if (dbg) { int i; printf("runcode: flags=["); fwrite(flags.s,1,flags.l,stdout); printf("] stack=[%d] (",depth); if (depth > 10) printf(" ..."); for (i=(depth>10)?depth-10:0;i= codeoff)) abort(); op = codevec[pc++]; if (op & CODE_FOLLOW) { if (pc >= codeoff) abort(); next = codevec[pc++]; } switch (op) { case CODE_COUNT: push(flags.l); break; case CODE_COUNT_x: { int n; int i; int f; f = (next == ':') ? pop() : next; n = 0; for (i=0;i rhs; break; case CODE_BIN_EQ: rv = lhs == rhs;break; case CODE_BIN_ADD: rv = lhs + rhs; break; case CODE_BIN_SUB: rv = lhs - rhs; break; case CODE_BIN_MUL: rv = lhs * rhs; break; case CODE_BIN_DIV: rv = lhs / rhs; break; case CODE_BIN_MOD: rv = lhs % rhs; break; case CODE_BIN_AND: rv = lhs & rhs; break; case CODE_BIN_OR: rv = lhs | rhs; break; } push(rv); } break; case CODE_UN_LNOT: push(!pop()); break; case CODE_UN_BNOT: push(~pop()); break; case CODE_IF: if (! pop()) pc = next; break; case CODE_JMP: pc = next; break; case CODE_SWAP: { int n1; int n2; n1 = pop(); n2 = pop(); push(n1); push(n2); } break; case CODE_ROT: { int n1; int n2; int n3; n1 = pop(); n2 = pop(); n3 = pop(); push(n2); push(n1); push(n3); } break; case CODE_ROTATE: { int n; int t; n = pop(); if (n == 0) break; if (n > 0) { if (n > depth) fatal("stack underflow"); if (n > 1) { t = stack[depth-n]; bcopy(&stack[depth+1-n],&stack[depth-n],(n-1)*sizeof(stack[0])); stack[depth-1] = t; } } else { n = - n; if (n > depth) fatal("stack underflow"); if (n > 1) { t = stack[depth-1]; bcopy(&stack[depth-n],&stack[depth+1-n],(n-1)*sizeof(stack[0])); stack[depth-n] = t; } } } break; case CODE_DUP: { int n; n = pop(); push(n); push(n); } break; case CODE_POP: pop(); break; case CODE_FOP_ADD: flags.s = realloc(flags.s,flags.l+1); flags.s[flags.l++] = pop(); break; case CODE_FOP_REM: { int i; int f; f = pop(); for (i=0;i flags.l)) fatal("F+ number out of range"); flags.s = realloc(flags.s,flags.l); if (n < flags.l) bcopy(&flags.s[n-1],&flags.s[n],flags.l-n); flags.s[n-1] = f; } break; case CODE_FOP_REM_N: { int n; int i; int f; f = pop(); n = pop(); if (n < 1) fatal("F- number out of range"); if (f == ':') { i = n - 1; } else { for (i=0;i= flags.l) fatal("F- number out of range"); flags.l --; if (i < flags.l) bcopy(&flags.s[i+1],&flags.s[i],flags.l-i); } break; case CODE_FOP_SET: { int n; int f; f = pop(); n = pop(); if ((n < 1) || (n > flags.l)) fatal("F! number out of range"); flags.s[n-1] = f; } break; case CODE_FOP_INDEX: { int i; int f; f = pop(); for (i=0;i flags.l)) fatal("F= number out of range"); push(flags.s[n-1]); } break; case CODE_CALL: runcode(next); break; case CODE_RET: return; break; case CODE_WRITE: { int fd; fd = pop(); write_trans(fd); } break; case CODE_DBG_ON: dbg = 1; break; case CODE_DBG_OFF: dbg = 0; break; } } } int main(int, char **); int main(int ac, char **av) { args_c = ac - 1; args_v = av + 1; defs = 0; codeoff = 0; ncodevec = 0; codevec = 0; usedW = 0; parse(); while (read_trans()) { depth = 0; runcode(0); if (! usedW) write_trans(1); free_trans(); } exit(0); }