#include        <u.h>
#include        <libc.h>
#include        <bio.h>

/*
bugs:
       00/ff for end of file can conflict with 00/ff characters
*/

enum
{
       Nline   = 100000,               /* default max number of lines saved in memory */
       Nmerge  = 10,                   /* max number of temporary files merged */
       Nfield  = 20,                   /* max number of argument fields */

       Bflag   = 1<<0,                 /* flags per field */
       B1flag  = 1<<1,

       Dflag   = 1<<2,
       Fflag   = 1<<3,
       Gflag   = 1<<4,
       Iflag   = 1<<5,
       Mflag   = 1<<6,
       Nflag   = 1<<7,
       Rflag   = 1<<8,
       Wflag   = 1<<9,

       NSstart = 0,                    /* states for number to key decoding */
       NSsign,
       NSzero,
       NSdigit,
       NSpoint,
       NSfract,
       NSzerofract,
       NSexp,
       NSexpsign,
       NSexpdigit,
};

typedef struct  Line    Line;
typedef struct  Key     Key;
typedef struct  Merge   Merge;
typedef struct  Field   Field;

struct  Line
{
       Key*    key;
       int     llen;           /* always >= 1 */
       uchar   line[1];        /* always ends in '\n' */
};

struct  Merge
{
       Key*    key;            /* copy of line->key so (Line*) looks like (Merge*) */
       Line*   line;           /* line at the head of a merged temp file */
       int     fd;             /* file descriptor */
       Biobuf  b;              /* iobuf for reading a temp file */
};

struct  Key
{
       int     klen;
       uchar   key[1];
};

struct  Field
{
       int     beg1;
       int     beg2;
       int     end1;
       int     end2;

       long    flags;
       uchar   mapto[256];

       void    (*dokey)(Key*, uchar*, uchar*, Field*);
};

struct args
{
       char*   ofile;
       char*   tname;
       Rune    tabchar;
       char    cflag;
       char    uflag;
       char    vflag;
       int     nfield;
       int     nfile;
       Field   field[Nfield];

       Line**  linep;
       long    nline;                  /* number of lines in this temp file */
       long    lineno;                 /* overall ordinal for -s option */
       int     ntemp;
       long    mline;                  /* max lines per file */
} args;

extern  int     latinmap[];
extern  Rune*   month[12];

void    buildkey(Line*);
void    doargs(int, char*[]);
void    dofield(char*, int*, int*, int, int);
void    dofile(Biobuf*);
void    dokey_(Key*, uchar*, uchar*, Field*);
void    dokey_dfi(Key*, uchar*, uchar*, Field*);
void    dokey_gn(Key*, uchar*, uchar*, Field*);
void    dokey_m(Key*, uchar*, uchar*, Field*);
void    dokey_r(Key*, uchar*, uchar*, Field*);
void    done(char*);
void*   emalloc(ulong);
void*   erealloc(void*, ulong);
int     kcmp(Key*, Key*);
void    makemapd(Field*);
void    makemapm(Field*);
void    mergefiles(int, int, Biobuf*);
void    mergeout(Biobuf*);
void    newfield(void);
Line*   newline(Biobuf*);
void    notifyf(void*, char*);
void    printargs(void);
void    printout(Biobuf*);
void    setfield(int, int);
uchar*  skip(uchar*, int, int, int, int);
void    sort4(void*, ulong);
char*   tempfile(int);
void    tempout(void);
void    lineout(Biobuf*, Line*);

void
main(int argc, char *argv[])
{
       int i, f;
       char *s;
       Biobuf bbuf;

       notify(notifyf);        /**/
       doargs(argc, argv);
       if(args.vflag)
               printargs();

       for(i=1; i<argc; i++) {
               if((s = argv[i]) == nil)
                       continue;
               if(strcmp(s, "-") == 0) {
                       Binit(&bbuf, 0, OREAD);
                       dofile(&bbuf);
                       Bterm(&bbuf);
                       continue;
               }
               f = open(s, OREAD);
               if(f < 0) {
                       fprint(2, "sort: open %s: %r\n", s);
                       done("open");
               }
               Binit(&bbuf, f, OREAD);
               dofile(&bbuf);
               Bterm(&bbuf);
               close(f);
       }
       if(args.nfile == 0) {
               Binit(&bbuf, 0, OREAD);
               dofile(&bbuf);
               Bterm(&bbuf);
       }
       if(args.cflag)
               done(nil);
       if(args.vflag)
               fprint(2, "=========\n");

       f = 1;
       if(args.ofile) {
               f = create(args.ofile, OWRITE, 0666);
               if(f < 0) {
                       fprint(2, "sort: create %s: %r\n", args.ofile);
                       done("create");
               }
       }

       Binit(&bbuf, f, OWRITE);
       if(args.ntemp) {
               tempout();
               mergeout(&bbuf);
       } else {
               printout(&bbuf);
       }
       Bterm(&bbuf);
       done(nil);
}

void
dofile(Biobuf *b)
{
       Line *l, *ol;
       int n;

       if(args.cflag) {
               if((ol = newline(b)) == nil)
                       return;
               for(;;) {
                       if((l = newline(b)) == nil)
                               break;
                       n = kcmp(ol->key, l->key);
                       if(n > 0 || (n == 0 && args.uflag)) {
                               fprint(2, "sort: -c file not in sort\n"); /**/
                               done("order");
                       }
                       free(ol->key);
                       free(ol);
                       ol = l;
               }
               return;
       }

       if(args.linep == nil)
               args.linep = emalloc(args.mline * sizeof(args.linep));
       for(;;) {
               if((l = newline(b)) == nil)
                       break;
               if(args.nline >= args.mline)
                       tempout();
               args.linep[args.nline] = l;
               args.nline++;
               args.lineno++;
       }
}

void
notifyf(void*, char *s)
{

       if(strcmp(s, "interrupt") == 0)
               done(nil);
       if(strcmp(s, "hangup") == 0)
               done(nil);
       if(strcmp(s, "kill") == 0)
               done(nil);
       if(strncmp(s, "sys: write on closed pipe", 25) == 0)
               done(nil);
       fprint(2, "sort: note: %s\n", s);
       abort();
}

Line*
newline(Biobuf *b)
{
       Line *l;
       char *p;
       int n, c;

       p = Brdline(b, '\n');
       n = Blinelen(b);
       if(p == nil) {
               if(n == 0)
                       return 0;
               l = nil;
               for(n=0;;) {
                       if((n & 31) == 0)
                               l = erealloc(l, sizeof(Line) +
                                       (n+31)*sizeof(l->line[0]));
                       c = Bgetc(b);
                       if(c < 0) {
                               fprint(2, "sort: newline added\n");
                               c = '\n';
                       }
                       if(l == nil)
                               sysfatal("bug: l == nil");
                       l->line[n++] = c;
                       if(c == '\n')
                               break;
               }
               l->llen = n;
               buildkey(l);
               return l;
       }
       l = emalloc(sizeof(Line) + (n-1)*sizeof(l->line[0]));
       l->llen = n;
       memmove(l->line, p, n);
       buildkey(l);
       return l;
}

void
lineout(Biobuf *b, Line *l)
{
       int n, m;

       n = l->llen;
       m = Bwrite(b, l->line, n);
       if(n != m)
               exits("write");
}

void
tempout(void)
{
       long n;
       Line **lp, *l;
       char *tf;
       int f;
       Biobuf tb;

       sort4(args.linep, args.nline);
       tf = tempfile(args.ntemp);
       args.ntemp++;
       f = create(tf, OWRITE, 0666);
       if(f < 0) {
               fprint(2, "sort: create %s: %r\n", tf);
               done("create");
       }

       Binit(&tb, f, OWRITE);
       lp = args.linep;
       for(n=args.nline; n>0; n--) {
               l = *lp++;
               lineout(&tb, l);
               free(l->key);
               free(l);
       }
       args.nline = 0;
       Bterm(&tb);
       close(f);
}

void
done(char *xs)
{
       int i;

       for(i=0; i<args.ntemp; i++)
               remove(tempfile(i));
       exits(xs);
}

void*
erealloc(void *v, ulong n)
{
       if((v = realloc(v, n)) == nil && n != 0){
               fprint(2, "realloc: %r\n");
               done("realloc");
       }

       return v;
}

void*
emalloc(ulong n)
{
       void *v;

       if((v = malloc(n)) == nil){
               fprint(2, "malloc: %r\n");
               done("malloc");
       }
       memset(v, 0, n);

       return v;
}

char*
tempfile(int n)
{
       static char file[100];
       static uint pid;
       char *dir;

       dir = "/tmp";
       if(args.tname)
               dir = args.tname;
       if(strlen(dir) >= nelem(file)-20) {
               fprint(2, "temp file directory name is too long: %s\n", dir);
               done("tdir");
       }

       if(pid == 0) {
               pid = getpid();
               if(pid == 0) {
                       pid = time(0);
                       if(pid == 0)
                               pid = 1;
               }
       }

       snprint(file, sizeof(file), "%s/sort.%.4d.%.4d", dir, pid%10000, n);
       return file;
}

void
mergeout(Biobuf *b)
{
       int n, i, f;
       char *tf;
       Biobuf tb;

       for(i=0; i<args.ntemp; i+=n) {
               n = args.ntemp - i;
               if(n > Nmerge) {
                       tf = tempfile(args.ntemp);
                       args.ntemp++;
                       f = create(tf, OWRITE, 0666);
                       if(f < 0) {
                               fprint(2, "sort: create %s: %r\n", tf);
                               done("create");
                       }
                       Binit(&tb, f, OWRITE);

                       n = Nmerge;
                       mergefiles(i, n, &tb);

                       Bterm(&tb);
                       close(f);
               } else
                       mergefiles(i, n, b);
       }
}

void
mergefiles(int t, int n, Biobuf *b)
{
       Merge *m, *mp, **mmp;
       Key *ok;
       Line *l;
       char *tf;
       int i, f, nn;

       mmp = emalloc(n*sizeof(*mmp));
       mp = emalloc(n*sizeof(*mp));

       nn = 0;
       m = mp;
       for(i=0; i<n; i++,m++) {
               tf = tempfile(t+i);
               f = open(tf, OREAD);
               if(f < 0) {
                       fprint(2, "sort: reopen %s: %r\n", tf);
                       done("open");
               }
               m->fd = f;
               Binit(&m->b, f, OREAD);
               mmp[nn] = m;

               if((l = newline(&m->b)) == nil)
                       continue;
               nn++;
               m->line = l;
               m->key = l->key;
       }

       ok = 0;
       for(;;) {
               sort4(mmp, nn);
               m = *mmp;
               if(nn == 0)
                       break;
               for(;;) {
                       l = m->line;
                       if(args.uflag && ok && kcmp(ok, l->key) == 0) {
                               free(l->key);
                               free(l);
                       } else {
                               lineout(b, l);
                               if(ok)
                                       free(ok);
                               ok = l->key;
                               free(l);
                       }

                       l = newline(&m->b);
                       if(l == nil) {
                               nn--;
                               mmp[0] = mmp[nn];
                               break;
                       }
                       m->line = l;
                       m->key = l->key;
                       if(nn > 1 && kcmp(mmp[0]->key, mmp[1]->key) > 0)
                               break;
               }
       }
       if(ok)
               free(ok);

       m = mp;
       for(i=0; i<n; i++,m++) {
               Bterm(&m->b);
               close(m->fd);
       }

       free(mp);
       free(mmp);
}

int
kcmp(Key *ka, Key *kb)
{
       int n, m;

       /*
        * set n to length of smaller key
        */
       n = ka->klen;
       m = kb->klen;
       if(n > m)
               n = m;
       return memcmp(ka->key, kb->key, n);
}

void
printout(Biobuf *b)
{
       long n;
       Line **lp, *l;
       Key *ok;

       sort4(args.linep, args.nline);
       lp = args.linep;
       ok = 0;
       for(n=args.nline; n>0; n--) {
               l = *lp++;
               if(args.uflag && ok && kcmp(ok, l->key) == 0)
                       continue;
               lineout(b, l);
               ok = l->key;
       }
}

void
setfield(int n, int c)
{
       Field *f;

       f = &args.field[n];
       switch(c) {
       default:
               fprint(2, "sort: unknown option: field.%C\n", c);
               done("option");
       case 'b':       /* skip blanks */
               f->flags |= Bflag;
               break;
       case 'd':       /* directory order */
               f->flags |= Dflag;
               break;
       case 'f':       /* fold case */
               f->flags |= Fflag;
               break;
       case 'g':       /* floating point -n case */
               f->flags |= Gflag;
               break;
       case 'i':       /* ignore non-ascii */
               f->flags |= Iflag;
               break;
       case 'M':       /* month */
               f->flags |= Mflag;
               break;
       case 'n':       /* numbers */
               f->flags |= Nflag;
               break;
       case 'r':       /* reverse */
               f->flags |= Rflag;
               break;
       case 'w':       /* ignore white */
               f->flags |= Wflag;
               break;
       }
}

void
dofield(char *s, int *n1, int *n2, int off1, int off2)
{
       int c, n;

       c = *s++;
       if(c >= '0' && c <= '9') {
               n = 0;
               while(c >= '0' && c <= '9') {
                       n = n*10 + (c-'0');
                       c = *s++;
               }
               n -= off1;      /* posix committee: rot in hell */
               if(n < 0) {
                       fprint(2, "sort: field offset must be positive\n");
                       done("option");
               }
               *n1 = n;
       }
       if(c == '.') {
               c = *s++;
               if(c >= '0' && c <= '9') {
                       n = 0;
                       while(c >= '0' && c <= '9') {
                               n = n*10 + (c-'0');
                               c = *s++;
                       }
                       n -= off2;
                       if(n < 0) {
                               fprint(2, "sort: character offset must be positive\n");
                               done("option");
                       }
                       *n2 = n;
               }
       }
       while(c != 0) {
               setfield(args.nfield, c);
               c = *s++;
       }
}

void
printargs(void)
{
       int i, n;
       Field *f;
       char *prefix;

       fprint(2, "sort");
       for(i=0; i<=args.nfield; i++) {
               f = &args.field[i];
               prefix = " -";
               if(i) {
                       n = f->beg1;
                       if(n >= 0)
                               fprint(2, " +%d", n);
                       else
                               fprint(2, " +*");
                       n = f->beg2;
                       if(n >= 0)
                               fprint(2, ".%d", n);
                       else
                               fprint(2, ".*");

                       if(f->flags & B1flag)
                               fprint(2, "b");

                       n = f->end1;
                       if(n >= 0)
                               fprint(2, " -%d", n);
                       else
                               fprint(2, " -*");
                       n = f->end2;
                       if(n >= 0)
                               fprint(2, ".%d", n);
                       else
                               fprint(2, ".*");
                       prefix = "";
               }
               if(f->flags & Bflag)
                       fprint(2, "%sb", prefix);
               if(f->flags & Dflag)
                       fprint(2, "%sd", prefix);
               if(f->flags & Fflag)
                       fprint(2, "%sf", prefix);
               if(f->flags & Gflag)
                       fprint(2, "%sg", prefix);
               if(f->flags & Iflag)
                       fprint(2, "%si", prefix);
               if(f->flags & Mflag)
                       fprint(2, "%sM", prefix);
               if(f->flags & Nflag)
                       fprint(2, "%sn", prefix);
               if(f->flags & Rflag)
                       fprint(2, "%sr", prefix);
               if(f->flags & Wflag)
                       fprint(2, "%sw", prefix);
       }
       if(args.cflag)
               fprint(2, " -c");
       if(args.uflag)
               fprint(2, " -u");
       if(args.ofile)
               fprint(2, " -o %s", args.ofile);
       if(args.mline != Nline)
               fprint(2, " -l %ld", args.mline);
       fprint(2, "\n");
}

void
newfield(void)
{
       int n;
       Field *f;

       n = args.nfield + 1;
       if(n >= Nfield) {
               fprint(2, "sort: too many fields specified\n");
               done("option");
       }
       args.nfield = n;
       f = &args.field[n];
       f->beg1 = -1;
       f->beg2 = -1;
       f->end1 = -1;
       f->end2 = -1;
}

void
doargs(int argc, char *argv[])
{
       int i, c, hadplus;
       char *s, *p, *q;
       Field *f;

       hadplus = 0;
       args.mline = Nline;
       for(i=1; i<argc; i++) {
               s = argv[i];
               c = *s++;
               if(c == '-') {
                       c = *s;
                       if(c == 0)              /* forced end of arg marker */
                               break;
                       argv[i] = 0;            /* clobber args processed */
                       if(c == '.' || (c >= '0' && c <= '9')) {
                               if(!hadplus)
                                       newfield();
                               f = &args.field[args.nfield];
                               dofield(s, &f->end1, &f->end2, 0, 0);
                               hadplus = 0;
                               continue;
                       }

                       while(c = *s++)
                       switch(c) {
                       case '-':       /* end of options */
                               i = argc;
                               continue;
                       case 'T':       /* temp directory */
                               if(*s == 0) {
                                       i++;
                                       if(i < argc) {
                                               args.tname = argv[i];
                                               argv[i] = 0;
                                       }
                               } else
                                       args.tname = s;
                               s = strchr(s, 0);
                               break;
                       case 'o':       /* output file */
                               if(*s == 0) {
                                       i++;
                                       if(i < argc) {
                                               args.ofile = argv[i];
                                               argv[i] = 0;
                                       }
                               } else
                                       args.ofile = s;
                               s = strchr(s, 0);
                               break;
                       case 'k':       /* posix key (what were they thinking?) */
                               p = nil;
                               if(*s == 0) {
                                       i++;
                                       if(i < argc) {
                                               p = argv[i];
                                               argv[i] = 0;
                                       }
                               } else
                                       p = s;
                               s = strchr(s, 0);
                               if(p == nil)
                                       break;

                               newfield();
                               q = strchr(p, ',');
                               if(q != nil)
                                       *q++ = 0;
                               f = &args.field[args.nfield];
                               dofield(p, &f->beg1, &f->beg2, 1, 1);
                               if(f->flags & Bflag) {
                                       f->flags |= B1flag;
                                       f->flags &= ~Bflag;
                               }
                               if(q != nil) {
                                       dofield(q, &f->end1, &f->end2, 1, 0);
                                       if(f->end2 <= 0)
                                               f->end1++;
                               }
                               hadplus = 0;
                               break;
                       case 't':       /* tab character */
                               if(*s == 0) {
                                       i++;
                                       if(i < argc) {
                                               chartorune(&args.tabchar, argv[i]);
                                               argv[i] = 0;
                                       }
                               } else
                                       s += chartorune(&args.tabchar, s);
                               if(args.tabchar == '\n') {
                                       fprint(2, "aw come on, rob\n");
                                       done("rob");
                               }
                               break;
                       case 'c':       /* check order */
                               args.cflag = 1;
                               break;
                       case 'u':       /* unique */
                               args.uflag = 1;
                               break;
                       case 'v':       /* debugging noise */
                               args.vflag = 1;
                               break;
                       case 'l':
                               if(*s == 0) {
                                       i++;
                                       if(i < argc) {
                                               args.mline = atol(argv[i]);
                                               argv[i] = 0;
                                       }
                               } else
                                       args.mline = atol(s);
                               s = strchr(s, 0);
                               break;

                       case 'M':       /* month */
                       case 'b':       /* skip blanks */
                       case 'd':       /* directory order */
                       case 'f':       /* fold case */
                       case 'g':       /* floating numbers */
                       case 'i':       /* ignore non-ascii */
                       case 'n':       /* numbers */
                       case 'r':       /* reverse */
                       case 'w':       /* ignore white */
                               if(args.nfield > 0)
                                       fprint(2, "sort: global field set after -k\n");
                               setfield(0, c);
                               break;
                       case 'm':
                               /* option m silently ignored but required by posix */
                               break;
                       default:
                               fprint(2, "sort: unknown option: -%C\n", c);
                               done("option");
                       }
                       continue;
               }
               if(c == '+') {
                       argv[i] = 0;            /* clobber args processed */
                       c = *s;
                       if(c == '.' || (c >= '0' && c <= '9')) {
                               newfield();
                               f = &args.field[args.nfield];
                               dofield(s, &f->beg1, &f->beg2, 0, 0);
                               if(f->flags & Bflag) {
                                       f->flags |= B1flag;
                                       f->flags &= ~Bflag;
                               }
                               hadplus = 1;
                               continue;
                       }
                       fprint(2, "sort: unknown option: +%C\n", c);
                       done("option");
               }
               args.nfile++;
       }

       for(i=0; i<=args.nfield; i++) {
               f = &args.field[i];

               /*
                * global options apply to fields that
                * specify no options
                */
               if(f->flags == 0) {
                       f->flags = args.field[0].flags;
                       if(args.field[0].flags & Bflag)
                               f->flags |= B1flag;
               }


               /*
                * build buildkey specification
                */
               switch(f->flags & ~(Bflag|B1flag)) {
               default:
                       fprint(2, "sort: illegal combination of flags: %lx\n", f->flags);
                       done("option");
               case 0:
                       f->dokey = dokey_;
                       break;
               case Rflag:
                       f->dokey = dokey_r;
                       break;
               case Gflag:
               case Nflag:
               case Gflag|Nflag:
               case Gflag|Rflag:
               case Nflag|Rflag:
               case Gflag|Nflag|Rflag:
                       f->dokey = dokey_gn;
                       break;
               case Mflag:
               case Mflag|Rflag:
                       f->dokey = dokey_m;
                       makemapm(f);
                       break;
               case Dflag:
               case Dflag|Fflag:
               case Dflag|Fflag|Iflag:
               case Dflag|Fflag|Iflag|Rflag:
               case Dflag|Fflag|Iflag|Rflag|Wflag:
               case Dflag|Fflag|Iflag|Wflag:
               case Dflag|Fflag|Rflag:
               case Dflag|Fflag|Rflag|Wflag:
               case Dflag|Fflag|Wflag:
               case Dflag|Iflag:
               case Dflag|Iflag|Rflag:
               case Dflag|Iflag|Rflag|Wflag:
               case Dflag|Iflag|Wflag:
               case Dflag|Rflag:
               case Dflag|Rflag|Wflag:
               case Dflag|Wflag:
               case Fflag:
               case Fflag|Iflag:
               case Fflag|Iflag|Rflag:
               case Fflag|Iflag|Rflag|Wflag:
               case Fflag|Iflag|Wflag:
               case Fflag|Rflag:
               case Fflag|Rflag|Wflag:
               case Fflag|Wflag:
               case Iflag:
               case Iflag|Rflag:
               case Iflag|Rflag|Wflag:
               case Iflag|Wflag:
               case Wflag:
                       f->dokey = dokey_dfi;
                       makemapd(f);
                       break;
               }
       }

       /*
        * random spot checks
        */
       if(args.nfile > 1 && args.cflag) {
               fprint(2, "sort: -c can have at most one input file\n");
               done("option");
       }
       return;
}

uchar*
skip(uchar *l, int n1, int n2, int bflag, int endfield)
{
       int i, c, ln, tc;
       Rune r;

       if(endfield && n1 < 0)
               return 0;

       c = *l++;
       ln = 1;
       tc = args.tabchar;
       if(tc) {
               if(tc < Runeself) {
                       for(i=n1; i>0; i--) {
                               while(c != tc) {
                                       if(c == '\n')
                                               return 0;
                                       c = *l++;
                               }
                               if(!(endfield && i == 1))
                                       c = *l++;
                       }
               } else {
                       l--;
                       l += ln = chartorune(&r, (char*)l);
                       for(i=n1; i>0; i--) {
                               while(r != tc) {
                                       if(r == '\n')
                                               return 0;
                                       l += ln = chartorune(&r, (char*)l);
                               }
                               if(!(endfield && i == 1))
                                       l += ln = chartorune(&r, (char*)l);
                       }
                       c = r;
               }
       } else {
               for(i=n1; i>0; i--) {
                       while(c == ' ' || c == '\t')
                               c = *l++;
                       while(c != ' ' && c != '\t') {
                               if(c == '\n')
                                       return 0;
                               c = *l++;
                       }
               }
       }

       if(bflag)
               while(c == ' ' || c == '\t'){
                       c = *l++;
                       ln = 1;
               }

       l -= ln;
       for(i=n2; i>0; i--) {
               c = *l;
               if(c < Runeself) {
                       if(c == '\n')
                               return 0;
                       l++;
                       continue;
               }
               l += chartorune(&r, (char*)l);
       }
       return l;
}

void
dokey_gn(Key *k, uchar *lp, uchar *lpe, Field *f)
{
       uchar *kp;
       int c, cl, dp;
       int state, nzero, exp, expsign, rflag;

       cl = k->klen + 3;
       kp = k->key + cl;       /* skip place for sign, exponent[2] */

       nzero = 0;              /* number of trailing zeros */
       exp = 0;                /* value of the exponent */
       expsign = 0;            /* sign of the exponent */
       dp = 0x4040;            /* location of decimal point */
       rflag = f->flags&Rflag; /* xor of rflag and - sign */
       state = NSstart;

       for(;; lp++) {
               if(lp >= lpe)
                       break;
               c = *lp;

               if(c == ' ' || c == '\t') {
                       switch(state) {
                       case NSstart:
                       case NSsign:
                               continue;
                       }
                       break;
               }
               if(c == '+' || c == '-') {
                       switch(state) {
                       case NSstart:
                               state = NSsign;
                               if(c == '-')
                                       rflag = !rflag;
                               continue;
                       case NSexp:
                               state = NSexpsign;
                               if(c == '-')
                                       expsign = 1;
                               continue;
                       }
                       break;
               }
               if(c == '0') {
                       switch(state) {
                       case NSdigit:
                               if(rflag)
                                       c = ~c;
                               *kp++ = c;
                               cl++;
                               nzero++;
                               dp++;
                               state = NSdigit;
                               continue;
                       case NSfract:
                               if(rflag)
                                       c = ~c;
                               *kp++ = c;
                               cl++;
                               nzero++;
                               state = NSfract;
                               continue;
                       case NSstart:
                       case NSsign:
                       case NSzero:
                               state = NSzero;
                               continue;
                       case NSzerofract:
                       case NSpoint:
                               dp--;
                               state = NSzerofract;
                               continue;
                       case NSexpsign:
                       case NSexp:
                       case NSexpdigit:
                               exp = exp*10 + (c - '0');
                               state = NSexpdigit;
                               continue;
                       }
                       break;
               }
               if(c >= '1' && c <= '9') {
                       switch(state) {
                       case NSzero:
                       case NSstart:
                       case NSsign:
                       case NSdigit:
                               if(rflag)
                                       c = ~c;
                               *kp++ = c;
                               cl++;
                               nzero = 0;
                               dp++;
                               state = NSdigit;
                               continue;
                       case NSzerofract:
                       case NSpoint:
                       case NSfract:
                               if(rflag)
                                       c = ~c;
                               *kp++ = c;
                               cl++;
                               nzero = 0;
                               state = NSfract;
                               continue;
                       case NSexpsign:
                       case NSexp:
                       case NSexpdigit:
                               exp = exp*10 + (c - '0');
                               state = NSexpdigit;
                               continue;
                       }
                       break;
               }
               if(c == '.') {
                       switch(state) {
                       case NSstart:
                       case NSsign:
                               state = NSpoint;
                               continue;
                       case NSzero:
                               state = NSzerofract;
                               continue;
                       case NSdigit:
                               state = NSfract;
                               continue;
                       }
                       break;
               }
               if((f->flags & Gflag) && (c == 'e' || c == 'E')) {
                       switch(state) {
                       case NSdigit:
                       case NSfract:
                               state = NSexp;
                               continue;
                       }
                       break;
               }
               break;
       }

       switch(state) {
       /*
        * result is zero
        */
       case NSstart:
       case NSsign:
       case NSzero:
       case NSzerofract:
       case NSpoint:
               kp = k->key + k->klen;
               k->klen += 2;
               kp[0] = 0x20;   /* between + and - */
               kp[1] = 0;
               return;
       /*
        * result has exponent
        */
       case NSexpsign:
       case NSexp:
       case NSexpdigit:
               if(expsign)
                       exp = -exp;
               dp += exp;

       /*
        * result is fixed point number
        */
       case NSdigit:
       case NSfract:
               kp -= nzero;
               cl -= nzero;
               break;
       }

       /*
        * end of number
        */
       c = 0;
       if(rflag)
               c = ~c;
       *kp = c;

       /*
        * sign and exponent
        */
       c = 0x30;
       if(rflag) {
               c = 0x10;
               dp = ~dp;
       }
       kp = k->key + k->klen;
       kp[0] = c;
       kp[1] = (dp >> 8);
       kp[2] = dp;
       k->klen = cl+1;
}

void
dokey_m(Key *k, uchar *lp, uchar *lpe, Field *f)
{
       uchar *kp;
       Rune r, place[3];
       int c, cl, pc;
       int rflag;

       rflag = f->flags&Rflag;
       pc = 0;

       cl = k->klen;
       kp = k->key + cl;

       for(;;) {
               /*
                * get the character
                */
               if(lp >= lpe)
                       break;
               c = *lp;
               if(c >= Runeself) {
                       lp += chartorune(&r, (char*)lp);
                       c = r;
               } else
                       lp++;

               if(c < nelem(f->mapto)) {
                       c = f->mapto[c];
                       if(c == 0)
                               continue;
               }
               place[pc++] = c;
               if(pc < 3)
                       continue;
               for(c=11; c>=0; c--)
                       if(memcmp(month[c], place, sizeof(place)) == 0)
                               break;
               c += 10;
               if(rflag)
                       c = ~c;
               *kp++ = c;
               cl++;
               break;
       }

       c = 0;
       if(rflag)
               c = ~c;
       *kp = c;
       k->klen = cl+1;
}

void
dokey_dfi(Key *k, uchar *lp, uchar *lpe, Field *f)
{
       uchar *kp;
       Rune r;
       int c, cl, n, rflag;

       cl = k->klen;
       kp = k->key + cl;
       rflag = f->flags & Rflag;

       for(;;) {
               /*
                * get the character
                */
               if(lp >= lpe)
                       break;
               c = *lp;
               if(c >= Runeself) {
                       lp += chartorune(&r, (char*)lp);
                       c = r;
               } else
                       lp++;

               /*
                * do the various mappings.
                * the common case is handled
                * completely by the table.
                */
               if(c != 0 && c < Runeself) {
                       c = f->mapto[c];
                       if(c) {
                               *kp++ = c;
                               cl++;
                       }
                       continue;
               }

               /*
                * for characters out of range,
                * the table does not do Rflag.
                * ignore is based on mapto[255]
                */
               if(c != 0 && c < nelem(f->mapto)) {
                       c = f->mapto[c];
                       if(c == 0)
                               continue;
               } else
                       if(f->mapto[nelem(f->mapto)-1] == 0)
                               continue;

               /*
                * put it in the key
                */
               r = c;
               n = runetochar((char*)kp, &r);
               kp += n;
               cl += n;
               if(rflag)
                       while(n > 0) {
                               kp[-n] = ~kp[-n];
                               n--;
                       }
       }

       /*
        * end of key
        */
       k->klen = cl+1;
       if(rflag) {
               *kp = ~0;
               return;
       }
       *kp = 0;
}

void
dokey_r(Key *k, uchar *lp, uchar *lpe, Field*)
{
       int cl, n;
       uchar *kp;

       n = lpe - lp;
       if(n < 0)
               n = 0;
       cl = k->klen;
       kp = k->key + cl;
       k->klen = cl+n+1;

       lpe -= 3;
       while(lp < lpe) {
               kp[0] = ~lp[0];
               kp[1] = ~lp[1];
               kp[2] = ~lp[2];
               kp[3] = ~lp[3];
               kp += 4;
               lp += 4;
       }

       lpe += 3;
       while(lp < lpe)
               *kp++ = ~*lp++;
       *kp = ~0;
}

void
dokey_(Key *k, uchar *lp, uchar *lpe, Field*)
{
       int n, cl;
       uchar *kp;

       n = lpe - lp;
       if(n < 0)
               n = 0;
       cl = k->klen;
       kp = k->key + cl;
       k->klen = cl+n+1;
       memmove(kp, lp, n);
       kp[n] = 0;
}

void
buildkey(Line *l)
{
       Key *k;
       uchar *lp, *lpe;
       int ll, kl, cl, i, n;
       Field *f;

       ll = l->llen - 1;
       kl = 0;                 /* allocated length */
       cl = 0;                 /* current length */
       k = 0;

       for(i=1; i<=args.nfield; i++) {
               f = &args.field[i];
               if((lp = skip(l->line, f->beg1, f->beg2, f->flags&B1flag, 0)) == nil)
                       lp = l->line + ll;
               if((lpe = skip(l->line, f->end1, f->end2, f->flags&Bflag, 1)) == nil)
                       lpe = l->line + ll;
               n = (lpe - lp) + 1;
               if(n <= 0)
                       n = 1;
               if(cl+(n+4) > kl) {
                       kl = cl+(n+4);
                       k = erealloc(k, sizeof(Key) +
                               (kl-1)*sizeof(k->key[0]));
               }
               k->klen = cl;
               (*f->dokey)(k, lp, lpe, f);
               cl = k->klen;
       }

       /*
        * global comparisons
        */
       if(!(args.uflag && cl > 0)) {
               f = &args.field[0];
               if(cl+(ll+4) > kl) {
                       kl = cl+(ll+4);
                       k = erealloc(k, sizeof(Key) +
                               (kl-1)*sizeof(k->key[0]));
               }
               k->klen = cl;
               (*f->dokey)(k, l->line, l->line+ll, f);
               cl = k->klen;
       }

       l->key = k;
       k->klen = cl;

       if(args.vflag) {
               if(write(2, l->line, l->llen) != l->llen)
                       exits("write");
               for(i=0; i<k->klen; i++) {
                       fprint(2, " %.2x", k->key[i]);
                       if(k->key[i] == 0x00 || k->key[i] == 0xff)
                               fprint(2, "\n");
               }
       }
}

void
makemapm(Field *f)
{
       int i, c;

       for(i=0; i<nelem(f->mapto); i++) {
               c = 1;
               if(i == ' ' || i == '\t')
                       c = 0;
               if(i >= 'a' && i <= 'z')
                       c = i + ('A' - 'a');
               if(i >= 'A' && i <= 'Z')
                       c = i;
               f->mapto[i] = c;
               if(args.vflag) {
                       if((i & 15) == 0)
                               fprint(2, "     ");
                       fprint(2, " %.2x", c);
                       if((i & 15) == 15)
                               fprint(2, "\n");
               }
       }
}

void
makemapd(Field *f)
{
       int i, j, c;

       for(i=0; i<nelem(f->mapto); i++) {
               c = i;
               if(f->flags & Iflag)
                       if(c < 040 || c > 0176)
                               c = -1;
               if((f->flags & Wflag) && c >= 0)
                       if(c == ' ' || c == '\t')
                               c = -1;
               if((f->flags & Dflag) && c >= 0)
                       if(!(c == ' ' || c == '\t' ||
                           (c >= 'a' && c <= 'z') ||
                           (c >= 'A' && c <= 'Z') ||
                           (c >= '0' && c <= '9'))) {
                               for(j=0; latinmap[j]; j+=3)
                                       if(c == latinmap[j+0] ||
                                          c == latinmap[j+1])
                                               break;
                               if(latinmap[j] == 0)
                                       c = -1;
                       }
               if((f->flags & Fflag) && c >= 0) {
                       if(c >= 'a' && c <= 'z')
                               c += 'A' - 'a';
                       for(j=0; latinmap[j]; j+=3)
                               if(c == latinmap[j+0] ||
                                  c == latinmap[j+1]) {
                                       c = latinmap[j+2];
                                       break;
                               }
               }
               if((f->flags & Rflag) && c >= 0 && i > 0 && i < Runeself)
                       c = ~c & 0xff;
               if(c < 0)
                       c = 0;
               f->mapto[i] = c;
               if(args.vflag) {
                       if((i & 15) == 0)
                               fprint(2, "     ");
                       fprint(2, " %.2x", c);
                       if((i & 15) == 15)
                               fprint(2, "\n");
               }
       }
}

int     latinmap[] =
{
/*      lcase   ucase   fold    */
       L'à',  L'À',  L'A',
       L'á',  L'Á',  L'A',
       L'â',  L'Â',  L'A',
       L'ä',  L'Ä',  L'A',
       L'ã',  L'Ã',  L'A',
       L'å',  L'Å',  L'A',
       L'è',  L'È',  L'E',
       L'é',  L'É',  L'E',
       L'ê',  L'Ê',  L'E',
       L'ë',  L'Ë',  L'E',
       L'ì',  L'Ì',  L'I',
       L'í',  L'Í',  L'I',
       L'î',  L'Î',  L'I',
       L'ï',  L'Ï',  L'I',
       L'ò',  L'Ò',  L'O',
       L'ó',  L'Ó',  L'O',
       L'ô',  L'Ô',  L'O',
       L'ö',  L'Ö',  L'O',
       L'õ',  L'Õ',  L'O',
       L'ø',  L'Ø',  L'O',
       L'ù',  L'Ù',  L'U',
       L'ú',  L'Ú',  L'U',
       L'û',  L'Û',  L'U',
       L'ü',  L'Ü',  L'U',
       L'æ',  L'Æ',  L'A',
       L'ð',  L'Ð',  L'D',
       L'ñ',  L'Ñ',  L'N',
       L'ý',  L'Ý',  L'Y',
       L'ç',  L'Ç',  L'C',
       0,
};

Rune*   month[12] =
{
       L"JAN",
       L"FEB",
       L"MAR",
       L"APR",
       L"MAY",
       L"JUN",
       L"JUL",
       L"AUG",
       L"SEP",
       L"OCT",
       L"NOV",
       L"DEC",
};

/************** radix sort ***********/

enum
{
       Threshold       = 14,
};

void    rsort4(Key***, ulong, int);
void    bsort4(Key***, ulong, int);

void
sort4(void *a, ulong n)
{
       if(n > Threshold)
               rsort4((Key***)a, n, 0);
       else
               bsort4((Key***)a, n, 0);
}

void
rsort4(Key ***a, ulong n, int b)
{
       Key ***ea, ***t, ***u, **t1, **u1, *k;
       Key ***part[257];
       static long count[257];
       long clist[257+257], *cp, *cp1;
       int c, lowc, higc;

       /*
        * pass 1 over all keys,
        * count the number of each key[b].
        * find low count and high count.
        */
       lowc = 256;
       higc = 0;
       ea = a+n;
       for(t=a; t<ea; t++) {
               k = **t;
               n = k->klen;
               if(b >= n) {
                       count[256]++;
                       continue;
               }
               c = k->key[b];
               n = count[c]++;
               if(n == 0) {
                       if(c < lowc)
                               lowc = c;
                       if(c > higc)
                               higc = c;
               }
       }

       /*
        * pass 2 over all counts,
        * put partition pointers in part[c].
        * save compacted indexes and counts
        * in clist[].
        */
       t = a;
       n = count[256];
       clist[0] = n;
       part[256] = t;
       t += n;

       cp1 = clist+1;
       cp = count+lowc;
       for(c=lowc; c<=higc; c++,cp++) {
               n = *cp;
               if(n) {
                       cp1[0] = n;
                       cp1[1] = c;
                       cp1 += 2;
                       part[c] = t;
                       t += n;
               }
       }
       *cp1 = 0;

       /*
        * pass 3 over all counts.
        * chase lowest pointer in each partition
        * around a permutation until it comes
        * back and is stored where it started.
        * static array, count[], should be
        * reduced to zero entries except maybe
        * count[256].
        */
       for(cp1=clist+1; cp1[0]; cp1+=2) {
               c = cp1[1];
               cp = count+c;
               while(*cp) {
                       t1 = *part[c];
                       for(;;) {
                               k = *t1;
                               n = 256;
                               if(b < k->klen)
                                       n = k->key[b];
                               u = part[n]++;
                               count[n]--;
                               u1 = *u;
                               *u = t1;
                               if(n == c)
                                       break;
                               t1 = u1;
                       }
               }
       }

       /*
        * pass 4 over all partitions.
        * call recursively.
        */
       b++;
       t = a + clist[0];
       count[256] = 0;
       for(cp1=clist+1; n=cp1[0]; cp1+=2) {
               if(n > Threshold)
                       rsort4(t, n, b);
               else
               if(n > 1)
                       bsort4(t, n, b);
               t += n;
       }
}

/*
* bubble sort to pick up
* the pieces.
*/
void
bsort4(Key ***a, ulong n, int b)
{
       Key ***i, ***j, ***k, ***l, **t;
       Key *ka, *kb;
       int n1, n2;

       l = a+n;
       j = a;

loop:
       i = j;
       j++;
       if(j >= l)
               return;

       ka = **i;
       kb = **j;
       n1 = ka->klen - b;
       n2 = kb->klen - b;
       if(n1 > n2)
               n1 = n2;
       if(n1 <= 0)
               goto loop;
       n2 = ka->key[b] - kb->key[b];
       if(n2 == 0)
               n2 = memcmp(ka->key+b, kb->key+b, n1);
       if(n2 <= 0)
               goto loop;

       for(;;) {
               k = i+1;

               t = *k;
               *k = *i;
               *i = t;

               if(i <= a)
                       goto loop;

               i--;
               ka = **i;
               kb = *t;
               n1 = ka->klen - b;
               n2 = kb->klen - b;
               if(n1 > n2)
                       n1 = n2;
               if(n1 <= 0)
                       goto loop;
               n2 = ka->key[b] - kb->key[b];
               if(n2 == 0)
                       n2 = memcmp(ka->key+b, kb->key+b, n1);
               if(n2 <= 0)
                       goto loop;
       }
}